Rev 25117 |
Rev 25163 |
Go to most recent revision |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
For general Scribus (>=1.3.2) copyright and licensing information please refer
to the COPYING file provided with the program. Following this notice may exist
a copyright and/or license notice that predates the release of Scribus 1.3.2
for which a new license (GPL+exception) is in place.
*/
/***************************************************************************
pdflib_core.cpp - description
-------------------
begin : Sat Jan 19 2002
copyright : (C) 2002 by Franz Schmid
email : Franz.Schmid@altmuehlnet.de
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#include "pdflib_core.h"
#include "scconfig.h"
#if defined(_MSC_VER) && !defined(_USE_MATH_DEFINES)
#define _USE_MATH_DEFINES
#endif
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <string>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "rc4.h"
#include <QByteArray>
#include <QCryptographicHash>
#include <QDateTime>
#include <QDataStream>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QImage>
#include <QList>
#include <QPainterPath>
#include <QRect>
#include <QRegExp>
#include <QScopedPointer>
#include <QStack>
#include <QString>
#include <QTemporaryFile>
#include <QTextCodec>
#include <QtXml>
#include <QUuid>
#include "cmsettings.h"
#include "commonstrings.h"
#include "pageitem.h"
#include "pageitem_textframe.h"
#include "pageitem_group.h"
#include "pageitem_table.h"
#include "pdfoptions.h"
#include "prefscontext.h"
#include "prefsmanager.h"
#include "sccolor.h"
#include "sccolorengine.h"
#include "scfonts.h"
#include "text/textlayoutpainter.h"
#include "fonts/cff.h"
#include "fonts/sfnt.h"
#include "scpage.h"
#include "scpaths.h"
#include "scpattern.h"
#include "scribuscore.h"
#include "scribusdoc.h"
#include "scstreamfilter_flate.h"
#include "scstreamfilter_rc4.h"
#include "tableutils.h"
#include "util.h"
#include "util_file.h"
#include "util_formats.h"
#include "util_math.h"
#include "util_ghostscript.h"
#include "text/boxes.h"
#ifdef HAVE_OSG
#include "third_party/prc/exportPRC.h"
#endif
#include "ui/bookmwin.h"
#include "ui/bookmarkpalette.h"
#include "ui/multiprogressdialog.h"
using namespace std;
using namespace TableUtils;
#if defined(_WIN32)
#undef GetObject
#endif
static inline QByteArray FToStr(double c)
{
double v = c;
if (fabs(c) < 0.0000001)
v = 0.0;
return QByteArray::number(v, 'f', 5);
}
static inline QByteArray TransformToStr(const QTransform& tr)
{
return FToStr(tr.m11()) + " " + FToStr(tr.m12()) + " " + FToStr(tr.m21()) + " " + FToStr(tr.m22()) + " " + FToStr(tr.dx()) + " " + FToStr(tr.dy());
}
class PdfPainter: public TextLayoutPainter
{
QByteArray m_backBuffer;
QByteArray m_glyphBuffer;
QByteArray m_pathBuffer;
QMap<QString, PdfFont> m_UsedFontsP;
PDFLibCore *m_pdf;
uint m_PNr;
const ScPage* m_page;
QByteArray m_prevFontName;
int m_prevFontSize { - 1 };
QByteArray transformToStr(const QTransform& tr) const
{
return FToStr(tr.m11()) + " " + FToStr(-tr.m12()) + " " + FToStr(-tr.m21()) + " " + FToStr(tr.m22()) + " " + FToStr(tr.dx()) + " " + FToStr(-tr.dy());
}
public:
PdfPainter(PDFLibCore *pdf, uint num, const ScPage* pag) :
m_pdf(pdf),
m_PNr(num),
m_page(pag)
{}
~PdfPainter() {}
void drawGlyph(const GlyphCluster& gc) override
{
if (gc.isControlGlyphs() || gc.isEmpty())
return;
double current_x = 0.0;
for (const GlyphLayout& gl : gc.glyphs())
{
if (gl.glyph >= ScFace::CONTROL_GLYPHS)
{
current_x += gl.xadvance * gl.scaleH;
continue;
}
PdfFont pdfFont = m_pdf->UsedFontsP[font().replacementName()];
QByteArray StrokeColor;
QByteArray FillColor;
if (strokeColor().color != CommonStrings::None)
StrokeColor = m_pdf->putColor(strokeColor().color, strokeColor().shade, false);
if (fillColor().color != CommonStrings::None)
FillColor = m_pdf->putColor(fillColor().color, fillColor().shade, true);
QTransform transform = matrix();
if (pdfFont.method == Use_XForm)
{
if (!FillColor.isEmpty())
m_pathBuffer += FillColor;
m_pathBuffer += "q\n";
m_pathBuffer += transformToStr(transform) + " cm\n";
m_pathBuffer += FToStr(fontSize()) + " 0 0 " + FToStr(fontSize()) + " " + FToStr(x() + gl.xoffset + current_x) + " " + FToStr(-y() + fontSize() - gl.yoffset) + " cm\n";
if (gl.scaleV != 1.0)
m_pathBuffer += "1 0 0 1 0 " + FToStr(gl.scaleV - 1.0) + " cm\n";
m_pathBuffer += FToStr(qMax(gl.scaleH, 0.1)) + " 0 0 " + FToStr(qMax(gl.scaleV, 0.1)) + " 0 0 cm\n";
if (!FillColor.isEmpty())
m_pathBuffer += pdfFont.name + "_gl" + Pdf::toPdf(gl.glyph) + " Do\n";
m_pathBuffer += "Q\n";
}
else
{
uint gid = gl.glyph;
uint fontNr = 65535;
switch (pdfFont.encoding)
{
case Encode_256:
gid = pdfFont.glyphmap[gid];
fontNr = gid / 256;
gid = gid % 256;
break;
case Encode_224:
fontNr = gid / 224;
gid = gid % 224 + 32;
break;
case Encode_Subset:
gid = pdfFont.glyphmap[gid];
break;
case Encode_IdentityH:
break;
}
QByteArray pdfFontName = pdfFont.name;
if (fontNr != 65535)
{
pdfFontName += "S";
pdfFontName += Pdf::toPdf(fontNr);
}
if ((pdfFontName != m_prevFontName) || (fontSize() != m_prevFontSize))
{
m_glyphBuffer += pdfFontName + " " + FToStr(fontSize()) + " Tf\n";
m_prevFontName = pdfFontName;
m_prevFontSize = fontSize();
}
if (!StrokeColor.isEmpty())
m_glyphBuffer += StrokeColor;
if (!FillColor.isEmpty())
m_glyphBuffer += FillColor;
m_glyphBuffer += "0 Tr\n";
transform.translate(x() + gl.xoffset + current_x , y() + gl.yoffset);
transform.scale(qMax(gl.scaleH, 0.1), qMax(gl.scaleV, 0.1));
m_glyphBuffer += transformToStr(transform) + " Tm\n";
if (pdfFont.method != Use_Type3 || !FillColor.isEmpty())
{
switch (pdfFont.encoding)
{
case Encode_224:
case Encode_256:
m_glyphBuffer += Pdf::toHexString8(gid) + " Tj\n";
break;
case Encode_IdentityH:
default:
m_glyphBuffer += Pdf::toHexString16(gid) + " Tj\n";
break;
}
}
}
current_x += gl.xadvance * gl.scaleH;
}
}
void drawGlyphOutline(const GlyphCluster& gc, bool fill) override
{
if (gc.isControlGlyphs() || gc.isEmpty())
return;
double current_x = 0.0;
for (const GlyphLayout& gl : gc.glyphs())
{
if (gl.glyph >= ScFace::CONTROL_GLYPHS)
{
current_x += gl.xadvance * gl.scaleH;
continue;
}
PdfFont pdfFont = m_pdf->UsedFontsP[font().replacementName()];
QByteArray StrokeColor;
QByteArray FillColor;
QTransform transform = matrix();
if (strokeColor().color != CommonStrings::None)
StrokeColor = m_pdf->putColor(strokeColor().color, strokeColor().shade, false);
if (fill && fillColor().color != CommonStrings::None)
FillColor = m_pdf->putColor(fillColor().color, fillColor().shade, true);
if (pdfFont.method == Use_XForm)
{
m_pathBuffer += "q\n";
if (!StrokeColor.isEmpty())
{
m_pathBuffer += FToStr(strokeWidth()) + " w\n[] 0 d\n0 J\n0 j\n";
m_pathBuffer += StrokeColor;
}
m_pathBuffer += transformToStr(transform) + " cm\n";
if (!FillColor.isEmpty())
{
m_pathBuffer += "q\n";
m_pathBuffer += FillColor;
m_pathBuffer += FToStr(fontSize()) + " 0 0 " + FToStr(fontSize()) + " " + FToStr(x() + gl.xoffset + current_x) + " " + FToStr(-y() + fontSize() - gl.yoffset) + " cm\n";
if (gc.scaleV() != 1.0)
m_pathBuffer += "1 0 0 1 0 " + FToStr(gl.scaleV - 1.0) + " cm\n";
m_pathBuffer += FToStr(qMax(gc.scaleH(), 0.1)) + " 0 0 " + FToStr(qMax(gc.scaleV(), 0.1)) + " 0 0 cm\n";
m_pathBuffer += pdfFont.name + "_gl" + Pdf::toPdf(gl.glyph) + " Do\n";
m_pathBuffer += "Q\n";
}
m_pathBuffer += "1.0 0 0 1.0 " + FToStr(x()) + " " + FToStr(fontSize() - y()) + " cm\n";
m_pathBuffer += "1.0 0 0 1.0 " + FToStr(gl.xoffset + current_x) + " " + FToStr(gc.scaleV() * fontSize() - fontSize() - gl.yoffset) + " cm\n";
FPointArray outline = font().glyphOutline(gl.glyph);
QTransform mat;
mat.scale((fontSize() * gc.scaleH()) / 10.0, (fontSize() * gc.scaleV()) / 10.0);
outline.map(mat);
bool nPath = true;
FPoint np;
if (outline.size() > 3)
{
for (int poi = 0; poi < outline.size() - 3; poi += 4)
{
if (outline.isMarker(poi))
{
m_pathBuffer += "h\n";
nPath = true;
continue;
}
if (nPath)
{
np = outline.point(poi);
m_pathBuffer += FToStr(np.x()) + " " + FToStr(-np.y()) + " m\n";
nPath = false;
}
np = outline.point(poi + 1);
m_pathBuffer += FToStr(np.x()) + " " + FToStr(-np.y()) + " ";
np = outline.point(poi + 3);
m_pathBuffer += FToStr(np.x()) + " " + FToStr(-np.y()) + " ";
np = outline.point(poi + 2);
m_pathBuffer += FToStr(np.x()) + " " + FToStr(-np.y()) + " c\n";
}
}
m_pathBuffer += "h s\n";
m_pathBuffer += "Q\n";
}
else
{
if (!StrokeColor.isEmpty())
m_pathBuffer += StrokeColor;
uint gid = gl.glyph;
uint fontNr = 65535;
switch (pdfFont.encoding)
{
case Encode_256:
gid = pdfFont.glyphmap[gid];
fontNr = gid / 256;
gid = gid % 256;
break;
case Encode_224:
fontNr = gid / 224;
gid = gid % 224 + 32;
break;
case Encode_Subset:
gid = pdfFont.glyphmap[gid];
break;
case Encode_IdentityH:
break;
}
QByteArray pdfFontName = pdfFont.name;
if (fontNr != 65535)
{
pdfFontName += "S";
pdfFontName += Pdf::toPdf(fontNr);
}
if ((pdfFontName != m_prevFontName) || (fontSize() != m_prevFontSize))
{
m_glyphBuffer += pdfFontName + " " + FToStr(fontSize()) + " Tf\n";
m_prevFontName = pdfFontName;
m_prevFontSize = fontSize();
}
if (!StrokeColor.isEmpty())
m_glyphBuffer += StrokeColor;
if (!FillColor.isEmpty())
m_glyphBuffer += FillColor;
if (pdfFont.method == Use_Type3 && !StrokeColor.isEmpty())
{
m_pathBuffer += "q\n";
m_pathBuffer += FToStr(strokeWidth()) + " w\n[] 0 d\n0 J\n0 j\n";
m_pathBuffer += transformToStr(transform) + " cm\n";
m_pathBuffer += "1.0 0 0 1.0 " + FToStr(x()) + " " + FToStr(fontSize() - y()) + " cm\n";
m_pathBuffer += "1.0 0 0 1.0 " + FToStr(gl.xoffset + current_x) + " " + FToStr(gc.scaleV() * fontSize() - fontSize() - gl.yoffset) + " cm\n";
/* paint outline */
FPointArray outline = font().glyphOutline(gl.glyph);
QTransform mat;
mat.scale((fontSize() * gc.scaleH()) / 10.0, (fontSize() * gc.scaleV()) / 10.0);
outline.map(mat);
bool nPath = true;
FPoint np;
if (outline.size() > 3)
{
for (int poi = 0; poi < outline.size() - 3; poi += 4)
{
if (outline.isMarker(poi))
{
m_pathBuffer += "h\n";
nPath = true;
continue;
}
if (nPath)
{
np = outline.point(poi);
m_pathBuffer += FToStr(np.x()) + " " + FToStr(-np.y()) + " m\n";
nPath = false;
}
np = outline.point(poi + 1);
m_pathBuffer += FToStr(np.x()) + " " + FToStr(-np.y()) + " ";
np = outline.point(poi + 3);
m_pathBuffer += FToStr(np.x()) + " " + FToStr(-np.y()) + " ";
np = outline.point(poi + 2);
m_pathBuffer += FToStr(np.x()) + " " + FToStr(-np.y()) + " c\n";
}
}
m_pathBuffer += "h s\n";
m_pathBuffer += "Q\n";
}
else
{
m_glyphBuffer += FToStr(strokeWidth()) + " w ";
if (fill)
m_glyphBuffer += "2 Tr\n";
else
m_glyphBuffer += "1 Tr\n";
}
transform.translate(x() + gl.xoffset + current_x, y() + gl.yoffset);
transform.scale(qMax(gc.scaleH(), 0.1), qMax(gc.scaleV(), 0.1));
m_glyphBuffer += transformToStr(transform) + " Tm\n";
if (pdfFont.method != Use_Type3 || !FillColor.isEmpty())
{
switch (pdfFont.encoding)
{
case Encode_224:
case Encode_256:
m_glyphBuffer += Pdf::toHexString8(gid) + " Tj\n";
break;
case Encode_IdentityH:
default:
m_glyphBuffer += Pdf::toHexString16(gid) + " Tj\n";
break;
}
}
}
current_x += gl.xadvance * gl.scaleH;
}
}
void drawLine(QPointF start, QPointF end) override
{
QTransform transform = matrix();
transform.translate(x(), y());
m_pathBuffer += "q\n";
m_pathBuffer += transformToStr(transform) + " cm\n";
m_pathBuffer += m_pdf->putColor(strokeColor().color, strokeColor().shade, false);
m_pathBuffer += FToStr(strokeWidth()) + " w\n";
m_pathBuffer += FToStr(start.x()) + " " + FToStr(-start.y()) + " m\n";
m_pathBuffer += FToStr(end.x()) + " " + FToStr(-end.y()) + " l\n";
m_pathBuffer += "S\n";
m_pathBuffer += "Q\n";
}
QByteArray getBuffer()
{
return m_backBuffer + "BT\n" + m_glyphBuffer + "ET\n" + m_pathBuffer;
}
void drawRect(const QRectF& rect) override
{
QTransform transform = matrix();
// transform.translate(x(), y());
double rectX = x() + rect.x();
double rectY = -y() - rect.y();
m_backBuffer += "q\n";
m_backBuffer += transformToStr(transform) + " cm\n";
m_backBuffer += "n\n";
m_backBuffer += m_pdf->putColor(fillColor().color, fillColor().shade, true);
m_backBuffer += m_pdf->putColor(strokeColor().color, strokeColor().shade, false);
m_backBuffer += FToStr(rectX) + " " + FToStr(rectY) + " m\n";
m_backBuffer += FToStr(rectX + rect.width()) + " " + FToStr(rectY) + " l\n";
m_backBuffer += FToStr(rectX + rect.width()) + " " + FToStr(rectY - rect.height()) + " l\n";
m_backBuffer += FToStr(rectX) + " " + FToStr(rectY - rect.height()) + " l\n";
m_backBuffer += "h\nf\n";
m_backBuffer += "Q\n";
}
void drawObject(PageItem* embedded) override
{
m_glyphBuffer += "ET\n" + m_pathBuffer;
m_pathBuffer.clear();
m_pathBuffer += "q\n";
m_pathBuffer += FToStr(scaleH()) + " 0 0 " + FToStr(scaleV()) + " " + FToStr(x() + embedded->gXpos) + " " + FToStr(-(y() + embedded->gYpos)) + " cm\n";
QByteArray output;
if (!m_pdf->PDF_ProcessItem(output, embedded, m_page, m_PNr, true))
return;
m_pathBuffer += output;
m_pathBuffer += "Q\n";
m_glyphBuffer += m_pathBuffer + "\n";
m_pathBuffer.clear();
m_prevFontName.clear();
m_prevFontSize = -1;
m_glyphBuffer += "BT\n";
}
};
PDFLibCore::PDFLibCore(ScribusDoc & docu)
: PDFLibCore(docu, docu.pdfOptions())
{
}
PDFLibCore::PDFLibCore(ScribusDoc & docu, const PDFOptions& options)
: QObject(&docu),
doc(docu),
Options(options),
usingGUI(ScCore->usingGUI())
{
Catalog.Outlines = 2;
Catalog.PageTree = 3;
Catalog.Dest = 4;
Outlines.First = 0;
Outlines.Last = 0;
Outlines.Count = 0;
pageData.ObjNum = 0;
pageData.Thumb = 0;
if (usingGUI)
{
progressDialog = new MultiProgressDialog( tr("Saving PDF"), CommonStrings::tr_Cancel, doc.scMW());
Q_CHECK_PTR(progressDialog);
QStringList barNames, barTexts;
barNames << "EMP" << "EP" << "ECPI";
barTexts << tr("Exporting Master Page:") << tr("Exporting Page:") << tr("Exporting Items on Current Page:");
QList<bool> barsNumeric;
barsNumeric << true << true << false;
progressDialog->addExtraProgressBars(barNames, barTexts, barsNumeric);
connect(progressDialog, SIGNAL(canceled()), this, SLOT(cancelRequested()));
}
}
PDFLibCore::~PDFLibCore()
{
delete progressDialog;
}
bool PDFLibCore::PDF_IsPDFX() const
{
if (Options.Version == PDFVersion::PDF_X1a)
return true;
if (Options.Version == PDFVersion::PDF_X3)
return true;
if (Options.Version == PDFVersion::PDF_X4)
return true;
return false;
}
bool PDFLibCore::PDF_IsPDFX(const PDFVersion& ver) const
{
if (ver == PDFVersion::PDF_X1a)
return true;
if (ver == PDFVersion::PDF_X3)
return true;
if (ver == PDFVersion::PDF_X4)
return true;
return false;
}
bool PDFLibCore::doExport(const QString& fn, const std::vector<int> & pageNs, const QMap<int, QImage> & thumbs)
{
QImage thumb;
bool ret = false, error = false;
int pc_exportpages=0;
int pc_exportmasterpages=0;
if (usingGUI)
progressDialog->show();
ucs2Codec = QTextCodec::codecForName("ISO-10646-UCS-2");
if (!ucs2Codec)
ucs2Codec = QTextCodec::codecForName("UTF-16");
if (!ucs2Codec)
{
PDF_Error( tr("Qt build miss both \"UTF-16\" and \"ISO-10646-UCS-2\" text codecs, pdf export is not possible") );
return false;
}
QMap<QString, QMap<uint, QString> > usedFonts;
doc.getUsedFonts(usedFonts);
if (PDF_Begin_Doc(fn, PrefsManager::instance().appPrefs.fontPrefs.AvailFonts, usedFonts, doc.scMW()->bookmarkPalette->BView))
{
QMap<int, int> pageNsMpa;
for (uint a = 0; a < pageNs.size(); ++a)
{
pageNsMpa.insert(doc.MasterNames[doc.DocPages.at(pageNs[a] - 1)->masterPageName()], 0);
}
if (usingGUI)
{
progressDialog->setOverallTotalSteps(pageNsMpa.count() + pageNs.size());
progressDialog->setTotalSteps("EMP", pageNsMpa.count());
progressDialog->setTotalSteps("EP", pageNs.size());
progressDialog->setOverallProgress(0);
progressDialog->setProgress("EMP", 0);
progressDialog->setProgress("EP", 0);
}
for (int ap = 0; ap < doc.MasterPages.count() && !abortExport; ++ap)
{
if (doc.MasterItems.count() != 0)
{
if (pageNsMpa.contains(ap))
{
qApp->processEvents();
if (!PDF_TemplatePage(doc.MasterPages.at(ap)))
error = abortExport = true;
++pc_exportmasterpages;
}
}
if (usingGUI)
{
progressDialog->setProgress("EMP", pc_exportmasterpages);
progressDialog->setOverallProgress(pc_exportmasterpages+pc_exportpages);
}
}
for (uint a = 0; a < pageNs.size() && !abortExport; ++a)
{
if (Options.Thumbnails)
thumb = thumbs[pageNs[a]];
qApp->processEvents();
if (abortExport) break;
PDF_Begin_Page(doc.DocPages.at(pageNs[a]-1), thumb);
qApp->processEvents();
if (abortExport) break;
if (!PDF_ProcessPage(doc.DocPages.at(pageNs[a]-1), pageNs[a]-1, Options.doClip))
error = abortExport = true;
qApp->processEvents();
if (abortExport) break;
PDF_End_Page();
pc_exportpages++;
if (usingGUI)
{
progressDialog->setProgress("EP", pc_exportpages);
progressDialog->setOverallProgress(pc_exportmasterpages+pc_exportpages);
}
}
ret = true;//Even when aborting we return true. Don't want that "couldn't write msg"
if (!abortExport)
{
if (PDF_IsPDFX(Options.Version))
ret = PDF_End_Doc(ScCore->PrinterProfiles[Options.PrintProf]);
else
ret = PDF_End_Doc();
}
else
closeAndCleanup();
}
if (usingGUI)
progressDialog->close();
return (ret && !error);
}
const QString& PDFLibCore::errorMessage() const
{
return ErrorMessage;
}
bool PDFLibCore::exportAborted() const
{
return abortExport;
}
//#define StartObj(n) writer.startObj((n))
#define PutDoc(s) writer.write(s)
//#define newObject() writer.newObject()
static QByteArray blendMode(int code)
{
switch (code)
{
case 0:
return "Normal";
break;
case 1:
return "Darken";
break;
case 2:
return "Lighten";
break;
case 3:
return "Multiply";
break;
case 4:
return "Screen";
break;
case 5:
return "Overlay";
break;
case 6:
return "HardLight";
break;
case 7:
return "SoftLight";
break;
case 8:
return "Difference";
break;
case 9:
return "Exclusion";
break;
case 10:
return "ColorDodge";
break;
case 11:
return "ColorBurn";
break;
case 12:
return "Hue";
break;
case 13:
return "Saturation";
break;
case 14:
return "Color";
break;
case 15:
return "Luminosity";
break;
default:
return "";
}
}
QByteArray PDFLibCore::EncStream(const QByteArray & in, PdfId ObjNum)
{
if (in.length() < 1)
return QByteArray();
if (!Options.Encrypt)
return in;
return writer.encryptBytes(in, ObjNum);
}
QByteArray PDFLibCore::EncString(const QByteArray & in, PdfId ObjNum)
{
if (in.length() < 1)
return "<>";
if (!Options.Encrypt)
{
return Pdf::toLiteralString(in);
}
return Pdf::toHexString(writer.encryptBytes(in, ObjNum));
}
QByteArray PDFLibCore::EncStringUTF16(const QString & in, PdfId ObjNum)
{
if (in.length() < 1)
return "<>";
if (!Options.Encrypt)
{
QByteArray us = Pdf::toUTF16(in);
return Pdf::toHexString(us);
}
QByteArray us = Pdf::toUTF16(in);
QByteArray tmp = Pdf::toHexString(writer.encryptBytes(us, ObjNum));
return tmp;
}
bool PDFLibCore::EncodeArrayToStream(const QByteArray& in, PdfId ObjNum)
{
if (in.size() < 1)
return true;
bool succeed = false;
if (Options.Encrypt)
{
ScStreamFilter* rc4Encode = writer.openStreamFilter(true, ObjNum);
if (rc4Encode->openFilter())
{
succeed = rc4Encode->writeData(in.data(), in.size());
succeed &= rc4Encode->closeFilter();
}
delete rc4Encode;
}
else
writer.write(in);
return (writer.getOutStream().status() == QDataStream::Ok);
}
int PDFLibCore::WriteImageToStream(ScImage& image, PdfId ObjNum, ColorSpaceEnum format, bool precal)
{
bool fromCmyk, succeed = false;
int bytesWritten = 0;
ScStreamFilter* rc4Encode = writer.openStreamFilter(Options.Encrypt, ObjNum);
if (rc4Encode->openFilter())
{
switch (format)
{
case ColorSpaceMonochrome :
fromCmyk = !Options.UseRGB && !Options.isGrayscale && !(doc.HasCMS && Options.UseProfiles2);
succeed = image.writeMonochromeDataToFilter(rc4Encode, fromCmyk); break;
case ColorSpaceGray :
succeed = image.writeGrayDataToFilter(rc4Encode, precal); break;
case ColorSpaceCMYK :
succeed = image.writeCMYKDataToFilter(rc4Encode); break;
default :
succeed = image.writeRGBDataToFilter(rc4Encode); break;
}
succeed &= rc4Encode->closeFilter();
bytesWritten = rc4Encode->writtenToStream();
delete rc4Encode;
}
return (succeed ? bytesWritten : 0);
}
int PDFLibCore::WriteJPEGImageToStream(ScImage& image, const QString& fn, PdfId ObjNum, int quality, ColorSpaceEnum format,
bool sameFile, bool precal)
{
bool succeed = true;
int bytesWritten = 0;
QFileInfo fInfo(fn);
QString ext = fInfo.suffix().toLower();
QString jpgFileName, tmpFile;
if (extensionIndicatesJPEG(ext) && sameFile)
jpgFileName = fn;
else
{
tmpFile = QDir::toNativeSeparators(ScPaths::tempFileDir() + "sc.jpg");
if (format == ColorSpaceGray && (!precal))
image.convertToGray();
if (image.convert2JPG(tmpFile, quality, format == ColorSpaceCMYK, format == ColorSpaceGray))
jpgFileName = tmpFile;
}
if (jpgFileName.isEmpty())
return 0;
if (Options.Encrypt)
{
succeed = false;
ScStreamFilter* rc4Encode = writer.openStreamFilter(true, ObjNum);
if (rc4Encode->openFilter())
{
succeed = copyFileToFilter(jpgFileName, *rc4Encode);
succeed &= rc4Encode->closeFilter();
bytesWritten = rc4Encode->writtenToStream();
}
delete rc4Encode;
}
else
{
succeed &= copyFileToStream(jpgFileName, writer.getOutStream());
QFileInfo jpgInfo(jpgFileName);
bytesWritten = jpgInfo.size();
}
if (!tmpFile.isEmpty() && QFile::exists(tmpFile))
QFile::remove(tmpFile);
return (succeed ? bytesWritten : 0);
}
int PDFLibCore::WriteFlateImageToStream(ScImage& image, PdfId ObjNum, ColorSpaceEnum format, bool precal)
{
bool fromCmyk, succeed = false;
int bytesWritten = 0;
ScStreamFilter* rc4Encode = writer.openStreamFilter(Options.Encrypt, ObjNum);
ScFlateEncodeFilter flateEncode(rc4Encode);
if (flateEncode.openFilter())
{
switch (format)
{
case ColorSpaceMonochrome :
fromCmyk = !Options.UseRGB && !Options.isGrayscale && !(doc.HasCMS && Options.UseProfiles2);
succeed = image.writeMonochromeDataToFilter(&flateEncode, fromCmyk); break;
case ColorSpaceGray :
succeed = image.writeGrayDataToFilter(&flateEncode, precal); break;
case ColorSpaceCMYK :
succeed = image.writeCMYKDataToFilter(&flateEncode); break;
default :
succeed = image.writeRGBDataToFilter(&flateEncode); break;
}
succeed &= flateEncode.closeFilter();
bytesWritten = flateEncode.writtenToStream();
}
delete rc4Encode;
return (succeed ? bytesWritten : 0);
}
bool PDFLibCore::PDF_Begin_Doc(const QString& fn, SCFonts &AllFonts, const QMap<QString, QMap<uint, QString> >& DocFonts, BookmarkView* vi)
{
if (!writer.open(fn))
return false;
inPattern = 0;
Bvie = vi;
BookMinUse = false;
UsedFontsP.clear();
UsedFontsF.clear();
writer.writeHeader(Options.Version);
PDF_Begin_Catalog();
PDF_Begin_MetadataAndEncrypt();
PDF_Begin_WriteUsedFonts(AllFonts, PDF_Begin_FindUsedFonts(AllFonts, DocFonts));
PDF_Begin_Colors();
PDF_Begin_Layers();
return true;
}
void PDFLibCore::PDF_Begin_Catalog()
{
writer.startObj(writer.CatalogObj);
PutDoc("<<\n/Type /Catalog\n");
PutDoc("/Pages " + Pdf::toObjRef(writer.PagesObj) + "\n");
if (!PDF_IsPDFX())
{
if (doc.useAcroFormFields())
{
writer.AcroFormObj = writer.newObject();
PutDoc("/AcroForm " + Pdf::toObjRef(writer.AcroFormObj) + "\n");
}
if (doc.useAnnotations())
{
writer.DestsObj = writer.newObject();
PutDoc("/Dests " + Pdf::toObjRef(writer.DestsObj) + "\n");
}
if (doc.JavaScripts.count() > 0)
{
writer.NamesObj = writer.newObject();
PutDoc("/Names " + Pdf::toObjRef(writer.NamesObj) + "\n");
}
}
if (Options.Bookmarks)
{
writer.OutlinesObj = writer.newObject();
PutDoc("/Outlines " + Pdf::toObjRef(writer.OutlinesObj) + "\n");
}
if (Options.Articles)
{
writer.ThreadsObj = writer.newObject();
PutDoc("/Threads " + Pdf::toObjRef(writer.ThreadsObj) + "\n");
}
if (Options.exportsLayers())
{
writer.OCPropertiesObj = writer.newObject();
PutDoc("/OCProperties " + Pdf::toObjRef(writer.OCPropertiesObj) + "\n");
}
if (PDF_IsPDFX())
{
writer.OutputIntentObj = writer.newObject();
PutDoc("/OutputIntents [ " + Pdf::toObjRef(writer.OutputIntentObj) + " ]\n");
}
if ((Options.Version == PDFVersion::PDF_X4))
{
writer.MetaDataObj = writer.newObject();
PutDoc("/Metadata " + Pdf::toObjRef(writer.MetaDataObj) + "\n");
}
PutDoc("/PageLayout ");
switch (Options.PageLayout)
{
case PDFOptions::SinglePage:
PutDoc("/SinglePage\n");
break;
case PDFOptions::OneColumn:
PutDoc("/OneColumn\n");
break;
case PDFOptions::TwoColumnLeft:
PutDoc("/TwoColumnLeft\n");
break;
case PDFOptions::TwoColumnRight:
PutDoc("/TwoColumnRight\n");
break;
}
if (Options.displayBookmarks)
PutDoc("/PageMode /UseOutlines\n");
else if (Options.displayFullscreen)
PutDoc("/PageMode /FullScreen\n");
else if (Options.displayThumbs)
PutDoc("/PageMode /UseThumbs\n");
else if (Options.Version.supportsOCGs() && Options.displayLayers)
PutDoc("/PageMode /UseOC\n");
if (!Options.openAction.isEmpty())
{
writer.OpenActionObj = writer.newObject();
PutDoc("/OpenAction << /S /JavaScript /JS " + Pdf::toPdf(writer.OpenActionObj) + " 0 R >>\n");
}
QDateTime dt = QDateTime::currentDateTimeUtc();
Datum = Pdf::toDateString(dt);
// "D:";
// tmp.sprintf("%4d", d.year());
// tmp.replace(QRegExp(" "), "0");
// Datum += tmp;
// tmp.sprintf("%2d", d.month());
// tmp.replace(QRegExp(" "), "0");
// Datum += tmp;
// tmp.sprintf("%2d", d.day());
// tmp.replace(QRegExp(" "), "0");
// Datum += tmp;
// tmp = dt.time().toString();
// tmp.replace(QRegExp(":"), "");
// Datum += tmp;
// Datum += "Z";
// only include XMP to PDF/X-4 at the moment, could easily be extended to include it to any PDF
if (Options.Version == PDFVersion::PDF_X4)
generateXMP(dt.toString("yyyy-MM-ddThh:mm:ssZ"));
/* The following code makes the resulting PDF "Reader enabled" in Acrobat Reader 8
but sadly it doesn't work with newer version, because its based on a bug in AR 8
PutDoc("/Perms\n");
PutDoc("<<\n");
PutDoc("/UR3\n");
PutDoc("<<\n");
PutDoc("/M (" + Datum + ")\n");
PutDoc("/Name (Scribus " + QString(VERSION) + ")\n");
PutDoc("/Reference [\n");
PutDoc("<<\n");
PutDoc("/TransformParams\n");
PutDoc("<<\n");
PutDoc("/Type /TransformParams\n");
PutDoc("/V /2.2\n");
PutDoc("/Document [/FullSave]\n");
PutDoc("/Annots [/Create/Delete/Modify/Copy/Import/Export]\n");
PutDoc("/Form [/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate]\n");
PutDoc("/Signature [/Modify]\n");
PutDoc(">>\n");
PutDoc("/TransformMethod /UR3\n");
PutDoc("/Type /SigRef\n");
PutDoc(">>\n");
PutDoc("]\n");
PutDoc("/Type /Sig\n");
PutDoc(">>\n");
PutDoc(">>\n");
*/
PutDoc("/ViewerPreferences\n<<\n/PageDirection ");
PutDoc( Options.Binding == 0 ? "/L2R\n" : "/R2L\n");
if (Options.hideToolBar)
PutDoc("/HideToolbar true\n");
if (Options.hideMenuBar)
PutDoc("/HideMenubar true\n");
if (Options.fitWindow)
PutDoc("/FitWindow true\n");
PutDoc(" >>\n>>");
writer.endObj(writer.CatalogObj);
}
void PDFLibCore::PDF_Begin_MetadataAndEncrypt()
{
QString IDg(Datum);
IDg += Options.fileName;
IDg += "Scribus " + QString(VERSION);
IDg += "Scribus PDF Library " + QString(VERSION);
IDg += doc.documentInfo().title();
IDg += doc.documentInfo().author();
IDg += "/False";
writer.setFileId(Pdf::toPdfDocEncoding(IDg));
if (Options.Encrypt)
{
writer.setEncryption(Options.Version.supports128BitsEncryption(), Pdf::toPdfDocEncoding(Options.PassOwner), Pdf::toPdfDocEncoding(Options.PassUser), Options.Permissions);
}
writer.startObj(writer.InfoObj);
PutDoc("<<\n/Creator " + EncString(QByteArray("Scribus ") + VERSION, writer.InfoObj) + "\n");
PutDoc("/Producer " + EncString(QByteArray("Scribus PDF Library ") + VERSION, writer.InfoObj) + "\n");
QString docTitle = doc.documentInfo().title();
if ((PDF_IsPDFX()) && (docTitle.isEmpty()))
PutDoc("/Title " + EncStringUTF16(doc.documentFileName(), writer.InfoObj) + "\n");
else
PutDoc("/Title " + EncStringUTF16(doc.documentInfo().title(), writer.InfoObj) + "\n");
PutDoc("/Author " + EncStringUTF16(doc.documentInfo().author(), writer.InfoObj) + "\n");
PutDoc("/Subject " + EncStringUTF16(doc.documentInfo().subject(), writer.InfoObj) + "\n");
PutDoc("/Keywords " + EncStringUTF16(doc.documentInfo().keywords(), writer.InfoObj) + "\n");
PutDoc("/CreationDate " + EncString(Datum, writer.InfoObj) + "\n");
PutDoc("/ModDate " + EncString(Datum, writer.InfoObj) + "\n");
if (Options.Version == PDFVersion::PDF_X1a)
{
PutDoc("/GTS_PDFXVersion (PDF/X-1:2001)\n");
PutDoc("/GTS_PDFXConformance (PDF/X-1a:2001)\n");
}
if (Options.Version == PDFVersion::PDF_X3)
PutDoc("/GTS_PDFXVersion (PDF/X-3:2002)\n");
if (Options.Version == PDFVersion::PDF_X4)
PutDoc("/GTS_PDFXVersion (PDF/X-4)\n");
PutDoc("/Trapped /False\n>>");
writer.endObj(writer.InfoObj);
}
QMap<QString, QMap<uint, QString> >
PDFLibCore::PDF_Begin_FindUsedFonts(SCFonts &AllFonts, const QMap<QString, QMap<uint, QString> >& DocFonts)
{
QMap<QString, QMap<uint, QString> > usedFonts;
PageItem* pgit;
QMap<int, QByteArray> ind2PDFabr;
const QByteArray tmpf[] = {"/Courier", "/Courier-Bold", "/Courier-Oblique", "/Courier-BoldOblique",
"/Helvetica", "/Helvetica-Bold", "/Helvetica-Oblique", "/Helvetica-BoldOblique",
"/Times-Roman", "/Times-Bold", "/Times-Italic", "/Times-BoldItalic",
"/ZapfDingbats", "/Symbol"};
size_t ar = sizeof(tmpf) / sizeof(*tmpf);
for (uint ax = 0; ax < ar; ++ax)
ind2PDFabr[ax] = tmpf[ax];
QList<PageItem*> allItems = doc.FrameItems.values();
while (allItems.count() > 0)
{
pgit = allItems.takeFirst();
if (pgit->isGroup() || pgit->isTable())
{
allItems = pgit->getChildren() + allItems;
continue;
}
if ((pgit->itemType() == PageItem::TextFrame) || (pgit->itemType() == PageItem::PathText))
{
if (pgit->isAnnotation())
{
int annotType = pgit->annotation().Type();
bool mustEmbed = ((annotType >= Annotation::Button) && (annotType <= Annotation::Listbox) && (annotType != Annotation::Checkbox));
if ((pgit->annotation().Type() == Annotation::Checkbox) || (pgit->annotation().Type() == Annotation::RadioButton))
StdFonts.insert("/ZapfDingbats", "");
if (pgit->itemText.length() > 0 || mustEmbed)
{
if (Options.Version < PDFVersion::PDF_14)
StdFonts.insert(ind2PDFabr[pgit->annotation().Font()], "");
QString replacementName = pgit->itemText.defaultStyle().charStyle().font().replacementName();
usedFonts.insert(replacementName, DocFonts[replacementName]);
}
}
uint start = pgit->isTextFrame() ? (uint) pgit->firstInFrame() : 0;
uint stop = pgit->isTextFrame() ? (uint) pgit->lastInFrame() + 1 : (uint) pgit->itemText.length();
for (uint e = start; e < stop; ++e)
{
QString replacementName = pgit->itemText.charStyle(e).font().replacementName();
usedFonts.insert(replacementName, DocFonts[replacementName]);
}
}
}
allItems = doc.MasterItems;
while (allItems.count() > 0)
{
pgit = allItems.takeFirst();
if (pgit->isGroup() || pgit->isTable())
{
allItems = pgit->getChildren() + allItems;
continue;
}
if ((pgit->itemType() == PageItem::TextFrame) || (pgit->itemType() == PageItem::PathText))
{
if (pgit->isAnnotation())
{
int annotType = pgit->annotation().Type();
bool mustEmbed = ((annotType >= Annotation::Button) && (annotType <= Annotation::Listbox) && (annotType != Annotation::Checkbox));
if ((pgit->annotation().Type() == Annotation::Checkbox) || (pgit->annotation().Type() == Annotation::RadioButton))
StdFonts.insert("/ZapfDingbats", "");
if (pgit->itemText.length() > 0 || mustEmbed)
{
if (Options.Version < PDFVersion::PDF_14)
StdFonts.insert(ind2PDFabr[pgit->annotation().Font()], "");
QString replacementName = pgit->itemText.defaultStyle().charStyle().font().replacementName();
usedFonts.insert(replacementName, DocFonts[replacementName]);
}
}
uint start = pgit->isTextFrame() ? (uint) pgit->firstInFrame() : 0;
uint stop = pgit->isTextFrame() ? (uint) pgit->lastInFrame() + 1 : (uint) pgit->itemText.length();
for (uint e = start; e < stop; ++e)
{
QString replacementName = pgit->itemText.charStyle(e).font().replacementName();
usedFonts.insert(replacementName, DocFonts[replacementName]);
}
}
}
allItems = *(doc.Items);
while (allItems.count() > 0)
{
pgit = allItems.takeFirst();
if (pgit->isGroup() || pgit->isTable())
{
allItems = pgit->getChildren() + allItems;
continue;
}
if ((pgit->itemType() == PageItem::TextFrame) || (pgit->itemType() == PageItem::PathText))
{
if (pgit->isAnnotation())
{
int annotType = pgit->annotation().Type();
bool mustEmbed = ((annotType >= Annotation::Button) && (annotType <= Annotation::Listbox) && (annotType != Annotation::Checkbox));
if ((pgit->annotation().Type() == Annotation::Checkbox) || (pgit->annotation().Type() == Annotation::RadioButton))
StdFonts.insert("/ZapfDingbats", "");
if (pgit->itemText.length() > 0 || mustEmbed)
{
if (Options.Version < PDFVersion::PDF_14)
StdFonts.insert(ind2PDFabr[pgit->annotation().Font()], "");
QString replacementName = pgit->itemText.defaultStyle().charStyle().font().replacementName();
usedFonts.insert(replacementName, DocFonts[replacementName]);
}
}
uint start = pgit->isTextFrame() ? (uint) pgit->firstInFrame() : 0;
uint stop = pgit->isTextFrame() ? (uint) pgit->lastInFrame() + 1 : (uint) pgit->itemText.length();
for (uint e = start; e < stop; ++e)
{
QString replacementName = pgit->itemText.charStyle(e).font().replacementName();
usedFonts.insert(replacementName, DocFonts[replacementName]);
}
}
}
/* if (Options.docInfoMarks)
{
StdFonts.insert("/Helvetica", "");
} */
QStringList patterns = doc.getPatternDependencyList(doc.getUsedPatterns());
for (int c = 0; c < patterns.count(); ++c)
{
ScPattern pa = doc.docPatterns[patterns[c]];
allItems = pa.items;
while (allItems.count() > 0)
{
pgit = allItems.takeFirst();
if (pgit->isGroup() || pgit->isTable())
{
allItems = pgit->getChildren() + allItems;
continue;
}
if ((pgit->itemType() == PageItem::TextFrame) || (pgit->itemType() == PageItem::PathText))
{
if (pgit->isAnnotation())
{
if ((pgit->annotation().Type() == Annotation::Checkbox) || (pgit->annotation().Type() == Annotation::RadioButton))
StdFonts.insert("/ZapfDingbats", "");
if (pgit->itemText.length() > 0)
{
if (Options.Version < PDFVersion::PDF_14)
StdFonts.insert(ind2PDFabr[pgit->annotation().Font()], "");
QString replacementName = pgit->itemText.defaultStyle().charStyle().font().replacementName();
usedFonts.insert(replacementName, DocFonts[replacementName]);
}
}
uint start = pgit->isTextFrame() ? (uint) pgit->firstInFrame() : 0;
uint stop = pgit->isTextFrame() ? (uint) pgit->lastInFrame() + 1 : (uint) pgit->itemText.length();
for (uint e = start; e < stop; ++e)
{
QString replacementName = pgit->itemText.charStyle(e).font().replacementName();
usedFonts.insert(replacementName, DocFonts[replacementName]);
}
}
}
}
return usedFonts;
}
static QByteArray sanitizeFontName(QString fn)
{
return Pdf::toPdfDocEncoding(fn.replace( QRegExp("[\\s\\/\\{\\[\\]\\}\\<\\>\\(\\)\\%]"), "_"));
}
static QString sanitizeItemName(const QString& itemName)
{
QString sanitizedName = itemName;
sanitizedName.replace(".", "_");
return sanitizedName;
}
static QList<Pdf::Resource> asColorSpace(const QList<PdfICCD>& iccCSlist)
{
QList<Pdf::Resource> result;
for (const Pdf::Resource& r : iccCSlist)
result.append(r);
return result;
}
static QList<Pdf::Resource> asColorSpace(const QList<PdfSpotC>& spotMapValues)
{
QList<Pdf::Resource> result;
for (const Pdf::Resource& r : spotMapValues)
result.append(r);
return result;
}
void PDFLibCore::PDF_WriteStandardFonts()
{
int a = 0;
QMap<QString, QString>::Iterator itStd;
for (itStd = StdFonts.begin(); itStd != StdFonts.end(); ++itStd)
{
PdfId fontObject = writer.newObject();
writer.startObj(fontObject);
PutDoc("<<\n/Type /Font\n/Subtype /Type1\n");
PutDoc("/Name /FoStd" + Pdf::toPdf(a) + "\n");
PutDoc("/BaseFont " + Pdf::toPdfDocEncoding(itStd.key()) + "\n");
if (itStd.key() != "/ZapfDingbats")
{
PutDoc("/Encoding << \n");
PutDoc("/Differences [ \n");
PutDoc("24 /breve /caron /circumflex /dotaccent /hungarumlaut /ogonek /ring /tilde\n");
PutDoc("39 /quotesingle 96 /grave 128 /bullet /dagger /daggerdbl /ellipsis /emdash /endash /florin /fraction /guilsinglleft /guilsinglright\n");
PutDoc("/minus /perthousand /quotedblbase /quotedblleft /quotedblright /quoteleft /quoteright /quotesinglbase /trademark /fi /fl /Lslash /OE /Scaron\n");
PutDoc("/Ydieresis /Zcaron /dotlessi /lslash /oe /scaron /zcaron 160 /Euro 164 /currency 166 /brokenbar 168 /dieresis /copyright /ordfeminine 172 /logicalnot\n");
PutDoc("/.notdef /registered /macron /degree /plusminus /twosuperior /threesuperior /acute /mu 183 /periodcentered /cedilla /onesuperior /ordmasculine\n");
PutDoc("188 /onequarter /onehalf /threequarters 192 /Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla /Egrave /Eacute /Ecircumflex\n");
PutDoc("/Edieresis /Igrave /Iacute /Icircumflex /Idieresis /Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply /Oslash\n");
PutDoc("/Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls /agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla\n");
PutDoc("/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis /eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis\n");
PutDoc("/divide /oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis\n");
PutDoc("] >>\n");
}
PutDoc(">>");
writer.endObj(fontObject);
pageData.FObjects["FoStd" + Pdf::toPdf(a)] = fontObject;
itStd.value() = "FoStd" + Pdf::toPdf(a);
a++;
}
}
PdfFont PDFLibCore::PDF_WriteType3Font(const QByteArray& name, ScFace& face, const QMap<uint, QString>& usedGlyphs)
{
PdfFont result;
result.name = Pdf::toName(name);
result.usage = Used_in_Content;
result.method = Use_Type3;
result.encoding = Encode_256;
// TrueType font rasterizers use non-zero winding file rule,
// vs even-odd for current Postscript font rasterizers
// Note: OTF CFF2 will also use non-zero winding rule, so
// change code below when adding CCF2 support to Scribus
// ref: https://www.microsoft.com/typography/OTSpec/cff2.htm
bool useNonZeroRule = (face.type() == ScFace::TTF);
uint SubFonts = 0;
int glyphCount = 0;
double minx = std::numeric_limits<double>::max();
double miny = std::numeric_limits<double>::max();
double maxx = -std::numeric_limits<double>::max();
double maxy = -std::numeric_limits<double>::max();
QList<uint> glyphWidths;
QList<QByteArray> charProcs;
QStringList toUnicodeMaps;
QList<int> toUnicodeMapsCount;
QString toUnicodeMap;
int toUnicodeMapCounter = 0;
QByteArray encoding = "<< /Type /Encoding\n/Differences [ 0\n";
QByteArray fon;
QMap<uint, uint> glyphMapping;
ScFace::FaceEncoding gl;
face.glyphNames(gl);
for (auto ig = usedGlyphs.cbegin(); ig != usedGlyphs.cend(); ++ig)
{
FPoint np, np1, np2;
bool nPath = true;
fon.resize(0);
uint gid = ig.key();
FPointArray glyphOutline = face.glyphOutline(gid);
if (glyphOutline.size() > 3)
{
FPointArray gly = glyphOutline;
QTransform mat;
mat.scale(100.0, -100.0);
gly.map(mat);
gly.translate(0, 1000);
for (int poi = 0; poi < gly.size() - 3; poi += 4)
{
if (gly.isMarker(poi))
{
fon += "h\n";
nPath = true;
continue;
}
if (nPath)
{
np = gly.point(poi);
fon += FToStr(np.x()) + " " + FToStr(np.y()) + " m\n";
nPath = false;
}
np = gly.point(poi + 1);
np1 = gly.point(poi + 3);
np2 = gly.point(poi + 2);
fon += FToStr(np.x()) + " " + FToStr(np.y()) + " " + FToStr(np1.x()) + " " + FToStr(np1.y()) + " " + FToStr(np2.x()) + " " + FToStr(np2.y()) + " c\n";
}
fon += useNonZeroRule? "h f\n" : "h f*\n";
np = getMinClipF(&gly);
np1 = getMaxClipF(&gly);
}
else
{
// 13654: make evince emit warning about "Syntax Error: No current point in closepath"
// PDF specification is not quite clear about that, but a single "h" operator might not
// be a valid way to make an empty path
// fon = "h";
np = FPoint(0, 0);
np1 = FPoint(0, 0);
}
fon.prepend(Pdf::toPdf(qRound(np1.x())) + " 0 " + Pdf::toPdf(qRound(np.x())) + " " + Pdf::toPdf(qRound(np.y())) + " " + Pdf::toPdf(qRound(np1.x())) + " " + Pdf::toPdf(qRound(np1.y())) + " d1\n");
minx = qMin(minx, np.x());
miny = qMin(miny, np.y());
maxx = qMax(maxx, np1.x());
maxy = qMax(maxy, np1.y());
glyphWidths.append(qRound(np1.x()));
PdfId charProcObject = writer.newObject();
writer.startObj(charProcObject);
if (Options.Compress)
fon = CompressArray(fon);
PutDoc("<< /Length " + Pdf::toPdf(fon.length() + 1));
if (Options.Compress)
PutDoc("\n/Filter /FlateDecode");
PutDoc("\n>>\nstream\n" + EncStream(fon, charProcObject) + "\nendstream");
writer.endObj(charProcObject);
// #15449 : in some cases we cannot retrieve glyph names for all glyphs we need
// using ScFace's glyphNames(), so generate custom glyph names using glyph index.
// With Type 3 fonts we have more flexibility than with other fonts.
QString glGlyphName, glToUnicode;
auto glyphIt = gl.constFind(gid);
if ((glyphIt != gl.constEnd()) && (glyphIt->charcode > 0))
glToUnicode = QString::asprintf("%04X", glyphIt->charcode);
if (glToUnicode.isEmpty() && usedGlyphs.contains(gid))
{
QString uniStr = usedGlyphs.value(gid);
for (int i = 0; i < uniStr.length(); ++i)
glToUnicode += QString::asprintf("%04X", uniStr.at(i).unicode());
}
if (glToUnicode.isEmpty())
glToUnicode = QString("0000");
glGlyphName = "gly" + QString::asprintf("%04X", gid);
charProcs.append(Pdf::toName(glGlyphName) + " " + Pdf::toPdf(charProcObject) + " 0 R\n");
encoding += Pdf::toName(glGlyphName) + " ";
glyphMapping.insert(gid, glyphCount + SubFonts * 256);
QString tmp = QString::asprintf("%02X", glyphCount);
toUnicodeMap += "<" + Pdf::toAscii(tmp) + "> <" + Pdf::toAscii(glToUnicode) + ">\n";
toUnicodeMapCounter++;
if (toUnicodeMapCounter == 100)
{
toUnicodeMaps.append(toUnicodeMap);
toUnicodeMapsCount.append(toUnicodeMapCounter);
toUnicodeMap.clear();
toUnicodeMapCounter = 0;
}
glyphCount++;
int glyphsLeft = usedGlyphs.count() - SubFonts * 256;
if ((glyphCount > 255) || (glyphCount == glyphsLeft))
{
PdfId fontWidths = writer.newObject();
writer.startObj(fontWidths);
PutDoc("[ ");
for (int ww = 0; ww < glyphWidths.count(); ++ww)
{
PutDoc(Pdf::toPdf(glyphWidths[ww]) + " ");
}
PutDoc("]");
writer.endObj(fontWidths);
PdfId fontCharProcs = writer.newObject();
writer.startObj(fontCharProcs);
PutDoc("<<\n");
for (int ww = 0; ww < charProcs.count(); ++ww)
{
PutDoc(charProcs[ww]);
}
PutDoc(">>");
writer.endObj(fontCharProcs);
PdfId fontEncoding = writer.newObject();
writer.startObj(fontEncoding);
PutDoc(encoding);
PutDoc("]\n");
PutDoc(">>");
writer.endObj(fontEncoding);
if (toUnicodeMapCounter != 0)
{
toUnicodeMaps.append(toUnicodeMap);
toUnicodeMapsCount.append(toUnicodeMapCounter);
}
QByteArray toUnicodeMapStream;
toUnicodeMapStream += "/CIDInit /ProcSet findresource begin\n";
toUnicodeMapStream += "12 dict begin\n";
toUnicodeMapStream += "begincmap\n";
toUnicodeMapStream += "/CIDSystemInfo <<\n";
toUnicodeMapStream += "/Registry (Adobe)\n";
toUnicodeMapStream += "/Ordering (UCS)\n";
toUnicodeMapStream += "/Supplement 0\n";
toUnicodeMapStream += ">> def\n";
toUnicodeMapStream += "/CMapName /Adobe-Identity-UCS def\n";
toUnicodeMapStream += "/CMapType 2 def\n";
toUnicodeMapStream += "1 begincodespacerange\n";
toUnicodeMapStream += "<0000> <FFFF>\n";
toUnicodeMapStream += "endcodespacerange\n";
for (int uniC = 0; uniC < toUnicodeMaps.count(); uniC++)
{
toUnicodeMapStream += Pdf::toPdf(toUnicodeMapsCount[uniC]);
toUnicodeMapStream += " beginbfchar\n";
toUnicodeMapStream += toUnicodeMaps[uniC];
toUnicodeMapStream += "endbfchar\n";
}
toUnicodeMapStream += "endcmap\n";
toUnicodeMapStream += "CMapName currentdict /CMap defineresource pop\n";
toUnicodeMapStream += "end\n";
toUnicodeMapStream += "end\n";
PdfId fontToUnicode = WritePDFStream(toUnicodeMapStream);
PdfId font3Object = writer.newObject();
writer.startObj(font3Object);
PutDoc("<<\n/Type /Font\n/Subtype /Type3\n");
PutDoc("/Name /" + name + "S" + Pdf::toPdf(SubFonts) + "\n");
PutDoc("/FirstChar 0\n");
PutDoc("/LastChar " + Pdf::toPdf(glyphCount-1) + "\n");
PutDoc("/Widths " + Pdf::toPdf(fontWidths) + " 0 R\n");
PutDoc("/CharProcs " + Pdf::toPdf(fontCharProcs) + " 0 R\n");
PutDoc("/FontBBox [" + Pdf::toPdf(qRound(minx)) + " " + Pdf::toPdf(qRound(miny)) + " " + Pdf::toPdf(qRound(maxx)) + " " + Pdf::toPdf(qRound(maxy)) + "]\n");
PutDoc("/FontMatrix [0.001 0 0 0.001 0 0]\n");
PutDoc("/Encoding " + Pdf::toPdf(fontEncoding) + " 0 R\n");
PutDoc("/ToUnicode " + Pdf::toPdf(fontToUnicode) + " 0 R\n");
PutDoc(">>");
writer.endObj(font3Object);
pageData.FObjects[name + "S" + Pdf::toPdf(SubFonts)] = font3Object;
charProcs.clear();
glyphWidths.clear();
toUnicodeMaps.clear();
toUnicodeMapsCount.clear();
toUnicodeMap.clear();
toUnicodeMapCounter = 0;
glyphCount = 0;
++SubFonts;
minx = std::numeric_limits<double>::max();
miny = std::numeric_limits<double>::max();
maxx = -std::numeric_limits<double>::max();
maxy = -std::numeric_limits<double>::max();
encoding = "<< /Type /Encoding\n/Differences [ 0\n";
}
}
result.glyphmap = glyphMapping;
return result;
}
PdfFont PDFLibCore::PDF_WriteGlyphsAsXForms(const QByteArray& fontName, ScFace& face, const QMap<uint, QString>& usedGlyphs)
{
PdfFont result;
result.name = Pdf::toName(fontName);
result.usage = Used_in_Content;
result.method = Use_XForm;
result.encoding = Encode_224;
// TrueType font rasterizers use non-zero winding file rule,
// vs even-odd for current Postscript font rasterizers
// Note: OTF CFF2 will also use non-zero winding rule, so
// change code below when adding CCF2 support to Scribus
// ref: https://www.microsoft.com/typography/OTSpec/cff2.htm
bool useNonZeroRule = (face.type() == ScFace::TTF);
QByteArray fon;
for (auto ig = usedGlyphs.cbegin(); ig != usedGlyphs.cend(); ++ig)
{
FPoint np, np1, np2;
bool nPath = true;
fon.resize(0);
uint gid = ig.key();
FPointArray glyphOutline = face.glyphOutline(gid);
if (glyphOutline.size() > 3)
{
FPointArray gly = glyphOutline;
QTransform mat;
mat.scale(0.1, 0.1);
gly.map(mat);
for (int poi = 0; poi < gly.size() - 3; poi += 4)
{
if (gly.isMarker(poi))
{
fon += "h\n";
nPath = true;
continue;
}
if (nPath)
{
np = gly.point(poi);
fon += FToStr(np.x()) + " " + FToStr(-np.y()) + " m\n";
nPath = false;
}
np = gly.point(poi + 1);
np1 = gly.point(poi + 3);
np2 = gly.point(poi + 2);
fon += FToStr(np.x()) + " " + FToStr(-np.y()) + " " +
FToStr(np1.x()) + " " + FToStr(-np1.y()) + " " +
FToStr(np2.x()) + " " + FToStr(-np2.y()) + " c\n";
}
fon += useNonZeroRule? "h f\n" : "h f*\n";
np = getMinClipF(&gly);
np1 = getMaxClipF(&gly);
}
else
{
// 13654: make evince emit warning about "Syntax Error: No current point in closepath"
// PDF specification is not quite clear about that, but a single "h" operator might not
// be a valid way to make an empty path
fon = " " /*"h"*/;
np = FPoint(0, 0);
np1 = FPoint(0, 0);
}
PdfId fontGlyphXForm = writer.newObject();
writer.startObj(fontGlyphXForm);
PutDoc("<<\n/Type /XObject\n/Subtype /Form\n/FormType 1\n");
PutDoc("/BBox [ " + FToStr(np.x()) + " " + FToStr(-np.y()) + " " + FToStr(np1.x()) + " " + FToStr(-np1.y()) + " ]\n");
PutDoc("/Resources << /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]\n");
PutDoc(">>\n");
if (Options.Compress)
fon = CompressArray(fon);
PutDoc("/Length " + Pdf::toPdf(fon.length() + 1));
if (Options.Compress)
PutDoc("\n/Filter /FlateDecode");
PutDoc(" >>\nstream\n" + EncStream(fon, fontGlyphXForm) + "\nendstream");
writer.endObj(fontGlyphXForm);
pageData.XObjects[fontName + "_gl" + Pdf::toPdf(gid)] = fontGlyphXForm;
}
return result;
}
PdfId PDFLibCore::PDF_EmbedFontObject(const QByteArray& font, const QByteArray& subtype)
{
PdfId embeddedFontObject = writer.newObject();
writer.startObj(embeddedFontObject);
int len = font.length();
QByteArray ttf = (Options.Compress? CompressArray(font) : font);
//qDebug() << QString("sfnt data: size=%1 compressed=%2").arg(len).arg(bb.length());
PutDoc("<<\n/Length " + Pdf::toPdf(ttf.length() + 1) + "\n");
PutDoc("/Length1 " + Pdf::toPdf(len) + "\n");
if (subtype.size() > 0)
PutDoc("/Subtype " + subtype);
if (Options.Compress)
PutDoc("/Filter /FlateDecode\n");
PutDoc(">>\nstream\n");
EncodeArrayToStream(ttf, embeddedFontObject);
PutDoc("\nendstream");
writer.endObj(embeddedFontObject);
return embeddedFontObject;
}
QByteArray PDFLibCore::PDF_GenerateSubsetTag(const QByteArray& fontName, const QList<uint>& usedGlyphs)
{
uint hash, mod;
QVector<uint> glyphVec = usedGlyphs.toVector();
hash = qHashBits(fontName.constData(), fontName.size(), 0);
hash = qHashBits(glyphVec.constData(), glyphVec.size() * sizeof(uint), hash);
QByteArray subsetTag(6, (char) 0);
for (int i = 0; i < 6; ++i)
{
mod = hash % 26;
subsetTag[i] = 'A' + mod;
hash = (hash - mod) / 26;
}
return subsetTag;
}
PdfId PDFLibCore::PDF_WriteFontDescriptor(const QByteArray& fontName, ScFace& face, ScFace::FontFormat fformat, PdfId embeddedFontObject)
{
PdfId fontDescriptor = writer.newObject();
writer.startObj(fontDescriptor);
// TODO: think about QByteArray ScFace::getFontDescriptor() -- AV
PutDoc("<<\n/Type /FontDescriptor\n");
PutDoc("/FontName " + Pdf::toName(fontName) + "\n");
PutDoc("/FontBBox [ " + Pdf::toAscii(face.pdfFontBBoxAsString()) + " ]\n");
PutDoc("/Flags ");
//FIXME: isItalic() should be queried from ScFace, not from Qt -- AV
//QFontInfo fo = QFontInfo(it.data());
int pfl = 0;
if (face.isFixedPitch())
pfl = pfl ^ 1;
//if (fo.italic())
if (face.italicAngleAsString() != "0")
pfl = pfl ^ 64;
// pfl = pfl ^ 4;
pfl = pfl ^ 32;
PutDoc(Pdf::toPdf(pfl) + "\n");
PutDoc("/Ascent " + Pdf::toAscii(face.pdfAscentAsString()) + "\n");
PutDoc("/Descent " + Pdf::toAscii(face.pdfDescentAsString()) + "\n");
PutDoc("/CapHeight " + Pdf::toAscii(face.pdfCapHeightAsString()) + "\n");
PutDoc("/ItalicAngle " + Pdf::toAscii(face.italicAngleAsString()) + "\n");
PutDoc("/StemV 1\n");
if (embeddedFontObject != 0)
{
if (fformat == ScFace::SFNT || fformat == ScFace::TTCF)
{
if (face.type() == ScFace::OTF)
{
PutDoc("/FontFile3 " + Pdf::toPdf(embeddedFontObject) + " 0 R\n");
}
else
{
PutDoc("/FontFile2 " + Pdf::toPdf(embeddedFontObject) + " 0 R\n");
}
}
else if (fformat == ScFace::PFB)
PutDoc("/FontFile " + Pdf::toPdf(embeddedFontObject) + " 0 R\n");
else if (fformat == ScFace::PFA)
PutDoc("/FontFile " + Pdf::toPdf(embeddedFontObject) + " 0 R\n");
}
PutDoc(">>");
writer.endObj(fontDescriptor);
return fontDescriptor;
}
PdfFont PDFLibCore::PDF_EncodeCidFont(const QByteArray& fontName, ScFace& face, const QByteArray& baseFont, PdfId fontDes, const QMap<uint, QString>& usedGlyphs, const QMap<uint,uint>& glyphmap)
{
PdfFont result;
result.name = Pdf::toName(fontName);
result.usage = Used_in_Content;
result.method = glyphmap.isEmpty()? Use_Embedded : Use_Subset;
result.encoding = glyphmap.isEmpty() ? Encode_IdentityH : Encode_Subset;
result.glyphmap = glyphmap;
PdfId fontWidths2 = writer.newObject();
writer.startObj(fontWidths2);
QList<QByteArray> toUnicodeMaps;
QList<int> toUnicodeMapsCount;
QByteArray toUnicodeMap;
int toUnicodeMapCounter = 0;
ScFace::FaceEncoding encoding;
face.glyphNames(encoding);
// In some case (arial!), all used glyphs may not be listed in the encoding map,
// and glyphs such as old numerals may be found only in the usedGlyphs map
QList<uint> keys = encoding.uniqueKeys() + usedGlyphs.uniqueKeys();
std::sort(keys.begin(), keys.end());
auto lastIt = std::unique(keys.begin(), keys.end());
keys.erase(lastIt, keys.end());
bool seenNotDef = false;
PutDoc("[ ");
for (auto git = keys.begin(); git != keys.end(); ++git)
{
uint gid = (result.encoding == Encode_Subset) ? glyphmap.value(*git, 0) : *git;
if (gid == 0 && seenNotDef)
continue;
seenNotDef |= (gid == 0);
PutDoc(Pdf::toPdf(gid) + " [" + Pdf::toPdf(static_cast<int>(face.glyphWidth(*git) * 1000)) + "] ");
QString tmp = QString::asprintf("%04X", gid);
QString tmp2;
auto glyphIt = encoding.constFind(*git);
if ((glyphIt != encoding.constEnd()) && (glyphIt->charcode > 0))
tmp2 = glyphIt->toUnicode;
if (tmp2.isEmpty() && usedGlyphs.contains(*git))
{
QString uniStr = usedGlyphs.value(*git);
for (int i = 0; i < uniStr.length(); ++i)
tmp2 += QString::asprintf("%04X", uniStr.at(i).unicode());
}
if (tmp2.isEmpty())
tmp2 = "0000";
toUnicodeMap += "<" + Pdf::toAscii(tmp) + "> <" + Pdf::toAscii(tmp2) + ">\n";
toUnicodeMapCounter++;
if (toUnicodeMapCounter == 100)
{
toUnicodeMaps.append(toUnicodeMap);
toUnicodeMapsCount.append(toUnicodeMapCounter);
toUnicodeMap.clear();
toUnicodeMapCounter = 0;
}
}
PutDoc("]");
writer.endObj(fontWidths2);
if (toUnicodeMapCounter != 0)
{
toUnicodeMaps.append(toUnicodeMap);
toUnicodeMapsCount.append(toUnicodeMapCounter);
}
QByteArray toUnicodeMapStream;
toUnicodeMapStream += "/CIDInit /ProcSet findresource begin\n";
toUnicodeMapStream += "12 dict begin\n";
toUnicodeMapStream += "begincmap\n";
toUnicodeMapStream += "/CIDSystemInfo <<\n";
toUnicodeMapStream += "/Registry (Adobe)\n";
toUnicodeMapStream += "/Ordering (UCS)\n";
toUnicodeMapStream += "/Supplement 0\n";
toUnicodeMapStream += ">> def\n";
toUnicodeMapStream += "/CMapName /Adobe-Identity-UCS def\n";
toUnicodeMapStream += "/CMapType 2 def\n";
toUnicodeMapStream += "1 begincodespacerange\n";
toUnicodeMapStream += "<0000> <FFFF>\n";
toUnicodeMapStream += "endcodespacerange\n";
for (int uniC = 0; uniC < toUnicodeMaps.count(); uniC++)
{
toUnicodeMapStream += Pdf::toPdf(toUnicodeMapsCount[uniC]);
toUnicodeMapStream += " beginbfchar\n";
toUnicodeMapStream += toUnicodeMaps[uniC];
toUnicodeMapStream += "endbfchar\n";
}
toUnicodeMapStream += "endcmap\n";
toUnicodeMapStream += "CMapName currentdict /CMap defineresource pop\n";
toUnicodeMapStream += "end\n";
toUnicodeMapStream += "end\n";
PdfId fontToUnicode2 = WritePDFStream(toUnicodeMapStream);
PdfId fontObject2 = writer.newObject();
writer.startObj(fontObject2);
PutDoc("<<\n/Type /Font\n/Subtype /Type0\n");
PutDoc("/Name " + Pdf::toName(fontName) + "\n");
PutDoc("/BaseFont " + Pdf::toName(baseFont) + "\n");
PutDoc("/Encoding /Identity-H\n");
PutDoc("/ToUnicode " + Pdf::toPdf(fontToUnicode2) + " 0 R\n");
PutDoc("/DescendantFonts [");
PutDoc("<</Type /Font");
if (face.type() == ScFace::OTF)
PutDoc("/Subtype /CIDFontType0");
else
PutDoc("/Subtype /CIDFontType2");
PutDoc("/BaseFont " + Pdf::toName(baseFont));
PutDoc("/FontDescriptor " + Pdf::toPdf(fontDes) + " 0 R");
PutDoc("/CIDSystemInfo <</Ordering(Identity)/Registry(Adobe)/Supplement 0>>");
PutDoc("/DW 1000");
PutDoc("/W " + Pdf::toPdf(fontWidths2) + " 0 R");
PutDoc("/CIDToGIDMap /Identity");
PutDoc(">>"); // close CIDFont dictionary
PutDoc("]\n"); // close DescendantFonts array
PutDoc(">>");
writer.endObj(fontObject2);
pageData.FObjects[fontName] = fontObject2;
return result;
}
PdfFont PDFLibCore::PDF_EncodeSimpleFont(const QByteArray& fontName, ScFace& face, const QByteArray& baseFont, const QByteArray& subtype, bool isEmbedded, PdfId fontDes, const QMap<uint, QString>& usedGlyphs)
{
PdfFont result;
result.name = Pdf::toName(fontName);
result.usage = Used_in_Content;
result.method = isEmbedded? Use_Embedded : Use_System;
result.encoding = Encode_224;
ScFace::FaceEncoding gl;
face.glyphNames(gl);
int nglyphs = 0;
for (auto gli = gl.cbegin(); gli != gl.cend(); ++gli)
{
if (gli.key() > static_cast<uint>(nglyphs))
nglyphs = gli.key();
}
++nglyphs;
// qDebug() << QString("pdflib: nglyphs %1 max %2").arg(nglyphs).arg(face.maxGlyph());
uint Fcc = nglyphs / 224;
if ((nglyphs % 224) != 0)
Fcc += 1;
for (uint Fc = 0; Fc < Fcc; ++Fc)
{
PdfId fontWidths2 = writer.newObject();
writer.startObj(fontWidths2);
int chCount = 32;
PutDoc("[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ");
for (int ww = 32; ww < 256; ++ww)
{
uint glyph = 224 * Fc + ww - 32;
if (gl.contains(glyph))
PutDoc(Pdf::toPdf(static_cast<int>(face.glyphWidth(glyph)* 1000)) + " ");
else
PutDoc("0 ");
chCount++;
if (signed(glyph) == nglyphs-1)
break;
}
PutDoc("]");
writer.endObj(fontWidths2);
QStringList toUnicodeMaps;
QList<int> toUnicodeMapsCount;
QString toUnicodeMap;
int toUnicodeMapCounter = 0;
int crc = 0;
bool startOfSeq = true;
PdfId fontEncoding2 = writer.newObject();
writer.startObj(fontEncoding2);
PutDoc("<< /Type /Encoding\n");
PutDoc("/Differences [ \n");
for (int ww2 = 32; ww2 < 256; ++ww2)
{
uint glyph = 224 * Fc + ww2 - 32;
ScFace::FaceEncoding::ConstIterator glIt = gl.find(glyph);
if (glIt != gl.cend() && !glIt->glyphName.isEmpty())
{
const ScFace::GlyphEncoding& glEncoding = glIt.value();
if (startOfSeq)
{
PutDoc(Pdf::toPdf(ww2) + " ");
startOfSeq = false;
}
PutDoc(Pdf::toName(glEncoding.glyphName) + " ");
QString tmp = QString::asprintf("%02X", ww2);
QString tmp2 = (glEncoding.charcode > 0) ? glEncoding.toUnicode : QString();
if (tmp2.isEmpty() && usedGlyphs.contains(glyph))
{
QString uniStr = usedGlyphs.value(glyph);
for (int i = 0; i < uniStr.length(); ++i)
tmp2 += QString::asprintf("%04X", uniStr.at(i).unicode());
}
if (tmp2.isEmpty())
tmp2 = "0000";
toUnicodeMap += "<" + Pdf::toAscii(tmp) + "> <" + Pdf::toAscii(tmp2) + ">\n";
toUnicodeMapCounter++;
if (toUnicodeMapCounter == 100)
{
toUnicodeMaps.append(toUnicodeMap);
toUnicodeMapsCount.append(toUnicodeMapCounter);
toUnicodeMap.clear();
toUnicodeMapCounter = 0;
}
crc++;
}
else
{
startOfSeq = true;
}
if (signed(glyph) == nglyphs-1)
break;
if (crc > 8)
{
PutDoc("\n");
crc = 0;
}
}
if (toUnicodeMapCounter != 0)
{
toUnicodeMaps.append(toUnicodeMap);
toUnicodeMapsCount.append(toUnicodeMapCounter);
}
PutDoc("]\n");
PutDoc(">>");
writer.endObj(fontEncoding2);
QByteArray toUnicodeMapStream;
toUnicodeMapStream += "/CIDInit /ProcSet findresource begin\n";
toUnicodeMapStream += "12 dict begin\n";
toUnicodeMapStream += "begincmap\n";
toUnicodeMapStream += "/CIDSystemInfo <<\n";
toUnicodeMapStream += "/Registry (Adobe)\n";
toUnicodeMapStream += "/Ordering (UCS)\n";
toUnicodeMapStream += "/Supplement 0\n";
toUnicodeMapStream += ">> def\n";
toUnicodeMapStream += "/CMapName /Adobe-Identity-UCS def\n";
toUnicodeMapStream += "/CMapType 2 def\n";
toUnicodeMapStream += "1 begincodespacerange\n";
toUnicodeMapStream += "<0000> <FFFF>\n";
toUnicodeMapStream += "endcodespacerange\n";
for (int uniC = 0; uniC < toUnicodeMaps.count(); uniC++)
{
toUnicodeMapStream += Pdf::toPdf(toUnicodeMapsCount[uniC]);
toUnicodeMapStream += " beginbfchar\n";
toUnicodeMapStream += toUnicodeMaps[uniC];
toUnicodeMapStream += "endbfchar\n";
}
toUnicodeMapStream += "endcmap\n";
toUnicodeMapStream += "CMapName currentdict /CMap defineresource pop\n";
toUnicodeMapStream += "end\n";
toUnicodeMapStream += "end\n";
PdfId fontToUnicode2 = WritePDFStream(toUnicodeMapStream);
PdfId fontObject2 = writer.newObject();
writer.startObj(fontObject2);
PutDoc("<<\n/Type /Font\n/Subtype ");
PutDoc(subtype + "\n");
PutDoc("/Name " + Pdf::toName(fontName) + "S" + Pdf::toPdf(Fc) + "\n");
PutDoc("/BaseFont " + Pdf::toName(baseFont) + "\n");
PutDoc("/FirstChar 0\n");
PutDoc("/LastChar " + Pdf::toPdf(chCount-1) + "\n");
PutDoc("/Widths " + Pdf::toPdf(fontWidths2) + " 0 R\n");
PutDoc("/Encoding " + Pdf::toPdf(fontEncoding2) + " 0 R\n");
PutDoc("/ToUnicode " + Pdf::toPdf(fontToUnicode2) + " 0 R\n");
PutDoc("/FontDescriptor " + Pdf::toPdf(fontDes) + " 0 R\n");
PutDoc(">>");
writer.endObj(fontObject2);
pageData.FObjects[fontName + "S" + Pdf::toPdf(Fc)] = fontObject2;
} // for (Fc)
return result;
}
PdfFont PDFLibCore::PDF_EncodeFormFont(const QByteArray& fontName, ScFace& face, const QByteArray& baseFont, const QByteArray& subtype, PdfId fontDes)
{
PdfFont formFont;
formFont.name = Pdf::toName(fontName) + "Form";
formFont.usage = Used_in_Forms;
PdfId fontWidthsForm = writer.newObject();
writer.startObj(fontWidthsForm);
PutDoc("[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ");
for (uint ww = 32; ww < 256; ++ww)
{
int unicode = Pdf::fromPDFDocEncoding(ww);
uint glyph = face.char2CMap(unicode);
if (glyph > 0)
PutDoc(Pdf::toPdf(static_cast<int>(face.glyphWidth(glyph)* 1000)) + " ");
else
PutDoc("0 ");
}
PutDoc("]");
writer.endObj(fontWidthsForm);
PdfId fontObjectForm = writer.newObject();
writer.startObj(fontObjectForm);
PutDoc("<<\n/Type /Font\n/Subtype ");
PutDoc(subtype + "\n");
PutDoc("/Name " + formFont.name + "\n");
PutDoc("/BaseFont " + Pdf::toName(baseFont) + "\n");
PutDoc("/Encoding << \n");
PutDoc("/Differences [ \n");
PutDoc("24 /breve /caron /circumflex /dotaccent /hungarumlaut /ogonek /ring /tilde\n");
PutDoc("39 /quotesingle 96 /grave 128 /bullet /dagger /daggerdbl /ellipsis /emdash /endash /florin /fraction /guilsinglleft /guilsinglright\n");
PutDoc("/minus /perthousand /quotedblbase /quotedblleft /quotedblright /quoteleft /quoteright /quotesinglbase /trademark /fi /fl /Lslash /OE /Scaron\n");
PutDoc("/Ydieresis /Zcaron /dotlessi /lslash /oe /scaron /zcaron 160 /Euro 164 /currency 166 /brokenbar 168 /dieresis /copyright /ordfeminine 172 /logicalnot\n");
PutDoc("/.notdef /registered /macron /degree /plusminus /twosuperior /threesuperior /acute /mu 183 /periodcentered /cedilla /onesuperior /ordmasculine\n");
PutDoc("188 /onequarter /onehalf /threequarters 192 /Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla /Egrave /Eacute /Ecircumflex\n");
PutDoc("/Edieresis /Igrave /Iacute /Icircumflex /Idieresis /Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply /Oslash\n");
PutDoc("/Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls /agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla\n");
PutDoc("/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis /eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis\n");
PutDoc("/divide /oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis\n");
PutDoc("] >>\n");
PutDoc("/FirstChar 0\n");
PutDoc("/LastChar 255\n");
PutDoc("/Widths " + Pdf::toPdf(fontWidthsForm) + " 0 R\n");
PutDoc("/FontDescriptor " + Pdf::toPdf(fontDes) + " 0 R\n");
PutDoc(">>");
writer.endObj(fontObjectForm);
pageData.FObjects[fontName + "Form"] = fontObjectForm;
UsedFontsF.insert(face.replacementName(), formFont);
return formFont;
}
/*
static void dumpFont(QString name, QByteArray data)
{
QFile file(name);
if (file.open(QIODevice::WriteOnly))
{
QDataStream out(&file);
out.writeRawData(data.data(), data.length());
file.close();
}
}
static void dumpCFF(QString name, cff::CFF font)
{
QFile file(name);
if (file.open(QIODevice::WriteOnly))
{
QDataStream out(&file);
font.dump(out);
file.close();
}
}
*/
PdfFont PDFLibCore::PDF_WriteTtfSubsetFont(const QByteArray& fontName, ScFace& face, const QMap<uint, QString>& usedGlyphs)
{
QByteArray font;
face.rawData(font);
/*dumpFont(face.psName() + ".ttf", font);*/
QList<ScFace::gid_type> glyphs = usedGlyphs.uniqueKeys();
glyphs.removeAll(0);
glyphs.prepend(0);
QMap<uint, uint> glyphMap;
QByteArray subset = sfnt::subsetFace(font, glyphs, glyphMap);
/*dumpFont(face.psName() + "subs.ttf", subset);*/
QByteArray baseFont = sanitizeFontName(face.psName());
QByteArray subsetTag = PDF_GenerateSubsetTag(baseFont, glyphs);
QByteArray subsetName = subsetTag + '+' + baseFont;
PdfId embeddedFontObj = PDF_EmbedFontObject(subset, QByteArray());
PdfId fontDes = PDF_WriteFontDescriptor(subsetName, face, face.format(), embeddedFontObj);
PdfFont result = PDF_EncodeCidFont(fontName, face, subsetName, fontDes, usedGlyphs, glyphMap);
return result;
}
PdfFont PDFLibCore::PDF_WriteCffSubsetFont(const QByteArray& fontName, ScFace& face, const QMap<uint, QString>& usedGlyphs)
{
// QByteArray sfnt; //TEST
// face.rawData(sfnt);
// QByteArray cff = sfnt::getTable(sfnt, "CFF ");
// dumpFont(fontName, cff);
// cff::CFF cfffont(cff);
// cfffont.dump();
// QByteArray subsetfont = cff::subsetFace(cff, it.value().keys());
// dumpFont(it.key() + "subs.cff", subsetfont);
// cff::CFF subset(subsetfont);
// subset.dump();
// PDF_WriteFontDescriptor(fontName, face, fformat, 0);
// // END
QByteArray font, data;
face.rawData(data);
font = sfnt::getTable(data, "CFF ");
/*dumpFont(face.psName() + ".cff", font);*/
QList<ScFace::gid_type> glyphs = usedGlyphs.uniqueKeys();
glyphs.removeAll(0);
glyphs.prepend(0);
QMap<uint, uint> glyphMap;
QByteArray subset = cff::subsetFace(font, glyphs, glyphMap);
if (subset.isEmpty())
{
PdfFont result = PDF_WriteType3Font(fontName, face, usedGlyphs);
return result;
}
/*dumpFont(face.psName() + "subs.cff", subset);*/
QByteArray baseFont = sanitizeFontName(face.psName());
QByteArray subsetTag = PDF_GenerateSubsetTag(baseFont, glyphs);
QByteArray subsetName = subsetTag + '+' + baseFont;
PdfId embeddedFontObj = PDF_EmbedFontObject(subset, "/CIDFontType0C");
PdfId fontDes = PDF_WriteFontDescriptor(subsetName, face, face.format(), embeddedFontObj);
PdfFont result = PDF_EncodeCidFont(fontName, face, subsetName, fontDes, usedGlyphs, glyphMap);
return result;
}
PdfFont PDFLibCore::PDF_WriteOpenTypeSubsetFont(const QByteArray& fontName, ScFace& face, const QMap<uint, QString>& usedGlyphs)
{
QByteArray data;
face.rawData(data);
QList<ScFace::gid_type> glyphs = usedGlyphs.uniqueKeys();
glyphs.removeAll(0);
glyphs.prepend(0);
QMap<uint, uint> glyphMap;
QByteArray subset = sfnt::subsetFaceWithHB(data, glyphs, face.faceIndex(), glyphMap);
if (subset.isEmpty())
{
PdfFont result = PDF_WriteType3Font(fontName, face, usedGlyphs);
return result;
}
QByteArray subType = "/OpenType";
if (!Options.supportsEmbeddedOpenTypeFonts())
{
QByteArray cffData = sfnt::getTable(subset, "CFF ");
QByteArray cffSubset(cffData.data(), cffData.length());
if (cffSubset.isEmpty())
{
PdfFont result = PDF_WriteType3Font(fontName, face, usedGlyphs);
return result;
}
subset = cffSubset;
subType = "/CIDFontType0C";
}
/*dumpFont(face.psName() + "subs.otf", subset);*/
QByteArray baseFont = sanitizeFontName(face.psName());
QByteArray subsetTag = PDF_GenerateSubsetTag(baseFont, glyphs);
QByteArray subsetName = subsetTag + '+' + baseFont;
PdfId embeddedFontObj = PDF_EmbedFontObject(subset, subType);
PdfId fontDes = PDF_WriteFontDescriptor(subsetName, face, face.format(), embeddedFontObj);
PdfFont result = PDF_EncodeCidFont(fontName, face, subsetName, fontDes, usedGlyphs, glyphMap);
return result;
}
PdfId PDFLibCore::PDF_EmbedType1AsciiFontObject(const QByteArray& fon)
{
QByteArray fon2;
PdfId embeddedFontObject = writer.newObject();
writer.startObj(embeddedFontObject);
int len1 = fon.indexOf("eexec") + 5;
fon2 = fon.left(len1) + "\n";
int len2 = fon.indexOf("0000000000000000000000000");
if (len2 == -1)
len2 = fon.length() + 1;
// QString tm;
// uint value;
// bool ok = true;
// int count = 0;
// for (int xx = len1; xx < len2-1; ++xx)
// {
// tm = fon.at(xx);
// if ((tm == QChar(13)) || (tm == QChar(10)))
// continue;
// xx++;
// count++;
// tm += fon.at(xx);
// value = tm.toUInt(&ok, 16);
// fon2 += char(value);
// }
QByteArray hexData = QByteArray::fromHex(fon.mid(len1, len2-len1));
fon2 += hexData;
fon2 += fon.mid(len2);
if (Options.Compress)
fon2 = CompressArray(fon2);
PutDoc("<<\n/Length " + Pdf::toPdf(fon2.length() + 1) + "\n");
PutDoc("/Length1 " + Pdf::toPdf(len1+1) + "\n");
PutDoc("/Length2 " + Pdf::toPdf(hexData.length()) + "\n");
if (static_cast<int>(fon.length() - len2) == -1)
PutDoc("/Length3 0\n");
else
PutDoc("/Length3 " + Pdf::toPdf(fon.length() - len2) + "\n");
if (Options.Compress)
PutDoc("/Filter /FlateDecode\n");
PutDoc(">>\nstream\n" + EncStream(fon2, embeddedFontObject) + "\nendstream");
writer.endObj(embeddedFontObject);
return embeddedFontObject;
}
PdfId PDFLibCore::PDF_EmbedType1BinaryFontObject(const QByteArray& bb)
{
PdfId embeddedFontObject = writer.newObject();
QByteArray fon;
writer.startObj(embeddedFontObject);
int posi;
for (posi = 6; posi < bb.size(); ++posi)
{
if ((bb[posi] == static_cast<char>(0x80)) && (static_cast<int>(bb[posi+1]) == 2))
break;
fon += bb[posi];
}
int len1 = fon.length();
int ulen;
ulen = bb[posi+2] & 0xff;
ulen |= (bb[posi+3] << 8) & 0xff00;
ulen |= (bb[posi+4] << 16) & 0xff0000;
ulen |= (bb[posi+5] << 24) & 0xff000000;
if (ulen > bb.size())
ulen = bb.size() - 7;
posi += 6;
for (int j = 0; j < ulen; ++j)
fon += bb[posi++];
posi += 6;
int len2 = fon.length() - len1;
for (int j = posi; j < bb.size(); ++j)
{
if ((bb[j] == static_cast<char>(0x80)) && (static_cast<int>(bb[j+1]) == 3))
break;
if (bb[j] == '\r')
fon += "\n";
else
fon += bb[j];
}
int len3 = fon.length() - len2 - len1;
if (Options.Compress)
fon = CompressArray(fon);
PutDoc("<<\n/Length " + Pdf::toPdf(fon.length() + 1) + "\n");
PutDoc("/Length1 " + Pdf::toPdf(len1) + "\n");
PutDoc("/Length2 " + Pdf::toPdf(len2) + "\n");
PutDoc("/Length3 " + Pdf::toPdf(len3) + "\n");
if (Options.Compress)
PutDoc("/Filter /FlateDecode\n");
PutDoc(">>\nstream\n" + EncStream(fon,embeddedFontObject) + "\nendstream");
writer.endObj(embeddedFontObject);
return embeddedFontObject;
}
PdfId PDFLibCore::PDF_EmbedFontObject(const QString& name, ScFace& face)
{
PdfId embeddedFontObject = 0;
if (Options.EmbedList.contains(name))
{
ScFace::FontFormat fformat = face.format();
QByteArray bb;
face.rawData(bb);
if (fformat == ScFace::PFB)
{
embeddedFontObject = PDF_EmbedType1BinaryFontObject(bb);
}
if (fformat == ScFace::PFA)
{
embeddedFontObject = PDF_EmbedType1AsciiFontObject(bb);
}
if (fformat == ScFace::SFNT || fformat == ScFace::TTCF)
{
QByteArray subtype;
if (face.type() == ScFace::OTF && Options.supportsEmbeddedOpenTypeFonts())
subtype = "/OpenType";
else if (face.type() == ScFace::OTF)
{
subtype = "/CIDFontType0C";
QByteArray cff_bb = sfnt::getTable(bb, "CFF ");
//dumpCFF(face.psName() + ".cff", cff::CFF(cff_bb));
bb = cff::extractFace(cff_bb, 0);
//dumpCFF(face.psName() + "full.cff", cff::CFF(bb));
}
embeddedFontObject = PDF_EmbedFontObject(bb, subtype);
}
}
return embeddedFontObject;
}
void PDFLibCore::PDF_Begin_WriteUsedFonts(SCFonts &AllFonts, const QMap<QString, QMap<uint, QString> >& usedFonts)
{
qDebug() << "embed list:" << QStringList(Options.EmbedList).join(", ");
qDebug() << "subset list:" << QStringList(Options.SubsetList).join(", ");
qDebug() << "outline list:" << QStringList(Options.OutlineList).join(", ");
int a = 0;
for (auto it = usedFonts.cbegin(); it != usedFonts.cend(); ++it)
{
ScFace& face(AllFonts[it.key()]);
ScFace::FontFormat fformat = face.format();
PdfFont pdfFont;
QByteArray fontName = QByteArray("Fo") + Pdf::toPdf(a);
QMap<uint, QString> usedGlyphs = it.value();
if (usedGlyphs.count() <= 0)
continue;
qDebug() << "pdf font" << it.key();
if (Options.OutlineList.contains(it.key()))
{
pdfFont = PDF_WriteGlyphsAsXForms(fontName, face, usedGlyphs);
}
else
{
if (Options.SubsetList.contains(it.key()))
{
if (fformat == ScFace::SFNT || fformat == ScFace::TTCF)
{
if (face.type() == ScFace::TTF)
{
pdfFont = PDF_WriteTtfSubsetFont(fontName, face, usedGlyphs);
}
else if (face.type() == ScFace::OTF && face.isCIDKeyed() && sfnt::canSubsetOpenTypeFonts())
{
pdfFont = PDF_WriteOpenTypeSubsetFont(fontName, face, usedGlyphs);
}
else if (face.isCIDKeyed())
{
pdfFont = PDF_WriteType3Font(fontName, face, usedGlyphs);
}
else
{
pdfFont = PDF_WriteCffSubsetFont(fontName, face, usedGlyphs);
}
}
else
{
pdfFont = PDF_WriteType3Font(fontName, face, usedGlyphs);
}
}
else
{
PdfId embeddedFontObject = PDF_EmbedFontObject(it.key(), face);
QByteArray baseFont = sanitizeFontName(face.psName());
PdfId fontDescriptor = PDF_WriteFontDescriptor(baseFont, face, fformat, embeddedFontObject);
ScFace::FaceEncoding gl;
face.glyphNames(gl);
// #14550 : for TTF fonts, we already avoid using the Postscript glyph name table
// as it is notoriously unreliable. We hence retrieve glyph names using unicode cmap
// and adobe glyph names uniXXXX convention. Unfortunately we may still not get all
// required glyph names and some glyphs may not have a name anyway. So check we have
// ps names for all glyphs we need and if not, then use CID encoding
bool hasNeededGlyphNames = face.hasNames() && (gl.count() >= usedGlyphs.count());
if ((fformat == ScFace::SFNT || fformat == ScFace::TTCF))
{
for (auto it = usedGlyphs.constBegin(); it != usedGlyphs.constEnd(); ++it)
{
int glyphIndex = it.key();
hasNeededGlyphNames &= gl.contains(glyphIndex);
if (!hasNeededGlyphNames)
break;
}
}
QByteArray subtype = (fformat == ScFace::SFNT || fformat == ScFace::TTCF) ? "/TrueType" : "/Type1";
if ((face.isSymbolic() || !hasNeededGlyphNames || Options.Version == PDFVersion::PDF_X4 || face.type() == ScFace::OTF) &&
(fformat == ScFace::SFNT || fformat == ScFace::TTCF))
{
pdfFont = PDF_EncodeCidFont(fontName, face, baseFont, fontDescriptor, usedGlyphs, QMap<uint,uint>());
}
else
{
pdfFont = PDF_EncodeSimpleFont(fontName, face, baseFont, subtype, embeddedFontObject != 0, fontDescriptor, usedGlyphs);
}
if ((Options.Version != PDFVersion::PDF_X4) && (face.type() != ScFace::OTF))
PDF_EncodeFormFont(fontName, face, baseFont, subtype, fontDescriptor);
}
pdfFont.usage = Used_in_Content;
}
a++;
QString meth;
switch (pdfFont.method)
{
case Use_System:
meth = "Systemfont (no embedding)"; break;
case Use_Embedded:
meth = "Embed"; break;
case Use_Subset:
meth = "Subset"; break;
case Use_Type3:
meth = "Subset as Type3 font"; break;
case Use_XForm:
meth = "Outline (PDF XForm)"; break;
default:
meth = "?"; break;
}
qDebug() << pdfFont.name << "uses method" << meth << "and encoding" << pdfFont.encoding;
UsedFontsP.insert(it.key(), pdfFont);
}
}
void PDFLibCore::PDF_Begin_Colors()
{
if (Options.UseLPI)
{
uint halftones = writer.newObject();
writer.startObj(halftones);
PutDoc("<<\n/Type /Halftone\n/HalftoneType 5\n");
QMap<QString,LPIData>::const_iterator itlp;
for (itlp = Options.LPISettings.constBegin(); itlp != Options.LPISettings.constEnd(); ++itlp)
{
PutDoc(Pdf::toName(itlp.key()) + "\n<<\n/Type /Halftone\n/HalftoneType 1\n/Frequency ");
PutDoc(Pdf::toPdf(itlp.value().Frequency) + "\n/Angle " + Pdf::toPdf(itlp.value().Angle) + "\n/SpotFunction ");
QByteArray func ("");
switch (itlp.value().SpotFunc)
{
case 0:
func = "/SimpleDot";
break;
case 1:
func = "/Line";
break;
case 2:
func = "/Round";
break;
case 3:
func = "/Ellipse";
break;
default:
func = "/SimpleDot";
break;
}
PutDoc(func + "\n>>\n");
}
PutDoc("/Default\n<<\n/Type /Halftone\n/HalftoneType 1\n/Frequency 50\n/Angle 45\n/SpotFunction /Round\n>>\n");
PutDoc(">>");
writer.endObj(halftones);
HTName = ResNam + Pdf::toPdf(ResCount);
Transpar[HTName] = writeGState("/HT " + Pdf::toPdf(halftones) + " 0 R\n");
ResCount++;
}
if ((doc.HasCMS) && (Options.UseProfiles) && (Options.Version != PDFVersion::PDF_X1a))
{
PdfId iccProfileObject = writer.newObject();
writer.startObj(iccProfileObject);
QByteArray dataP;
PdfICCD dataD;
loadRawBytes(ScCore->InputProfiles[Options.SolidProf], dataP);
PutDoc("<<\n");
if (Options.Compress)
{
QByteArray compData = CompressArray(dataP);
if (compData.size() > 0)
{
PutDoc("/Filter /FlateDecode\n");
dataP = compData;
}
}
PutDoc("/Length " + Pdf::toPdf(dataP.size() + 1) + "\n");
PutDoc("/N " + Pdf::toPdf(Options.SComp) + "\n");
PutDoc(">>\nstream\n");
EncodeArrayToStream(dataP, iccProfileObject);
PutDoc("\nendstream");
writer.endObj(iccProfileObject);
PdfId iccColorspace = writer.newObject();
writer.startObj(iccColorspace);
dataD.ResName = ResNam + Pdf::toPdf(ResCount);
dataD.ICCArray = "[ /ICCBased " + Pdf::toPdf(iccProfileObject) + " 0 R ]";
dataD.ResNum = iccColorspace;
dataD.components = Options.SComp;
ICCProfiles[Options.SolidProf] = dataD;
PutDoc("[ /ICCBased " + Pdf::toPdf(iccProfileObject) + " 0 R ]");
writer.endObj(iccColorspace);
ResCount++;
}
if (((!Options.isGrayscale) && (!Options.UseRGB)) && (Options.UseSpotColors))
{
doc.getUsedColors(colorsToUse);
for (auto itf = colorsToUse.begin(); itf != colorsToUse.end(); ++itf)
{
const ScColor& colorToUse = itf.value();
if (colorToUse.isProcessColor())
continue;
CMYKColorF cmykValues;
PdfSpotC spotD;
ScColorEngine::getCMYKValues(colorToUse, &doc, cmykValues);
QByteArray colorDesc = "{\ndup " + FToStr(cmykValues.c) + "\nmul exch dup ";
colorDesc += FToStr(cmykValues.m) + "\nmul exch dup ";
colorDesc += FToStr(cmykValues.y) + "\nmul exch ";
colorDesc += FToStr(cmykValues.k) + " mul }";
PdfId separationFunction = writer.newObject();
writer.startObj(separationFunction);
PutDoc("<<\n/FunctionType 4\n");
PutDoc("/Domain [0.0 1.0]\n");
PutDoc("/Range [0.0 1.0 0.0 1.0 0.0 1.0 0.0 1.0]\n");
PutDoc("/Length " + Pdf::toPdf(colorDesc.length() + 1) + "\n");
PutDoc(">>\nstream\n" + EncStream(colorDesc, separationFunction) + "\nendstream");
writer.endObj(separationFunction);
PdfId separationColorspace = writer.newObject();
writer.startObj(separationColorspace);
PutDoc("[ /Separation ");
if (colorsToUse[itf.key()].isRegistrationColor())
PutDoc("/All");
else
PutDoc(Pdf::toName(itf.key().simplified()));
PutDoc(" /DeviceCMYK " + Pdf::toObjRef(separationFunction) + " ]");
writer.endObj(separationColorspace);
spotD.ResName = spotNam + Pdf::toPdf(spotCount);
spotD.ResNum = separationColorspace;
spotMap.insert(itf.key(), spotD);
spotCount++;
}
}
if ((Options.cropMarks) || (Options.bleedMarks) || (Options.registrationMarks) || (Options.colorMarks) || (Options.docInfoMarks))
{
PdfSpotC spotD;
PdfId registrationColorspace = writer.newObject();
writer.startObj(registrationColorspace);
PutDoc("[ /Separation /All /DeviceCMYK\n");
PutDoc("<<\n/FunctionType 2\n");
PutDoc("/Domain [0.0 1.0]\n");
PutDoc("/Range [0.0 1.0 0.0 1.0 0.0 1.0 0.0 1.0]\n");
PutDoc("/C0 [0 0 0 0] \n");
PutDoc("/C1 [1 1 1 1] \n");
PutDoc("/N 1\n");
PutDoc(">>\n]");
writer.endObj(registrationColorspace);
spotD.ResName = spotNam + Pdf::toPdf(spotCount);
spotD.ResNum = registrationColorspace;
spotMapReg.insert("Register", spotD);
spotCount++;
}
}
void PDFLibCore::PDF_Begin_Layers()
{
if (Options.exportsLayers())
{
ScLayer ll;
PdfOCGInfo ocg;
ll.isPrintable = false;
ll.ID = 0;
QByteArray ocgNam("oc");
int docLayersCount = doc.Layers.count();
for (int la = 0; la < docLayersCount; ++la)
{
PdfId optionalContent = writer.newObject();
doc.Layers.levelToLayer(ll, la);
ocg.Name = ocgNam + Pdf::toPdf(ll.ID);
ocg.ObjNum = optionalContent;
ocg.visible = ll.isViewable;
OCGEntries.insert(ll.Name, ocg);
writer.startObj(optionalContent);
PutDoc("<<\n");
PutDoc("/Type /OCG\n");
PutDoc("/Name ");
PutDoc(EncStringUTF16(ll.Name, optionalContent));
PutDoc("\n");
PutDoc("/Usage <</Print <</PrintState ");
if (ll.isPrintable)
PutDoc("/ON");
else
PutDoc("/OFF");
PutDoc(">> /View <</ViewState ");
if (ll.isViewable)
PutDoc("/ON");
else
PutDoc("/OFF");
PutDoc(">>>>");
PutDoc("\n");
PutDoc(">>");
writer.endObj(optionalContent);
}
}
}
bool PDFLibCore::PDF_TemplatePage(const ScPage* pag, bool)
{
QByteArray tmpOut;
ActPageP = pag;
PageItem* ite;
QList<PageItem*> PItems;
ScLayer ll;
ll.isPrintable = false;
ll.ID = 0;
Content.clear();
double bLeft, bRight, bBottom, bTop;
getBleeds(pag, bLeft, bRight, bBottom, bTop);
pageData.AObjects.clear();
for (int la = 0; la < doc.Layers.count(); ++la)
{
doc.Layers.levelToLayer(ll, la);
PItems = doc.MasterItems;
if (!ll.isPrintable && !Options.exportsLayers())
continue;
if (Options.exportsLayers())
PutPage("/OC /" + OCGEntries[ll.Name].Name + " BDC\n");
for (int a = 0; a < PItems.count(); ++a)
{
Content.clear();
ite = PItems.at(a);
if (ite->m_layerID != ll.ID)
continue;
double x = pag->xOffset() - bLeft;
double y = pag->yOffset() - bTop;
double w = pag->width() + bLeft + bRight;
double h1 = pag->height() + bBottom + bTop;
double ilw= ite->visualLineWidth();
double x2 = ite->BoundingX - ilw / 2.0;
double y2 = ite->BoundingY - ilw / 2.0;
double w2 = ite->BoundingW + ilw;
double h2 = ite->BoundingH + ilw;
if (!QRectF(x2, y2, w2, h2).intersects(QRectF(x, y, w, h1)))
continue;
if (ite->ChangedMasterItem)
continue;
if ((!pag->pageNameEmpty()) && (ite->OwnPage != pag->pageNr()) && (ite->OwnPage != -1))
continue;
PutPage("q\n");
if ((ite->doOverprint) && (!Options.UseRGB))
{
QByteArray ShName = ResNam + Pdf::toPdf(ResCount);
ResCount++;
Transpar[ShName] = writeGState("/OP true\n"
"/op true\n"
"/OPM 1\n");
PutPage(Pdf::toName(ShName) + " gs\n");
}
/* Bookmarks on Master Pages do not make any sense */
// if ((ite->isBookmark) && (Options.Bookmarks))
// PDF_Bookmark(ite, pag->height() - (ite->yPos() - pag->yOffset()));
if (!ite->printEnabled() || (ite->isTextContainer() && !pag->pageNameEmpty()))
{
PutPage("Q\n");
continue;
}
if (ite->fillColor() != CommonStrings::None)
PutPage(putColor(ite->fillColor(), ite->fillShade(), true));
if (ite->lineColor() != CommonStrings::None)
PutPage(putColor(ite->lineColor(), ite->lineShade(), false));
Content += FToStr(fabs(ite->lineWidth())) + " w\n";
if (ite->DashValues.count() != 0)
{
PutPage("[ ");
for (auto it = ite->DashValues.cbegin(); it != ite->DashValues.cend(); ++it)
{
double da = *it;
// #8758: Custom dotted lines don't export properly to pdf
// Null values have to be exported if line end != flat
if ((da != 0) || (ite->lineEnd() != Qt::FlatCap))
PutPage(Pdf::toPdf(da) + " ");
}
PutPage("] " + Pdf::toPdf(ite->DashOffset) + " d\n");
}
else
PutPage("[" + Pdf::toAscii(getDashString(ite->PLineArt, ite->lineWidth())) + "] 0 d\n");
switch (ite->PLineEnd)
{
case Qt::FlatCap:
PutPage("0 J\n");
break;
case Qt::SquareCap:
PutPage("2 J\n");
break;
case Qt::RoundCap:
PutPage("1 J\n");
break;
default:
PutPage("0 J\n");
break;
}
switch (ite->PLineJoin)
{
case Qt::MiterJoin:
PutPage("0 j\n");
break;
case Qt::BevelJoin:
PutPage("2 j\n");
break;
case Qt::RoundJoin:
PutPage("1 j\n");
break;
default:
PutPage("0 j\n");
break;
}
PutPage("1 0 0 1 " + FToStr(ite->xPos() - pag->xOffset()) + " " + FToStr(pag->height() - (ite->yPos() - pag->yOffset())) + " cm\n");
if (ite->rotation() != 0)
{
double sr = sin(-ite->rotation()* M_PI / 180.0);
double cr = cos(-ite->rotation()* M_PI / 180.0);
if ((cr * cr) < 0.000001)
cr = 0;
if ((sr * sr) < 0.000001)
sr = 0;
PutPage(FToStr(cr) + " " + FToStr(sr) + " " + FToStr(-sr) + " " + FToStr(cr) + " 0 0 cm\n");
}
PutPage(PDF_PutSoftShadow(ite));
switch (ite->itemType())
{
case PageItem::ImageFrame:
case PageItem::LatexFrame:
PutPage("q\n");
// Same functions as for ImageFrames work for LatexFrames too
if (((ite->GrMask > 0) || (ite->fillTransparency() != 0) || (ite->fillBlendmode() != 0)) && Options.supportsTransparency())
{
PutPage(PDF_TransparenzFill(ite));
}
if ((ite->fillColor() != CommonStrings::None) || (ite->GrType != 0))
{
if (ite->GrType != 0)
{
if (ite->GrType == Gradient_Pattern)
{
if (!PDF_PatternFillStroke(tmpOut, ite))
return false;
}
else if (ite->GrType == Gradient_Hatch)
{
if (!PDF_HatchFill(tmpOut, ite))
return false;
}
else
{
if (!PDF_GradientFillStroke(tmpOut, ite))
return false;
}
if (!tmpOut.isEmpty())
{
PutPage("q\n");
if (ite->GrType == Gradient_Hatch)
PutPage(tmpOut);
else
{
PutPage(tmpOut);
PutPage(SetClipPath(ite));
PutPage(ite->fillRule ? "h\nf*\n" : "h\nf\n");
}
PutPage("Q\n");
}
}
else
{
if (ite->fillColor() != CommonStrings::None)
{
PutPage(SetClipPath(ite));
PutPage(ite->fillRule ? "h\nf*\n" : "h\nf\n");
}
}
}
PutPage("q\n");
PutPage(SetPathAndClip(ite, true));
if (ite->imageFlippedH())
PutPage("-1 0 0 1 " + FToStr(ite->width()) + " 0 cm\n");
if (ite->imageFlippedV())
PutPage("1 0 0 -1 0 " + FToStr(-ite->height()) + " cm\n");
if (!ite->imageClip.empty())
PutPage(SetImagePathAndClip(ite));
if ((ite->imageIsAvailable) && (!ite->Pfile.isEmpty()))
{
if (!PDF_Image(ite, ite->Pfile, ite->imageXScale(), ite->imageYScale(), ite->imageXOffset(), -ite->imageYOffset(), false, ite->ImageProfile, ite->UseEmbedded, ite->ImageIntent, &tmpOut))
return false;
PutPage(tmpOut);
}
PutPage("Q\n");
PutPage("Q\n");
if (((ite->lineColor() != CommonStrings::None) || (!ite->NamedLStyle.isEmpty()) || (!ite->strokePattern().isEmpty()) || (ite->GrTypeStroke > 0)))
{
if (((ite->lineTransparency() != 0) || (ite->lineBlendmode() != 0)) && Options.supportsTransparency())
PutPage(PDF_TransparenzStroke(ite));
if (ite->NamedLStyle.isEmpty())
{
ScPattern* strokePattern = doc.checkedPattern(ite->strokePattern());
if (strokePattern)
{
if (ite->patternStrokePath)
{
QPainterPath path = ite->PoLine.toQPainterPath(false);
PutPage(handleBrushPattern(ite, path, pag, pag->pageNr()));
}
else
{
if (!PDF_PatternFillStroke(tmpOut, ite, 1))
return false;
PutPage(SetClipPath(ite));
PutPage(tmpOut);
PutPage("h\nS\n");
}
}
else if (ite->GrTypeStroke > 0)
{
if (!PDF_GradientFillStroke(tmpOut, ite, true))
return false;
PutPage("q\n");
PutPage(tmpOut);
PutPage(SetClipPath(ite));
PutPage("h\nS\n");
PutPage("Q\n");
}
else if (ite->lineColor() != CommonStrings::None)
{
PutPage(SetClipPath(ite));
PutPage("h\nS\n");
}
}
else
{
multiLine ml = doc.docLineStyles[ite->NamedLStyle];
for (int it = ml.size() - 1; it > -1; it--)
{
if ((ml[it].Color != CommonStrings::None) && (ml[it].Width != 0))
{
PutPage(setStrokeMulti(&ml[it]));
PutPage(SetClipPath(ite));
PutPage("h\nS\n");
}
}
}
}
break;
case PageItem::TextFrame:
break;
case PageItem::Line:
if (((ite->lineTransparency() != 0) || (ite->lineBlendmode() != 0)) && Options.supportsTransparency())
PutPage(PDF_TransparenzStroke(ite));
if (ite->NamedLStyle.isEmpty())
{
ScPattern* strokePattern = doc.checkedPattern(ite->strokePattern());
if (strokePattern)
{
if (ite->patternStrokePath)
{
QPainterPath path;
path.moveTo(0, 0);
path.lineTo(ite->width(), 0);
PutPage(handleBrushPattern(ite, path, pag, pag->pageNr()));
}
else
{
if (!PDF_PatternFillStroke(tmpOut, ite, 1))
return false;
PutPage(tmpOut);
PutPage("0 0 m\n");
PutPage(FToStr(ite->width()) + " 0 l\n");
PutPage("S\n");
}
}
else if (ite->GrTypeStroke > 0)
{
if (!PDF_GradientFillStroke(tmpOut, ite, true))
return false;
PutPage("q\n");
PutPage(tmpOut);
PutPage("0 0 m\n");
PutPage(FToStr(ite->width()) + " 0 l\n");
PutPage("S\nQ\n");
}
else if (ite->lineColor() != CommonStrings::None)
{
PutPage("0 0 m\n");
PutPage(FToStr(ite->width()) + " 0 l\n");
PutPage("S\n");
}
}
else
{
multiLine ml = doc.docLineStyles[ite->NamedLStyle];
for (int it = ml.size() - 1; it > -1; it--)
{
if ((ml[it].Color != CommonStrings::None) && (ml[it].Width != 0))
{
PutPage(setStrokeMulti(&ml[it]));
PutPage("0 0 m\n");
PutPage(FToStr(ite->width()) + " 0 l\n");
PutPage("S\n");
}
}
}
if (ite->startArrowIndex() != 0)
{
QTransform arrowTrans;
arrowTrans.scale(-1,1);
arrowTrans.scale(ite->startArrowScale() / 100.0, ite->startArrowScale() / 100.0);
PutPage(drawArrow(ite, arrowTrans, ite->startArrowIndex()));
}
if (ite->endArrowIndex() != 0)
{
QTransform arrowTrans;
arrowTrans.translate(ite->width(), 0);
arrowTrans.scale(ite->endArrowScale() / 100.0, ite->endArrowScale() / 100.0);
PutPage(drawArrow(ite, arrowTrans, ite->endArrowIndex()));
}
break;
case PageItem::ItemType1:
case PageItem::ItemType3:
case PageItem::Polygon:
case PageItem::RegularPolygon:
case PageItem::Arc:
PutPage("q\n");
if (((ite->GrMask > 0) || (ite->fillTransparency() != 0) || (ite->fillBlendmode() != 0)) && Options.supportsTransparency())
{
PutPage(PDF_TransparenzFill(ite));
}
if (ite->GrType != 0)
{
if (ite->GrType == Gradient_Pattern)
{
if (!PDF_PatternFillStroke(tmpOut, ite))
return false;
}
else if (ite->GrType == Gradient_Hatch)
{
if (!PDF_HatchFill(tmpOut, ite))
return false;
}
else
{
if (!PDF_GradientFillStroke(tmpOut, ite))
return false;
}
if (!tmpOut.isEmpty())
{
if (ite->GrType == Gradient_Hatch)
PutPage(tmpOut);
else
{
PutPage(tmpOut);
PutPage(SetClipPath(ite));
PutPage(ite->fillRule ? "h\nf*\n" : "h\nf\n");
}
}
}
else
{
if (ite->fillColor() != CommonStrings::None)
{
PutPage(SetClipPath(ite));
PutPage(ite->fillRule ? "h\nf*\n" : "h\nf\n");
}
}
PutPage("Q\n");
if ((ite->lineColor() != CommonStrings::None) || (!ite->NamedLStyle.isEmpty()) || (!ite->strokePattern().isEmpty()) || (ite->GrTypeStroke > 0))
{
if (((ite->lineTransparency() != 0) || (ite->lineBlendmode() != 0)) && Options.supportsTransparency())
PutPage(PDF_TransparenzStroke(ite));
if ((ite->NamedLStyle.isEmpty()) && (ite->lineWidth() != 0.0))
{
ScPattern* strokePattern = doc.checkedPattern(ite->strokePattern());
if (strokePattern)
{
if (ite->patternStrokePath)
{
QPainterPath path = ite->PoLine.toQPainterPath(false);
PutPage(handleBrushPattern(ite, path, pag, pag->pageNr()));
}
else
{
if (!PDF_PatternFillStroke(tmpOut, ite, 1))
return false;
PutPage(tmpOut);
PutPage("h\nS\n");
}
}
else if (ite->GrTypeStroke > 0)
{
if (!PDF_GradientFillStroke(tmpOut, ite, true))
return false;
PutPage("q\n");
PutPage(tmpOut);
PutPage(SetClipPath(ite));
PutPage("h\nS\n");
PutPage("Q\n");
}
else if (ite->lineColor() != CommonStrings::None)
{
PutPage(SetClipPath(ite));
PutPage("h\nS\n");
}
}
else
{
multiLine ml = doc.docLineStyles[ite->NamedLStyle];
for (int it = ml.size() - 1; it > -1; it--)
{
if ((ml[it].Color != CommonStrings::None) && (ml[it].Width != 0))
{
PutPage(setStrokeMulti(&ml[it]));
PutPage(SetClipPath(ite));
PutPage("h\nS\n");
}
}
}
}
break;
case PageItem::PolyLine:
case PageItem::Spiral:
if (ite->PoLine.size() > 3) // && ((ite->PoLine.point(0) != ite->PoLine.point(1)) || (ite->PoLine.point(2) != ite->PoLine.point(3))))
{
PutPage("q\n");
if (((ite->GrMask > 0) || (ite->fillTransparency() != 0) || (ite->fillBlendmode() != 0)) && Options.supportsTransparency())
{
PutPage(PDF_TransparenzFill(ite));
}
if (ite->GrType != 0)
{
if (ite->GrType == Gradient_Pattern)
{
if (!PDF_PatternFillStroke(tmpOut, ite))
return false;
}
else if (ite->GrType == Gradient_Hatch)
{
if (!PDF_HatchFill(tmpOut, ite))
return false;
}
else
{
if (!PDF_GradientFillStroke(tmpOut, ite))
return false;
}
if (!tmpOut.isEmpty())
{
if (ite->GrType == Gradient_Hatch)
PutPage(tmpOut);
else
{
PutPage(tmpOut);
PutPage(SetClipPath(ite));
PutPage(ite->fillRule ? "h\nf*\n" : "h\nf\n");
}
}
}
else
{
if (ite->fillColor() != CommonStrings::None)
{
PutPage(SetClipPath(ite));
PutPage(ite->fillRule ? "h\nf*\n" : "h\nf\n");
}
}
PutPage("Q\n");
}
if ((ite->lineColor() != CommonStrings::None) || (!ite->NamedLStyle.isEmpty()) || (!ite->strokePattern().isEmpty()) || (ite->GrTypeStroke > 0))
{
if (((ite->lineTransparency() != 0) || (ite->lineBlendmode() != 0)) && Options.supportsTransparency())
PutPage(PDF_TransparenzStroke(ite));
if (ite->NamedLStyle.isEmpty())
{
ScPattern* strokePattern = doc.checkedPattern(ite->strokePattern());
if (strokePattern)
{
if (ite->patternStrokePath)
{
QPainterPath path = ite->PoLine.toQPainterPath(false);
PutPage(handleBrushPattern(ite, path, pag, pag->pageNr()));
}
else
{
if (!PDF_PatternFillStroke(tmpOut, ite, 1))
return false;
PutPage(tmpOut);
PutPage(SetClipPath(ite, false));
PutPage("S\n");
}
}
else if (ite->GrTypeStroke > 0)
{
if (!PDF_GradientFillStroke(tmpOut, ite, true))
return false;
PutPage("q\n");
PutPage(tmpOut);
PutPage(SetClipPath(ite, false));
PutPage("S\n");
PutPage("Q\n");
}
else if (ite->lineColor() != CommonStrings::None)
{
PutPage(SetClipPath(ite, false));
PutPage("S\n");
}
}
else
{
multiLine ml = doc.docLineStyles[ite->NamedLStyle];
for (int it = ml.size() - 1; it > -1; it--)
{
if ((ml[it].Color != CommonStrings::None) && (ml[it].Width != 0))
{
PutPage(setStrokeMulti(&ml[it]));
PutPage(SetClipPath(ite, false));
PutPage("S\n");
}
}
}
}
if (ite->startArrowIndex() != 0)
{
FPoint Start = ite->PoLine.point(0);
for (int xx = 1; xx < ite->PoLine.size(); xx += 2)
{
FPoint Vector = ite->PoLine.point(xx);
if ((Start.x() != Vector.x()) || (Start.y() != Vector.y()))
{
double r = atan2(Start.y() - Vector.y(), Start.x() - Vector.x()) * (180.0 / M_PI);
QTransform arrowTrans;
arrowTrans.translate(Start.x(), Start.y());
arrowTrans.rotate(r);
arrowTrans.scale(ite->startArrowScale() / 100.0, ite->startArrowScale() / 100.0);
PutPage(drawArrow(ite, arrowTrans, ite->startArrowIndex()));
break;
}
}
}
if (ite->endArrowIndex() != 0)
{
FPoint End = ite->PoLine.point(ite->PoLine.size() - 2);
for (uint xx = ite->PoLine.size() - 1; xx > 0; xx -= 2)
{
FPoint Vector = ite->PoLine.point(xx);
if ((End.x() != Vector.x()) || (End.y() != Vector.y()))
{
double r = atan2(End.y() - Vector.y(), End.x() - Vector.x()) * (180.0 / M_PI);
QTransform arrowTrans;
arrowTrans.translate(End.x(), End.y());
arrowTrans.rotate(r);
arrowTrans.scale(ite->endArrowScale() / 100.0, ite->endArrowScale() / 100.0);
PutPage(drawArrow(ite, arrowTrans, ite->endArrowIndex()));
break;
}
}
}
break;
case PageItem::PathText:
case PageItem::OSGFrame:
case PageItem::Table:
break;
case PageItem::Symbol:
if (doc.docPatterns.contains(ite->pattern()))
{
QByteArray tmpD;
ScPattern pat = doc.docPatterns[ite->pattern()];
PutPage("q\n");
PutPage(SetPathAndClip(ite));
if (ite->imageFlippedH())
PutPage("-1 0 0 1 " + FToStr(ite->width()) + " 0 cm\n");
if (ite->imageFlippedV())
PutPage("1 0 0 -1 0 " + FToStr(-ite->height()) + " cm\n");
QTransform trans;
trans.scale(ite->width() / pat.width, ite->height() / pat.height);
trans.translate(0.0, -ite->height());
// trans.translate(pat.items.at(0)->gXpos, -pat.items.at(0)->gYpos);
PutPage(TransformToStr(trans) + " cm\n");
groupStackPos.push(QPointF(0, ite->height()));
for (int em = 0; em < pat.items.count(); ++em)
{
PageItem* embedded = pat.items.at(em);
tmpD += "q\n";
tmpD += "1 0 0 1 " + FToStr(embedded->gXpos) + " " + FToStr(ite->height() - embedded->gYpos) + " cm\n";
QByteArray output;
if (!PDF_ProcessItem(output, embedded, pag, pag->pageNr(), true))
return false;
tmpD += output;
tmpD += "Q\n";
}
groupStackPos.pop();
if (Options.supportsTransparency())
PutPage(Write_TransparencyGroup(ite->fillTransparency(), ite->fillBlendmode(), tmpD, ite));
else
PutPage(Write_FormXObject(tmpD, ite));
PutPage("Q\n");
}
break;
case PageItem::Group:
if (ite->groupItemList.count() > 0)
{
QByteArray tmpD;
PutPage("q\n");
if (ite->groupClipping())
PutPage(SetPathAndClip(ite));
if (ite->imageFlippedH())
PutPage("-1 0 0 1 " + FToStr(ite->width()) + " 0 cm\n");
if (ite->imageFlippedV())
PutPage("1 0 0 -1 0 " + FToStr(-ite->height()) + " cm\n");
QTransform trans;
trans.scale(ite->width() / ite->groupWidth, ite->height() / ite->groupHeight);
trans.translate(0.0, -ite->height());
PutPage(TransformToStr(trans) + " cm\n");
groupStackPos.push(QPointF(ite->xPos(), ite->height()));
for (int em = 0; em < ite->groupItemList.count(); ++em)
{
PageItem* embedded = ite->groupItemList.at(em);
tmpD += "q\n";
tmpD += "1 0 0 1 " + FToStr(embedded->gXpos) + " " + FToStr(ite->height() - embedded->gYpos) + " cm\n";
QByteArray output;
patternStackPos.push(QPointF(embedded->gXpos, ite->height() - embedded->gYpos));
inPattern++; // We are not really exporting a pattern, but that fix gradient export
if (!PDF_ProcessItem(output, embedded, pag, pag->pageNr(), true, true))
return false;
inPattern--;
patternStackPos.pop();
tmpD += output;
tmpD += "Q\n";
}
groupStackPos.pop();
if (Options.supportsTransparency())
PutPage(Write_TransparencyGroup(ite->fillTransparency(), ite->fillBlendmode(), tmpD, ite));
else
PutPage(Write_FormXObject(tmpD, ite));
PutPage("Q\n");
}
break;
case PageItem::Multiple:
Q_ASSERT(false);
break;
default:
break;
}
PutPage("Q\n");
PdfId templateObject = writer.newObject();
writer.startObj(templateObject);
PutDoc("<<\n/Type /XObject\n/Subtype /Form\n/FormType 1\n");
double bleedRight = 0.0;
double bleedLeft = 0.0;
getBleeds(ActPageP, bleedLeft, bleedRight);
double maxBoxX = ActPageP->width() + bleedRight + bleedLeft;
double maxBoxY = ActPageP->height() + Options.bleeds.top() + Options.bleeds.bottom();
PutDoc("/BBox [ " + FToStr(-bleedLeft) + " " + FToStr(-Options.bleeds.bottom()) + " " + FToStr(maxBoxX) + " " + FToStr(maxBoxY) + " ]\n");
// PutDoc("/BBox [ 0 0 " + FToStr(ActPageP->width()) + " " + FToStr(ActPageP->height()) + " ]\n");
Pdf::ResourceDictionary dict;
dict.XObject.unite(pageData.ImgObjects);
dict.XObject.unite(pageData.XObjects);
dict.Font = pageData.FObjects;
dict.Shading = Shadings;
dict.Pattern = Patterns;
dict.ExtGState = Transpar;
dict.ColorSpace.append(asColorSpace(ICCProfiles.values()));
dict.ColorSpace.append(asColorSpace(spotMap.values()));
writer.write("/Resources ");
writer.write(dict);
if (Options.Compress)
Content = CompressArray(Content);
PutDoc("/Length " + Pdf::toPdf(Content.length() + 1));
if (Options.Compress)
PutDoc("\n/Filter /FlateDecode");
PutDoc(" >>\nstream\n" + EncStream(Content, templateObject) + "\nendstream");
writer.endObj(templateObject);
int pIndex = doc.MasterPages.indexOf((ScPage* const) pag) + 1;
QByteArray name = QByteArray("master_page_obj_%1_%2")
.replace("%1", Pdf::toPdf(pIndex))
.replace("%2", Pdf::toPdf(qHash(ite)));
pageData.XObjects[name] = templateObject;
}
if (Options.exportsLayers())
PutPage("EMC\n");
}
return true;
}
void PDFLibCore::PDF_Begin_Page(const ScPage* pag, const QImage& thumb)
{
ActPageP = pag;
Content.clear();
pageData.AObjects.clear();
pageData.radioButtonList.clear();
pageData.radioButtonGroups.clear();
if (Options.Thumbnails)
{
ScImage img(thumb);
bool compDataAvail = false;
QByteArray array = img.ImageToArray();
if (Options.Compress)
{
QByteArray compArray = CompressArray(array);
if (compArray.size() > 0)
{
array = compArray;
compDataAvail = true;
}
}
PdfId thumbnail = writer.newObject();
writer.startObj(thumbnail);
PutDoc("<<\n/Width " + Pdf::toPdf(img.width()) + "\n");
PutDoc("/Height " + Pdf::toPdf(img.height()) + "\n");
PutDoc("/ColorSpace /DeviceRGB\n/BitsPerComponent 8\n");
PutDoc("/Length " + Pdf::toPdf(array.size() + 1) + "\n");
if (Options.Compress && compDataAvail)
PutDoc("/Filter /FlateDecode\n");
PutDoc(">>\nstream\n");
EncodeArrayToStream(array, thumbnail);
PutDoc("\nendstream");
writer.endObj(thumbnail);
pageData.Thumb = thumbnail;
}
}
void PDFLibCore::PDF_End_Page()
{
if (!pageData.radioButtonList.isEmpty())
PDF_RadioButtonGroups();
uint PgNr = ActPageP->pageNr();
double markOffs = 0.0;
if ((Options.cropMarks) || (Options.bleedMarks) || (Options.registrationMarks) || (Options.colorMarks) || (Options.docInfoMarks))
markOffs = Options.markLength + Options.markOffset;
if ((Options.registrationMarks) || (Options.colorMarks) || (Options.docInfoMarks))
markOffs = qMax(markOffs, Options.markOffset + 20.0);
double markDelta = markOffs - (Options.markLength + Options.markOffset);
double bleedRight, bleedLeft;
getBleeds(ActPageP, bleedLeft, bleedRight);
double maxBoxX = ActPageP->width() + bleedLeft + bleedRight + markOffs * 2.0;
double maxBoxY = ActPageP->height() + Options.bleeds.bottom() + Options.bleeds.top() + markOffs*2.0;
// (JG) Fix #5977 and #6075 (invalid restore)
//PutPage("Q\n");
if ((