Implementation of font drawing - first part

This commit is contained in:
Jakub Melka 2019-03-30 18:45:30 +01:00
parent 21e125bd40
commit 81be3cbd21
8 changed files with 725 additions and 33 deletions

View File

@ -262,6 +262,17 @@ const PDFObject& PDFObjectStorage::getObject(PDFObjectReference reference) const
}
}
QByteArray PDFDocumentDataLoaderDecorator::readName(const PDFObject& object)
{
const PDFObject& dereferencedObject = m_document->getObject(object);
if (dereferencedObject.isName())
{
return dereferencedObject.getString();
}
return QByteArray();
}
PDFInteger PDFDocumentDataLoaderDecorator::readInteger(const PDFObject& object, PDFInteger defaultValue) const
{
const PDFObject& dereferencedObject = m_document->getObject(object);
@ -458,4 +469,14 @@ bool PDFDocumentDataLoaderDecorator::readBooleanFromDictionary(const PDFDictiona
return defaultValue;
}
QByteArray PDFDocumentDataLoaderDecorator::readNameFromDictionary(const PDFDictionary* dictionary, const char* key)
{
if (dictionary->hasKey(key))
{
return readName(dictionary->get(key));
}
return QByteArray();
}
} // namespace pdf

View File

@ -87,6 +87,11 @@ public:
inline explicit PDFDocumentDataLoaderDecorator(const PDFDocument* document) : m_document(document) { }
inline ~PDFDocumentDataLoaderDecorator() = default;
/// Reads a name from the object, if it is possible. If object is not a name,
/// then empty byte array is returned.
/// \param object Object, can be an indirect reference to object (it is dereferenced)
QByteArray readName(const PDFObject& object);
/// Reads an integer from the object, if it is possible.
/// \param object Object, can be an indirect reference to object (it is dereferenced)
/// \param defaultValue Default value
@ -217,6 +222,11 @@ public:
/// \param defaultValue Default value
bool readBooleanFromDictionary(const PDFDictionary* dictionary, const char* key, bool defaultValue) const;
/// Reads a name from dictionary. If dictionary entry doesn't exist, or error occurs, empty byte array is returned.
/// \param dictionary Dictionary containing desired data
/// \param key Entry key
QByteArray readNameFromDictionary(const PDFDictionary* dictionary, const char* key);
private:
const PDFDocument* m_document;
};

View File

@ -46,7 +46,11 @@ public:
PDFDoc, ///< Appendix D, Section D.1/D.2, PDFDocEncoding
MacExpert, ///< Appendix D, Section D.3, MacExpertEncoding
Symbol, ///< Appendix D, Section D.4, Symbol Set and Encoding
ZapfDingbats ///< Appendix D, Section D.5, Zapf Dingbats Encoding
ZapfDingbats, ///< Appendix D, Section D.5, Zapf Dingbats Encoding
// Following encodings are used for internal use only and are not a part of PDF reference
Custom,
Invalid
};
/// Converts byte array to the unicode string using specified encoding
@ -73,11 +77,11 @@ public:
/// \param stream Stream, from which date/time is read
static QDateTime convertToDateTime(const QByteArray& stream);
private:
/// Returns conversion table for particular encoding
/// \param encoding Encoding
static const encoding::EncodingTable* getTableForEncoding(Encoding encoding);
private:
/// Returns true, if byte array has UTF-16BE unicode marking bytes at the
/// stream start. If they are present, then byte stream is probably encoded
/// as unicode.

View File

@ -16,6 +16,9 @@
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
#include "pdffont.h"
#include "pdfdocument.h"
#include "pdfparser.h"
#include "pdfnametounicode.h"
namespace pdf
{
@ -25,4 +28,295 @@ PDFFont::PDFFont()
}
PDFFontPointer PDFFont::createFont(const PDFObject& object, const PDFDocument* document)
{
const PDFObject& dereferencedFontDictionary = document->getObject(object);
if (!dereferencedFontDictionary.isDictionary())
{
throw PDFParserException(PDFTranslationContext::tr("Font object must be a dictionary."));
}
const PDFDictionary* fontDictionary = dereferencedFontDictionary.getDictionary();
PDFDocumentDataLoaderDecorator fontLoader(document);
// TODO: Fonts - implement all types of the font
// First, determine the font subtype
constexpr const std::array<std::pair<const char*, FontType>, 2> fontTypes = {
std::pair<const char*, FontType>{ "Type1", FontType::Type1 },
std::pair<const char*, FontType>{ "TrueType", FontType::TrueType }
};
const FontType fontType = fontLoader.readEnumByName(fontDictionary->get("Subtype"), fontTypes.cbegin(), fontTypes.cend(), FontType::Invalid);
if (fontType == FontType::Invalid)
{
throw PDFParserException(PDFTranslationContext::tr("Invalid font type."));
}
QByteArray name = fontLoader.readNameFromDictionary(fontDictionary, "Name");
QByteArray baseFont = fontLoader.readNameFromDictionary(fontDictionary, "BaseFont");
const PDFInteger firstChar = fontLoader.readIntegerFromDictionary(fontDictionary, "FirstChar", 0);
const PDFInteger lastChar = fontLoader.readIntegerFromDictionary(fontDictionary, "LastChar", 255);
std::vector<PDFInteger> widths = fontLoader.readIntegerArrayFromDictionary(fontDictionary, "Widths");
// Read standard font
constexpr const std::array<std::pair<const char*, StandardFontType>, 14> standardFonts = {
std::pair<const char*, StandardFontType>{ "Times-Roman", StandardFontType::TimesRoman },
std::pair<const char*, StandardFontType>{ "Times-Bold", StandardFontType::TimesRomanBold },
std::pair<const char*, StandardFontType>{ "Times-Italic", StandardFontType::TimesRomanItalics },
std::pair<const char*, StandardFontType>{ "Times-BoldItalic", StandardFontType::TimesRomanBoldItalics },
std::pair<const char*, StandardFontType>{ "Helvetica", StandardFontType::Helvetica },
std::pair<const char*, StandardFontType>{ "Helvetica-Bold", StandardFontType::HelveticaBold },
std::pair<const char*, StandardFontType>{ "Helvetica-Oblique", StandardFontType::HelveticaOblique },
std::pair<const char*, StandardFontType>{ "Helvetica-BoldOblique", StandardFontType::HelveticaBoldOblique },
std::pair<const char*, StandardFontType>{ "Courier", StandardFontType::Courier },
std::pair<const char*, StandardFontType>{ "Courier-Bold", StandardFontType::CourierBold },
std::pair<const char*, StandardFontType>{ "Courier-Oblique", StandardFontType::CourierOblique },
std::pair<const char*, StandardFontType>{ "Courier-BoldOblique", StandardFontType::CourierBoldOblique },
std::pair<const char*, StandardFontType>{ "Symbol", StandardFontType::Symbol },
std::pair<const char*, StandardFontType>{ "ZapfDingbats", StandardFontType::ZapfDingbats }
};
const StandardFontType standardFont = fontLoader.readEnumByName(fontDictionary->get("BaseFont"), standardFonts.cbegin(), standardFonts.cend(), StandardFontType::Invalid);
// Read Font Descriptor
// TODO: Read font descriptor
// Read Font Encoding
// The font encoding for the simple font is determined by this algorithm:
// 1) Try to use Encoding dictionary to determine base encoding
// (it can be MacRomanEncoding, MacExpertEncoding, WinAnsiEncoding or StandardEncoding)
// 2) If it is not present, then try to obtain built-in encoding from the font file (usually, this is not possible)
// 3) Use default encoding for the font depending on the font type
// - one of the 14 base fonts - use builtin encoding for the font type
// - TrueType - use WinAnsiEncoding
// - all others - use StandardEncoding
// 4) Merge with Differences, if present
// 5) Fill missing characters from StandardEncoding
// TODO: Read font encoding from the font file
PDFEncoding::Encoding encoding = PDFEncoding::Encoding::Invalid;
encoding::EncodingTable simpleFontEncodingTable = { };
switch (fontType)
{
case FontType::Type1:
case FontType::TrueType:
{
bool hasDifferences = false;
encoding::EncodingTable differences = { };
if (fontDictionary->hasKey("Encoding"))
{
constexpr const std::array<std::pair<const char*, PDFEncoding::Encoding>, 3> encodings = {
std::pair<const char*, PDFEncoding::Encoding>{ "MacRomanEncoding", PDFEncoding::Encoding::MacRoman },
std::pair<const char*, PDFEncoding::Encoding>{ "MacExpertEncoding", PDFEncoding::Encoding::MacExpert },
std::pair<const char*, PDFEncoding::Encoding>{ "WinAnsiEncoding", PDFEncoding::Encoding::WinAnsi }
};
const PDFObject& encodingObject = document->getObject(fontDictionary->get("Encoding"));
if (encodingObject.isName())
{
// Decode name of the encoding
encoding = fontLoader.readEnumByName(encodingObject, encodings.cbegin(), encodings.cend(), PDFEncoding::Encoding::Invalid);
}
else if (encodingObject.isDictionary())
{
// Dictionary with base encoding and differences (all optional)
const PDFDictionary* encodingDictionary = encodingObject.getDictionary();
if (encodingDictionary->hasKey("BaseEncoding"))
{
encoding = fontLoader.readEnumByName(encodingDictionary->get("BaseEncoding"), encodings.cbegin(), encodings.cend(), PDFEncoding::Encoding::Invalid);
}
else
{
// We get encoding for the standard font. If we have invalid standard font,
// then we get standard encoding. So we shouldn't test it.
encoding = getEncodingForStandardFont(standardFont);
}
if (encodingDictionary->hasKey("Differences"))
{
const PDFObject& differencesArray = document->getObject(encodingDictionary->get("Differences"));
if (differencesArray.isArray())
{
hasDifferences = true;
const PDFArray* array = differencesArray.getArray();
size_t currentOffset = 0;
for (size_t i = 0, count = array->getCount(); i < count; ++i)
{
const PDFObject& item = document->getObject(array->getItem(i));
if (item.isInt())
{
currentOffset = static_cast<size_t>(item.getInteger());
}
else if (item.isName())
{
if (currentOffset >= differences.size())
{
throw PDFParserException(PDFTranslationContext::tr("Invalid differences in encoding entry of the font."));
}
QChar character = PDFNameToUnicode::getUnicodeForName(item.getString());
// Try ZapfDingbats, if this fails
if (character.isNull())
{
character = PDFNameToUnicode::getUnicodeForNameZapfDingbats(item.getString());
}
differences[currentOffset] = character;
++currentOffset;
}
else
{
throw PDFParserException(PDFTranslationContext::tr("Invalid differences in encoding entry of the font."));
}
}
}
else
{
throw PDFParserException(PDFTranslationContext::tr("Invalid differences in encoding entry of the font."));
}
}
}
else
{
throw PDFParserException(PDFTranslationContext::tr("Invalid encoding entry of the font."));
}
}
else
{
// We get encoding for the standard font. If we have invalid standard font,
// then we get standard encoding. So we shouldn't test it.
encoding = getEncodingForStandardFont(standardFont);
}
if (encoding == PDFEncoding::Encoding::Invalid)
{
throw PDFParserException(PDFTranslationContext::tr("Invalid encoding entry of the font."));
}
simpleFontEncodingTable = *PDFEncoding::getTableForEncoding(encoding);
// Fill in differences
if (hasDifferences)
{
for (size_t i = 0; i < differences.size(); ++i)
{
if (!differences[i].isNull())
{
simpleFontEncodingTable[i] = differences[i];
}
}
// Set the encoding to custom
encoding = PDFEncoding::Encoding::Custom;
}
// Fill in missing characters from standard encoding
const encoding::EncodingTable& standardEncoding = *PDFEncoding::getTableForEncoding(PDFEncoding::Encoding::Standard);
for (size_t i = 0; i < standardEncoding.size(); ++i)
{
if ((simpleFontEncodingTable[i].isNull() || simpleFontEncodingTable[i] == QChar(QChar::SpecialCharacter::ReplacementCharacter)) &&
(!standardEncoding[i].isNull() && standardEncoding[i] != QChar(QChar::SpecialCharacter::ReplacementCharacter)))
{
simpleFontEncodingTable[i] = standardEncoding[i];
}
}
break;
}
default:
{
Q_ASSERT(false);
break;
}
}
switch (fontType)
{
case FontType::Type1:
return PDFFontPointer(new PDFType1Font(qMove(name), qMove(baseFont), firstChar, lastChar, qMove(widths), encoding, simpleFontEncodingTable, standardFont));
case FontType::TrueType:
return PDFFontPointer(new PDFTrueTypeFont(qMove(name), qMove(baseFont), firstChar, lastChar, qMove(widths), encoding, simpleFontEncodingTable));
default:
{
Q_ASSERT(false);
break;
}
}
// Read To Unicode
// TODO: Read To Unicode
// Read Embedded fonts
// TODO: Read embedded fonts
return PDFFontPointer();
}
PDFSimpleFont::PDFSimpleFont(QByteArray name,
QByteArray baseFont,
PDFInteger firstChar,
PDFInteger lastChar,
std::vector<PDFInteger> widths,
PDFEncoding::Encoding encodingType,
encoding::EncodingTable encoding) :
m_name(qMove(name)),
m_baseFont(qMove(baseFont)),
m_firstChar(firstChar),
m_lastChar(lastChar),
m_widths(qMove(widths)),
m_encodingType(encodingType),
m_encoding(encoding)
{
}
QRawFont PDFSimpleFont::getRealizedFont(PDFReal fontSize) const
{
// TODO: Fix font creation to use also embedded fonts, font descriptor, etc.
QFont font(m_baseFont);
font.setHintingPreference(QFont::PreferNoHinting);
font.setPixelSize(fontSize);
return QRawFont::fromFont(font, QFontDatabase::Any);
}
QString PDFSimpleFont::getTextUsingEncoding(const QByteArray& byteArray) const
{
QString string;
string.resize(byteArray.size(), QChar());
for (int i = 0, count = byteArray.size(); i < count; ++i)
{
string[i] = m_encoding[static_cast<uint8_t>(byteArray[i])];
}
return string;
}
PDFType1Font::PDFType1Font(QByteArray name,
QByteArray baseFont,
PDFInteger firstChar,
PDFInteger lastChar,
std::vector<PDFInteger> widths,
PDFEncoding::Encoding encodingType,
encoding::EncodingTable encoding,
StandardFontType standardFontType) :
PDFSimpleFont(qMove(name), qMove(baseFont), firstChar, lastChar, qMove(widths), encodingType, encoding),
m_standardFontType(standardFontType)
{
}
FontType PDFType1Font::getFontType() const
{
return FontType::Type1;
}
FontType PDFTrueTypeFont::getFontType() const
{
return FontType::TrueType;
}
} // namespace pdf

View File

@ -19,11 +19,15 @@
#define PDFFONT_H
#include "pdfglobal.h"
#include "pdfencoding.h"
#include "pdfobject.h"
#include <QRawFont>
#include <QSharedPointer>
namespace pdf
{
class PDFDocument;
enum class TextRenderingMode
{
@ -82,13 +86,131 @@ constexpr bool isTextRenderingModeClipped(TextRenderingMode mode)
}
}
enum class FontType
{
Invalid,
Type1,
TrueType
};
/// Standard Type1 fonts
enum class StandardFontType
{
Invalid,
TimesRoman,
TimesRomanBold,
TimesRomanItalics,
TimesRomanBoldItalics,
Helvetica,
HelveticaBold,
HelveticaOblique,
HelveticaBoldOblique,
Courier,
CourierBold,
CourierOblique,
CourierBoldOblique,
Symbol,
ZapfDingbats
};
/// Returns builtin encoding for the standard font
static constexpr PDFEncoding::Encoding getEncodingForStandardFont(StandardFontType standardFont)
{
switch (standardFont)
{
case StandardFontType::Symbol:
return PDFEncoding::Encoding::Symbol;
case StandardFontType::ZapfDingbats:
return PDFEncoding::Encoding::ZapfDingbats;
default:
return PDFEncoding::Encoding::Standard;
}
}
class PDFFont;
using PDFFontPointer = QSharedPointer<PDFFont>;
/// Base class representing font in the PDF file
class PDFFont
{
public:
PDFFont();
explicit PDFFont();
virtual ~PDFFont() = default;
/// Returns the font type
virtual FontType getFontType() const = 0;
/// Realizes the font (physical materialization of the font using pixel size,
/// if font can't be realized, then empty QRawFont is returned).
/// \param fontSize Size of the font
virtual QRawFont getRealizedFont(PDFReal fontSize) const = 0;
/// Returns text using the font encoding
/// \param byteArray Byte array with encoded string
virtual QString getTextUsingEncoding(const QByteArray& byteArray) const = 0;
static PDFFontPointer createFont(const PDFObject& object, const PDFDocument* document);
};
using PDFFontPointer = QSharedPointer<PDFFont>;
/// Simple font, see PDF reference 1.7, chapter 5.5. Simple fonts have encoding table,
/// which maps single-byte character to the glyph in the font.
class PDFSimpleFont : public PDFFont
{
public:
explicit PDFSimpleFont(QByteArray name,
QByteArray baseFont,
PDFInteger firstChar,
PDFInteger lastChar,
std::vector<PDFInteger> widths,
PDFEncoding::Encoding encodingType,
encoding::EncodingTable encoding);
virtual ~PDFSimpleFont() override = default;
virtual QRawFont getRealizedFont(PDFReal fontSize) const override;
virtual QString getTextUsingEncoding(const QByteArray& byteArray) const override;
protected:
QByteArray m_name;
QByteArray m_baseFont;
PDFInteger m_firstChar;
PDFInteger m_lastChar;
std::vector<PDFInteger> m_widths;
PDFEncoding::Encoding m_encodingType;
encoding::EncodingTable m_encoding;
};
class PDFType1Font : public PDFSimpleFont
{
public:
explicit PDFType1Font(QByteArray name,
QByteArray baseFont,
PDFInteger firstChar,
PDFInteger lastChar,
std::vector<PDFInteger> widths,
PDFEncoding::Encoding encodingType,
encoding::EncodingTable encoding,
StandardFontType standardFontType);
virtual ~PDFType1Font() override = default;
virtual FontType getFontType() const override;
/// Returns the assigned standard font (or invalid, if font is not standard)
StandardFontType getStandardFontType() const { return m_standardFontType; }
private:
StandardFontType m_standardFontType; ///< Type of the standard font (or invalid, if it is not a standard font)
};
class PDFTrueTypeFont : public PDFSimpleFont
{
public:
using PDFSimpleFont::PDFSimpleFont;
virtual FontType getFontType() const override;
};
} // namespace pdf

View File

@ -153,20 +153,29 @@ PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page, const PDFD
m_page(page),
m_document(document),
m_colorSpaceDictionary(nullptr),
m_fontDictionary(nullptr),
m_textBeginEndState(0)
{
Q_ASSERT(page);
Q_ASSERT(document);
const PDFObject& resources = m_document->getObject(m_page->getResources());
if (resources.isDictionary() && resources.getDictionary()->hasKey(COLOR_SPACE_DICTIONARY))
auto getDictionary = [this](const char* resourceName) -> const pdf::PDFDictionary*
{
const PDFObject& colorSpace = m_document->getObject(resources.getDictionary()->get(COLOR_SPACE_DICTIONARY));
if (colorSpace.isDictionary())
const PDFObject& resources = m_document->getObject(m_page->getResources());
if (resources.isDictionary() && resources.getDictionary()->hasKey(resourceName))
{
m_colorSpaceDictionary = colorSpace.getDictionary();
const PDFObject& resourceDictionary = m_document->getObject(resources.getDictionary()->get(resourceName));
if (resourceDictionary.isDictionary())
{
return resourceDictionary.getDictionary();
}
}
}
return nullptr;
};
m_colorSpaceDictionary = getDictionary(COLOR_SPACE_DICTIONARY);
m_fontDictionary = getDictionary("Font");
}
PDFPageContentProcessor::~PDFPageContentProcessor()
@ -260,17 +269,21 @@ void PDFPageContentProcessor::performClipping(const QPainterPath& path, Qt::Fill
Q_UNUSED(fillRule);
}
void PDFPageContentProcessor::performUpdateGraphicsState(const PDFPageContentProcessor::PDFPageContentProcessorState& state)
void PDFPageContentProcessor::performUpdateGraphicsState(const PDFPageContentProcessorState& state)
{
Q_UNUSED(state);
if (state.getStateFlags().testFlag(PDFPageContentProcessorState::StateTextFont) ||
state.getStateFlags().testFlag(PDFPageContentProcessorState::StateTextFontSize))
{
m_realizedFont.dirty();
}
}
void PDFPageContentProcessor::performSaveGraphicState(PDFPageContentProcessor::ProcessOrder order)
void PDFPageContentProcessor::performSaveGraphicState(ProcessOrder order)
{
Q_UNUSED(order);
}
void PDFPageContentProcessor::performRestoreGraphicState(PDFPageContentProcessor::ProcessOrder order)
void PDFPageContentProcessor::performRestoreGraphicState(ProcessOrder order)
{
Q_UNUSED(order);
}
@ -689,6 +702,34 @@ void PDFPageContentProcessor::processCommand(const QByteArray& command)
break;
}
case Operator::TextShowTextString:
{
// Tj, show text string
invokeOperator(&PDFPageContentProcessor::operatorTextShowTextString);
break;
}
case Operator::TextShowTextIndividualSpacing:
{
// TJ, show text, allow individual text spacing
invokeOperator(&PDFPageContentProcessor::operatorTextShowTextIndividualSpacing);
break;
}
case Operator::TextNextLineShowText:
{
// ', move to the next line and show text ("string '" is equivalent to "T* string Tj")
invokeOperator(&PDFPageContentProcessor::operatorTextNextLineShowText);
break;
}
case Operator::TextSetSpacingAndShowText:
{
// ", move to the next line, set spacing and show text (equivalent to sequence "w1 Tw w2 Tc string '")
invokeOperator(&PDFPageContentProcessor::operatorTextSetSpacingAndShowText);
break;
}
case Operator::Invalid:
{
m_errorList.append(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Unknown operator '%1'.").arg(QString::fromLatin1(command))));
@ -902,7 +943,7 @@ void PDFPageContentProcessor::operatorSetLineDashPattern()
updateGraphicState();
}
void PDFPageContentProcessor::operatorSetRenderingIntent(PDFName intent)
void PDFPageContentProcessor::operatorSetRenderingIntent(PDFOperandName intent)
{
m_graphicState.setRenderingIntent(intent.name);
updateGraphicState();
@ -915,7 +956,7 @@ void PDFPageContentProcessor::operatorSetFlatness(PDFReal flatness)
updateGraphicState();
}
void PDFPageContentProcessor::operatorSetGraphicState(PDFName dictionaryName)
void PDFPageContentProcessor::operatorSetGraphicState(PDFOperandName dictionaryName)
{
const PDFObject& resources = m_page->getResources();
if (resources.isDictionary())
@ -1101,7 +1142,7 @@ PDFInteger PDFPageContentProcessor::readOperand<PDFInteger>(size_t index) const
}
template<>
PDFPageContentProcessor::PDFName PDFPageContentProcessor::readOperand<PDFPageContentProcessor::PDFName>(size_t index) const
PDFPageContentProcessor::PDFOperandName PDFPageContentProcessor::readOperand<PDFPageContentProcessor::PDFOperandName>(size_t index) const
{
if (index < m_operands.size())
{
@ -1110,7 +1151,7 @@ PDFPageContentProcessor::PDFName PDFPageContentProcessor::readOperand<PDFPageCon
switch (token.type)
{
case PDFLexicalAnalyzer::TokenType::Name:
return PDFName{ token.data.toByteArray() };
return PDFOperandName{ token.data.toByteArray() };
default:
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Can't read operand (name) on index %1. Operand is of type '%2'.").arg(index + 1).arg(PDFLexicalAnalyzer::getStringFromOperandType(token.type)));
@ -1121,7 +1162,32 @@ PDFPageContentProcessor::PDFName PDFPageContentProcessor::readOperand<PDFPageCon
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Can't read operand (name) on index %1. Only %2 operands provided.").arg(index + 1).arg(m_operands.size()));
}
return PDFName();
return PDFOperandName();
}
template<>
PDFPageContentProcessor::PDFOperandString PDFPageContentProcessor::readOperand<PDFPageContentProcessor::PDFOperandString>(size_t index) const
{
if (index < m_operands.size())
{
const PDFLexicalAnalyzer::Token& token = m_operands[index];
switch (token.type)
{
case PDFLexicalAnalyzer::TokenType::String:
return PDFOperandString{ token.data.toByteArray() };
default:
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Can't read operand (string) on index %1. Operand is of type '%2'.").arg(index + 1).arg(PDFLexicalAnalyzer::getStringFromOperandType(token.type)));
}
}
else
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Can't read operand (string) on index %1. Only %2 operands provided.").arg(index + 1).arg(m_operands.size()));
}
return PDFOperandString();
}
void PDFPageContentProcessor::operatorMoveCurrentPoint(PDFReal x, PDFReal y)
@ -1275,7 +1341,7 @@ void PDFPageContentProcessor::operatorClipEvenOdd()
}
}
void PDFPageContentProcessor::operatorColorSetStrokingColorSpace(PDFPageContentProcessor::PDFName name)
void PDFPageContentProcessor::operatorColorSetStrokingColorSpace(PDFPageContentProcessor::PDFOperandName name)
{
PDFColorSpacePointer colorSpace = PDFAbstractColorSpace::createColorSpace(m_colorSpaceDictionary, m_document, PDFObject::createName(std::make_shared<PDFString>(QByteArray(name.name))));
if (colorSpace)
@ -1291,7 +1357,7 @@ void PDFPageContentProcessor::operatorColorSetStrokingColorSpace(PDFPageContentP
}
}
void PDFPageContentProcessor::operatorColorSetFillingColorSpace(PDFName name)
void PDFPageContentProcessor::operatorColorSetFillingColorSpace(PDFOperandName name)
{
PDFColorSpacePointer colorSpace = PDFAbstractColorSpace::createColorSpace(m_colorSpaceDictionary, m_document, PDFObject::createName(std::make_shared<PDFString>(QByteArray(name.name))));
if (colorSpace)
@ -1458,13 +1524,27 @@ void PDFPageContentProcessor::operatorTextSetLeading(PDFReal leading)
updateGraphicState();
}
void PDFPageContentProcessor::operatorTextSetFontAndFontSize(PDFPageContentProcessor::PDFName fontName, PDFReal fontSize)
void PDFPageContentProcessor::operatorTextSetFontAndFontSize(PDFOperandName fontName, PDFReal fontSize)
{
Q_UNUSED(fontName);
Q_UNUSED(fontSize);
if (m_fontDictionary)
{
if (m_fontDictionary->hasKey(fontName.name))
{
PDFFontPointer font = PDFFont::createFont(m_fontDictionary->get(fontName.name), m_document);
// TODO: Implement this operator
throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Set font not implemented."));
m_graphicState.setTextFont(qMove(font));
m_graphicState.setTextFontSize(fontSize);
updateGraphicState();
}
else
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Font '%1' not found in font dictionary.").arg(QString::fromLatin1(fontName.name)));
}
}
else
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid font dictionary."));
}
}
void PDFPageContentProcessor::operatorTextSetRenderMode(PDFInteger mode)
@ -1533,6 +1613,105 @@ void PDFPageContentProcessor::operatorTextMoveByLeading()
operatorTextMoveByOffset(0.0, m_graphicState.getTextLeading());
}
void PDFPageContentProcessor::operatorTextShowTextString(PDFOperandString text)
{
if (m_graphicState.getTextFont())
{
QString textDecoded = m_graphicState.getTextFont()->getTextUsingEncoding(text.string);
drawText(TextSequence::fromString(textDecoded));
}
else
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid font, text can't be printed."));
}
}
void PDFPageContentProcessor::operatorTextShowTextIndividualSpacing()
{
// Operand stack must be of this form [ ... text, number, text, number, number, text ... ]. We check it.
if (m_operands.size() < 2)
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid parameters of text operator with individual character spacing."));
}
// Now, we have at least 2 arguments. Check we have an array
if (m_operands[0].type != PDFLexicalAnalyzer::TokenType::ArrayStart ||
m_operands[m_operands.size() - 1].type != PDFLexicalAnalyzer::TokenType::ArrayEnd)
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid line dash pattern."));
}
if (m_graphicState.getTextFont())
{
TextSequence textSequence;
// We use simple heuristic to ensure reallocation doesn't occur too often
textSequence.items.reserve(m_operands.size() * 4);
for (size_t i = 1, lastIndex = m_operands.size() - 1; i < lastIndex; ++i)
{
switch (m_operands[i].type)
{
case PDFLexicalAnalyzer::TokenType::Integer:
{
textSequence.items.push_back(TextSequenceItem(m_operands[i].data.value<PDFInteger>()));
break;
}
case PDFLexicalAnalyzer::TokenType::String:
{
QString string = m_graphicState.getTextFont()->getTextUsingEncoding(m_operands[i].data.toByteArray());
std::transform(string.cbegin(), string.cend(), std::back_inserter(textSequence.items), [](const QChar character) { return TextSequenceItem(character); });
break;
}
default:
{
// Error - we have operand of different type
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid operand of text show operator."));
}
}
}
drawText(textSequence);
}
else
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid font, text can't be printed."));
}
}
void PDFPageContentProcessor::operatorTextNextLineShowText(PDFOperandString text)
{
operatorTextMoveByLeading();
operatorTextShowTextString(qMove(text));
}
void PDFPageContentProcessor::operatorTextSetSpacingAndShowText(PDFReal t_w, PDFReal t_c, PDFOperandString text)
{
m_graphicState.setTextWordSpacing(t_w);
m_graphicState.setTextCharacterSpacing(t_c);
updateGraphicState();
operatorTextNextLineShowText(qMove(text));
}
void PDFPageContentProcessor::drawText(const TextSequence& textSequence)
{
}
QRawFont PDFPageContentProcessor::getRealizedFontImpl() const
{
if (m_graphicState.getTextFont())
{
return m_graphicState.getTextFont()->getRealizedFont(m_graphicState.getTextFontSize());
}
return QRawFont();
}
PDFPageContentProcessor::PDFPageContentProcessorState::PDFPageContentProcessorState() :
m_currentTransformationMatrix(),
m_fillColorSpace(),
@ -1810,4 +1989,12 @@ void PDFPageContentProcessor::PDFPageContentProcessorState::setTextCharacterSpac
}
}
PDFPageContentProcessor::TextSequence PDFPageContentProcessor::TextSequence::fromString(const QString& string)
{
TextSequence result;
result.items.reserve(string.size());
std::transform(string.cbegin(), string.cend(), std::back_inserter(result.items), [](const QChar character) { return TextSequenceItem(character); });
return result;
}
} // namespace pdf

View File

@ -22,8 +22,10 @@
#include "pdfparser.h"
#include "pdfcolorspaces.h"
#include "pdffont.h"
#include "pdfutils.h"
#include <QMatrix>
#include <QRawFont>
#include <QPainterPath>
#include <QSharedPointer>
@ -348,6 +350,28 @@ protected:
StateFlags m_stateFlags;
};
/// Item of the text sequence (either single character, or advance)
struct TextSequenceItem
{
inline explicit TextSequenceItem() = default;
inline explicit TextSequenceItem(QChar character) : character(character), advance(0) { }
inline explicit TextSequenceItem(PDFInteger advance) : character(), advance(advance) { }
inline bool isCharacter() const { return !character.isNull(); }
inline bool isAdvance() const { return advance != 0; }
inline bool isNull() const { return !isCharacter() && !isAdvance(); }
QChar character;
PDFInteger advance = 0;
};
struct TextSequence
{
static TextSequence fromString(const QString& string);
std::vector<TextSequenceItem> items;
};
enum class ProcessOrder
{
BeforeOperation,
@ -398,11 +422,17 @@ private:
void processCommand(const QByteArray& command);
/// Wrapper for PDF Name
struct PDFName
struct PDFOperandName
{
QByteArray name;
};
/// Wrapper for PDF String
struct PDFOperandString
{
QByteArray string;
};
template<typename T>
T readOperand(size_t index) const;
@ -413,7 +443,10 @@ private:
PDFInteger readOperand<PDFInteger>(size_t index) const;
template<>
PDFName readOperand<PDFName>(size_t index) const;
PDFOperandName readOperand<PDFOperandName>(size_t index) const;
template<>
PDFOperandString readOperand<PDFOperandString>(size_t index) const;
template<size_t index, typename T>
inline T readOperand() const { return readOperand<T>(index); }
@ -484,9 +517,9 @@ private:
void operatorSetLineJoin(PDFInteger lineJoin); ///< j, sets the line join
void operatorSetMitterLimit(PDFReal mitterLimit); ///< M, sets the mitter limit
void operatorSetLineDashPattern(); ///< d, sets the line dash pattern
void operatorSetRenderingIntent(PDFName intent); ///< ri, sets the rendering intent
void operatorSetRenderingIntent(PDFOperandName intent); ///< ri, sets the rendering intent
void operatorSetFlatness(PDFReal flatness); ///< i, sets the flattness (number in range from 0 to 100)
void operatorSetGraphicState(PDFName dictionaryName); ///< gs, sets the whole graphic state (stored in resource dictionary)
void operatorSetGraphicState(PDFOperandName dictionaryName); ///< gs, sets the whole graphic state (stored in resource dictionary)
// Special graphic state: q, Q, cm
void operatorSaveGraphicState(); ///< q, saves the graphic state
@ -518,8 +551,8 @@ private:
void operatorClipEvenOdd(); ///< W*, modify current clipping path by intersecting it with current path using "Even-odd rule"
// Color: CS, cs, SC, SCN, sc, scn, G, g, RG, rg, K, k
void operatorColorSetStrokingColorSpace(PDFName name); ///< CS, set current color space for stroking operations
void operatorColorSetFillingColorSpace(PDFName name); ///< cs, set current color space for filling operations
void operatorColorSetStrokingColorSpace(PDFOperandName name); ///< CS, set current color space for stroking operations
void operatorColorSetFillingColorSpace(PDFOperandName name); ///< cs, set current color space for filling operations
void operatorColorSetStrokingColor(); ///< SC, set current stroking color
void operatorColorSetStrokingColorN(); ///< SCN, same as SC, but also supports Pattern, Separation, DeviceN and ICCBased color spaces
void operatorColorSetFillingColor(); ///< sc, set current filling color
@ -540,7 +573,7 @@ private:
void operatorTextSetWordSpacing(PDFReal wordSpacing); ///< Tw, set text word spacing
void operatorTextSetHorizontalScale(PDFReal horizontalScaling); ///< Tz, set text horizontal scaling (in percents, 100% = normal scaling)
void operatorTextSetLeading(PDFReal leading); ///< TL, set text leading
void operatorTextSetFontAndFontSize(PDFName fontName, PDFReal fontSize); ///< Tf, set text font (name from dictionary) and its size
void operatorTextSetFontAndFontSize(PDFOperandName fontName, PDFReal fontSize); ///< Tf, set text font (name from dictionary) and its size
void operatorTextSetRenderMode(PDFInteger mode); ///< Tr, set text render mode
void operatorTextSetRise(PDFReal rise); ///< Ts, set text rise
@ -550,9 +583,25 @@ private:
void operatorTextSetMatrix(PDFReal a, PDFReal b, PDFReal c, PDFReal d, PDFReal e, PDFReal f); ///< Tm, set text matrix
void operatorTextMoveByLeading(); ///< T*, moves text by leading, equivalent to 0 leading Td
// Text showing: Tj, TJ, ', "
void operatorTextShowTextString(PDFOperandString text); ///< Tj, show text string
void operatorTextShowTextIndividualSpacing(); ///< TJ, show text, allow individual text spacing
void operatorTextNextLineShowText(PDFOperandString text); ///< ', move to the next line and show text ("string '" is equivalent to "T* string Tj")
void operatorTextSetSpacingAndShowText(PDFReal t_w, PDFReal t_c, PDFOperandString text); ///< ", move to the next line, set spacing and show text (equivalent to sequence "w1 Tw w2 Tc string '")
// Draws the text using the text sequence
void drawText(const TextSequence& textSequence);
/// Returns realized font
const QRawFont& getRealizedFont() { return m_realizedFont.get(this, &PDFPageContentProcessor::getRealizedFontImpl); }
/// Returns realized font (or empty font, if font can't be realized)
QRawFont getRealizedFontImpl() const;
const PDFPage* m_page;
const PDFDocument* m_document;
const PDFDictionary* m_colorSpaceDictionary;
const PDFDictionary* m_fontDictionary;
// Default color spaces
PDFColorSpacePointer m_deviceGrayColorSpace;
@ -576,6 +625,9 @@ private:
/// Nesting level of the begin/end of text object
int m_textBeginEndState;
/// Actually realized physical font
PDFCachedItem<QRawFont> m_realizedFont;
};
} // namespace pdf

View File

@ -102,6 +102,8 @@ void PDFPainter::performUpdateGraphicsState(const PDFPageContentProcessorState&
{
m_currentBrush.dirty();
}
PDFPageContentProcessor::performUpdateGraphicsState(state);
}
void PDFPainter::performSaveGraphicState(ProcessOrder order)