Adjust graphic state according to the PDF 2.0 specification

This commit is contained in:
Jakub Melka 2020-08-09 17:10:42 +02:00
parent 8262654b0e
commit c033de6917
4 changed files with 319 additions and 51 deletions

View File

@ -79,6 +79,13 @@ static constexpr const char* ICCBASED_ALTERNATE = "Alternate";
static constexpr const char* ICCBASED_N = "N";
static constexpr const char* ICCBASED_RANGE = "Range";
enum class BlackPointCompensationMode
{
Default,
ON,
OFF
};
/// Image raw data - containing data for image. Image data are row-ordered, and by components.
/// So the row can be for 3-components RGB like 'RGBRGBRGB...RGB', where size of row in bytes is 3 * width of image.
class PDFImageData

View File

@ -1722,7 +1722,7 @@ void PDFPageContentProcessor::processApplyGraphicState(const PDFDictionary* grap
const PDFReal lineWidth = loader.readNumberFromDictionary(graphicStateDictionary, "LW", m_graphicState.getLineWidth());
const Qt::PenCapStyle penCapStyle = convertLineCapToPenCapStyle(loader.readNumberFromDictionary(graphicStateDictionary, "LC", convertPenCapStyleToLineCap(m_graphicState.getLineCapStyle())));
const Qt::PenJoinStyle penJoinStyle = convertLineJoinToPenJoinStyle(loader.readNumberFromDictionary(graphicStateDictionary, "LJ", convertPenJoinStyleToLineJoin(m_graphicState.getLineJoinStyle())));
const PDFReal mitterLimit = loader.readNumberFromDictionary(graphicStateDictionary, "MT", m_graphicState.getMitterLimit());
const PDFReal mitterLimit = loader.readNumberFromDictionary(graphicStateDictionary, "ML", m_graphicState.getMitterLimit());
const PDFObject& lineDashPatternObject = m_document->getObject(graphicStateDictionary->get("D"));
if (lineDashPatternObject.isArray())
@ -1731,6 +1731,7 @@ void PDFPageContentProcessor::processApplyGraphicState(const PDFDictionary* grap
if (lineDashPatternDefinitionArray->getCount() == 2)
{
PDFLineDashPattern pattern(loader.readNumberArray(lineDashPatternDefinitionArray->getItem(0)), loader.readNumber(lineDashPatternDefinitionArray->getItem(1), 0.0));
pattern.fix();
m_graphicState.setLineDashPattern(pattern);
}
}
@ -1744,7 +1745,7 @@ void PDFPageContentProcessor::processApplyGraphicState(const PDFDictionary* grap
QByteArray renderingIntentName = loader.readNameFromDictionary(graphicStateDictionary, "RI");
const bool alphaIsShape = loader.readBooleanFromDictionary(graphicStateDictionary, "AIS", m_graphicState.getAlphaIsShape());
const bool strokeAdjustment = loader.readBooleanFromDictionary(graphicStateDictionary, "SA", m_graphicState.getStrokeAdjustment());
const PDFDictionary* softMask = m_document->getDictionaryFromObject(graphicStateDictionary->get("SMask"));
const PDFDictionary* softMask = graphicStateDictionary->hasKey("SMask") ? m_document->getDictionaryFromObject(graphicStateDictionary->get("SMask")) : m_graphicState.getSoftMask();
// We will try to get blend mode name from the array (if BM is array). First supported blend mode should
// be used. In PDF 2.0, array is deprecated, so for backward compatibility, we extract first blend mode
@ -1777,11 +1778,63 @@ void PDFPageContentProcessor::processApplyGraphicState(const PDFDictionary* grap
PDFOverprintMode overprintMode = m_graphicState.getOverprintMode();
overprintMode.overprintMode = loader.readIntegerFromDictionary(graphicStateDictionary, "OPM", overprintMode.overprintMode);
if (graphicStateDictionary->hasKey("OP"))
// Overprint for filling is ruled by overprint for stroking, if "op" is not present, according to the specification
overprintMode.overprintStroking = loader.readBooleanFromDictionary(graphicStateDictionary, "OP", overprintMode.overprintStroking);
overprintMode.overprintFilling = loader.readBooleanFromDictionary(graphicStateDictionary, "op", overprintMode.overprintStroking);
constexpr std::array blackPointCompensationModes = {
std::pair<const char*, BlackPointCompensationMode>{ "Default", BlackPointCompensationMode::Default },
std::pair<const char*, BlackPointCompensationMode>{ "ON", BlackPointCompensationMode::ON },
std::pair<const char*, BlackPointCompensationMode>{ "OFF", BlackPointCompensationMode::OFF },
};
BlackPointCompensationMode blackPointCompensationMode = m_graphicState.getBlackPointCompensationMode();
if (graphicStateDictionary->hasKey("UseBlackPtComp"))
{
// Overprint for filling is ruled by overprint for stroking, if "op" is not present, according to the specification
overprintMode.overprintStroking = loader.readBooleanFromDictionary(graphicStateDictionary, "OP", overprintMode.overprintStroking);
overprintMode.overprintFilling = loader.readBooleanFromDictionary(graphicStateDictionary, "op", overprintMode.overprintStroking);
blackPointCompensationMode = loader.readEnumByName(graphicStateDictionary->get("UseBlackPtComp"), blackPointCompensationModes.cbegin(), blackPointCompensationModes.cend(), BlackPointCompensationMode::Default);
}
PDFObject blackGenerationFunctionObject = m_graphicState.getBlackGenerationFunction();
if (graphicStateDictionary->hasKey("BG") || graphicStateDictionary->hasKey("BG2"))
{
blackGenerationFunctionObject = m_document->getObject(graphicStateDictionary->get("BG2"));
if (blackGenerationFunctionObject.isNull())
{
blackGenerationFunctionObject = m_document->getObject(graphicStateDictionary->get("BG"));
}
}
PDFObject undercolorRemovalFunctionObject = m_graphicState.getUndercolorRemovalFunction();
if (graphicStateDictionary->hasKey("UCR") || graphicStateDictionary->hasKey("UCR2"))
{
undercolorRemovalFunctionObject = m_document->getObject(graphicStateDictionary->get("UCR2"));
if (undercolorRemovalFunctionObject.isNull())
{
undercolorRemovalFunctionObject = m_document->getObject(graphicStateDictionary->get("UCR"));
}
}
PDFObject transferFunctionObject = m_graphicState.getTransferFunction();
if (graphicStateDictionary->hasKey("TR") || graphicStateDictionary->hasKey("TR2"))
{
transferFunctionObject = m_document->getObject(graphicStateDictionary->get("TR2"));
if (transferFunctionObject.isNull())
{
transferFunctionObject = m_document->getObject(graphicStateDictionary->get("TR"));
}
}
PDFObject halftoneObject = graphicStateDictionary->hasKey("HT") ? m_document->getObject(graphicStateDictionary->get("HT")) : m_graphicState.getHalftone();
QPointF halftoneOrigin = m_graphicState.getHalftoneOrigin();
if (graphicStateDictionary->hasKey("HTO"))
{
std::vector<PDFReal> halftoneOriginArray = loader.readNumberArrayFromDictionary(graphicStateDictionary, "HTO");
if (halftoneOriginArray.size() >= 2)
{
halftoneOrigin = QPointF(halftoneOriginArray[0], halftoneOriginArray[1]);
}
}
m_graphicState.setLineWidth(lineWidth);
@ -1797,6 +1850,12 @@ void PDFPageContentProcessor::processApplyGraphicState(const PDFDictionary* grap
m_graphicState.setAlphaIsShape(alphaIsShape);
m_graphicState.setStrokeAdjustment(strokeAdjustment);
m_graphicState.setSoftMask(softMask);
m_graphicState.setBlackPointCompensationMode(blackPointCompensationMode);
m_graphicState.setBlackGenerationFunction(qMove(blackGenerationFunctionObject));
m_graphicState.setUndercolorRemovalFunction(qMove(undercolorRemovalFunctionObject));
m_graphicState.setTransferFunction(qMove(transferFunctionObject));
m_graphicState.setHalftone(qMove(halftoneObject));
m_graphicState.setHalftoneOrigin(halftoneOrigin);
updateGraphicState();
if (graphicStateDictionary->hasKey("Font"))
@ -3208,6 +3267,7 @@ PDFPageContentProcessor::PDFPageContentProcessorState::PDFPageContentProcessorSt
m_alphaIsShape(false),
m_strokeAdjustment(false),
m_softMask(nullptr),
m_blackPointCompensationMode(BlackPointCompensationMode::Default),
m_stateFlags(StateUnchanged)
{
m_fillColorSpace.reset(new PDFDeviceGrayColorSpace);
@ -3253,6 +3313,12 @@ PDFPageContentProcessor::PDFPageContentProcessorState& PDFPageContentProcessor::
setAlphaIsShape(other.getAlphaIsShape());
setStrokeAdjustment(other.getStrokeAdjustment());
setSoftMask(other.getSoftMask());
setBlackPointCompensationMode(other.getBlackPointCompensationMode());
setBlackGenerationFunction(other.getBlackGenerationFunction());
setUndercolorRemovalFunction(other.getUndercolorRemovalFunction());
setTransferFunction(other.getTransferFunction());
setHalftone(other.getHalftone());
setHalftoneOrigin(other.getHalftoneOrigin());
return *this;
}
@ -3505,6 +3571,90 @@ void PDFPageContentProcessor::PDFPageContentProcessorState::setSoftMask(const PD
}
}
BlackPointCompensationMode PDFPageContentProcessor::PDFPageContentProcessorState::getBlackPointCompensationMode() const
{
return m_blackPointCompensationMode;
}
void PDFPageContentProcessor::PDFPageContentProcessorState::setBlackPointCompensationMode(BlackPointCompensationMode blackPointCompensationMode)
{
if (m_blackPointCompensationMode != blackPointCompensationMode)
{
m_blackPointCompensationMode = blackPointCompensationMode;
m_stateFlags |= StateBlackPointCompensation;
}
}
PDFObject PDFPageContentProcessor::PDFPageContentProcessorState::getHalftone() const
{
return m_halftone;
}
void PDFPageContentProcessor::PDFPageContentProcessorState::setHalftone(const PDFObject& halftone)
{
if (m_halftone != halftone)
{
m_halftone = halftone;
m_stateFlags |= StateHalftone;
}
}
QPointF PDFPageContentProcessor::PDFPageContentProcessorState::getHalftoneOrigin() const
{
return m_halftoneOrigin;
}
void PDFPageContentProcessor::PDFPageContentProcessorState::setHalftoneOrigin(const QPointF& halftoneOrigin)
{
if (m_halftoneOrigin != halftoneOrigin)
{
m_halftoneOrigin = halftoneOrigin;
m_stateFlags |= StateHalftoneOrigin;
}
}
PDFObject PDFPageContentProcessor::PDFPageContentProcessorState::getTransferFunction() const
{
return m_transferFunction;
}
void PDFPageContentProcessor::PDFPageContentProcessorState::setTransferFunction(const PDFObject& transferFunction)
{
if (m_transferFunction != transferFunction)
{
m_transferFunction = transferFunction;
m_stateFlags |= StateTransferFunction;
}
}
PDFObject PDFPageContentProcessor::PDFPageContentProcessorState::getUndercolorRemovalFunction() const
{
return m_undercolorRemovalFunction;
}
void PDFPageContentProcessor::PDFPageContentProcessorState::setUndercolorRemovalFunction(const PDFObject& undercolorRemovalFunction)
{
if (m_undercolorRemovalFunction != undercolorRemovalFunction)
{
m_undercolorRemovalFunction = undercolorRemovalFunction;
m_stateFlags |= StateUndercolorRemovalFunction;
}
}
PDFObject PDFPageContentProcessor::PDFPageContentProcessorState::getBlackGenerationFunction() const
{
return m_blackGenerationFunction;
}
void PDFPageContentProcessor::PDFPageContentProcessorState::setBlackGenerationFunction(const PDFObject& blackGenerationFunction)
{
if (m_blackGenerationFunction != blackGenerationFunction)
{
m_blackGenerationFunction = blackGenerationFunction;
m_stateFlags |= StateBlackGenerationFunction;
}
}
void PDFPageContentProcessor::PDFPageContentProcessorState::setTextMatrix(const QMatrix& textMatrix)
{
if (m_textMatrix != textMatrix)
@ -3613,4 +3763,35 @@ PDFPageContentProcessor::PDFTransparencyGroupGuard::~PDFTransparencyGroupGuard()
m_processor->performEndTransparencyGroup(ProcessOrder::AfterOperation, group);
}
PDFLineDashPattern::PDFLineDashPattern(const std::vector<PDFReal>& dashArray, PDFReal dashOffset) :
m_dashArray(dashArray),
m_dashOffset(dashOffset)
{
if (m_dashArray.size() % 2 == 1)
{
m_dashArray.push_back(m_dashArray.back());
}
}
void PDFLineDashPattern::fix()
{
if (m_dashOffset < 0.0)
{
// According to the PDF 2.0 specification, if dash offset is negative,
// then twice of sum of lengths in the dash array should be added
// so dash offset will become positive.
const PDFReal totalLength = 2 * std::accumulate(m_dashArray.cbegin(), m_dashArray.cend(), 0.0);
if (totalLength > 0.0)
{
m_dashOffset += (std::floor(std::abs(m_dashOffset / totalLength)) + 1.0) * totalLength;
}
else
{
// Reset to default solid line, dash pattern is invalid
*this = PDFLineDashPattern();
}
}
}
} // namespace pdf

View File

@ -48,15 +48,7 @@ class PDFLineDashPattern
{
public:
explicit inline PDFLineDashPattern() = default;
explicit inline PDFLineDashPattern(const std::vector<PDFReal>& dashArray, PDFReal dashOffset) :
m_dashArray(dashArray),
m_dashOffset(dashOffset)
{
if (m_dashArray.size() % 2 == 1)
{
m_dashArray.push_back(m_dashArray.back());
}
}
explicit PDFLineDashPattern(const std::vector<PDFReal>& dashArray, PDFReal dashOffset);
inline const std::vector<PDFReal>& getDashArray() const { return m_dashArray; }
inline void setDashArray(const std::vector<PDFReal>& dashArray) { m_dashArray = dashArray; }
@ -70,6 +62,9 @@ public:
/// Is line solid? Function returns true, if yes.
bool isSolid() const { return m_dashArray.empty(); }
/// Fix line dash pattern according to the specification
void fix();
private:
std::vector<PDFReal> m_dashArray;
PDFReal m_dashOffset = 0.0;
@ -273,45 +268,51 @@ protected:
PDFPageContentProcessorState& operator=(PDFPageContentProcessorState&&) = delete;
PDFPageContentProcessorState& operator=(const PDFPageContentProcessorState& other);
enum StateFlag : uint32_t
enum StateFlag : uint64_t
{
StateUnchanged = 0x00000000,
StateCurrentTransformationMatrix = 0x00000001,
StateStrokeColorSpace = 0x00000002,
StateFillColorSpace = 0x00000004,
StateStrokeColor = 0x00000008,
StateFillColor = 0x00000010,
StateLineWidth = 0x00000020,
StateLineCapStyle = 0x00000040,
StateLineJoinStyle = 0x00000080,
StateMitterLimit = 0x00000100,
StateLineDashPattern = 0x00000200,
StateRenderingIntentName = 0x00000400,
StateFlatness = 0x00000800,
StateSmoothness = 0x00001000,
StateTextMatrix = 0x00002000,
StateTextLineMatrix = 0x00004000,
StateTextCharacterSpacing = 0x00008000,
StateTextWordSpacing = 0x00010000,
StateTextHorizontalScaling = 0x00020000,
StateTextLeading = 0x00040000,
StateTextFont = 0x00080000,
StateTextFontSize = 0x00100000,
StateTextRenderingMode = 0x00200000,
StateTextRise = 0x00400000,
StateTextKnockout = 0x00800000,
StateAlphaStroking = 0x01000000,
StateAlphaFilling = 0x02000000,
StateBlendMode = 0x04000000,
StateRenderingIntent = 0x08000000,
StateOverprint = 0x10000000,
StateAlphaIsShape = 0x20000000,
StateStrokeAdjustment = 0x40000000,
StateSoftMask = 0x80000000,
StateAll = 0xFFFFFFFF
StateUnchanged = 0x0000000000000000,
StateCurrentTransformationMatrix = 0x0000000000000001,
StateStrokeColorSpace = 0x0000000000000002,
StateFillColorSpace = 0x0000000000000004,
StateStrokeColor = 0x0000000000000008,
StateFillColor = 0x0000000000000010,
StateLineWidth = 0x0000000000000020,
StateLineCapStyle = 0x0000000000000040,
StateLineJoinStyle = 0x0000000000000080,
StateMitterLimit = 0x0000000000000100,
StateLineDashPattern = 0x0000000000000200,
StateRenderingIntentName = 0x0000000000000400,
StateFlatness = 0x0000000000000800,
StateSmoothness = 0x0000000000001000,
StateTextMatrix = 0x0000000000002000,
StateTextLineMatrix = 0x0000000000004000,
StateTextCharacterSpacing = 0x0000000000008000,
StateTextWordSpacing = 0x0000000000010000,
StateTextHorizontalScaling = 0x0000000000020000,
StateTextLeading = 0x0000000000040000,
StateTextFont = 0x0000000000080000,
StateTextFontSize = 0x0000000000100000,
StateTextRenderingMode = 0x0000000000200000,
StateTextRise = 0x0000000000400000,
StateTextKnockout = 0x0000000000800000,
StateAlphaStroking = 0x0000000001000000,
StateAlphaFilling = 0x0000000002000000,
StateBlendMode = 0x0000000004000000,
StateRenderingIntent = 0x0000000008000000,
StateOverprint = 0x0000000010000000,
StateAlphaIsShape = 0x0000000020000000,
StateStrokeAdjustment = 0x0000000040000000,
StateSoftMask = 0x0000000080000000,
StateBlackPointCompensation = 0x0000000100000000,
StateBlackGenerationFunction = 0x0000000200000000,
StateUndercolorRemovalFunction = 0x0000000400000000,
StateTransferFunction = 0x0000000800000000,
StateHalftone = 0x0000001000000000,
StateHalftoneOrigin = 0x0000002000000000,
StateAll = 0xFFFFFFFFFFFFFFFF
};
Q_DECLARE_FLAGS(StateFlags, StateFlag)
using StateFlags = PDFFlags<StateFlag>;
const QMatrix& getCurrentTransformationMatrix() const { return m_currentTransformationMatrix; }
void setCurrentTransformationMatrix(const QMatrix& currentTransformationMatrix);
@ -418,6 +419,24 @@ protected:
const PDFDictionary* getSoftMask() const;
void setSoftMask(const PDFDictionary* softMask);
BlackPointCompensationMode getBlackPointCompensationMode() const;
void setBlackPointCompensationMode(BlackPointCompensationMode blackPointCompensationMode);
PDFObject getBlackGenerationFunction() const;
void setBlackGenerationFunction(const PDFObject& blackGenerationFunction);
PDFObject getUndercolorRemovalFunction() const;
void setUndercolorRemovalFunction(const PDFObject& undercolorRemovalFunction);
PDFObject getTransferFunction() const;
void setTransferFunction(const PDFObject& transferFunction);
PDFObject getHalftone() const;
void setHalftone(const PDFObject& halftone);
QPointF getHalftoneOrigin() const;
void setHalftoneOrigin(const QPointF& halftoneOrigin);
private:
QMatrix m_currentTransformationMatrix;
PDFColorSpacePointer m_strokeColorSpace;
@ -451,6 +470,12 @@ protected:
bool m_alphaIsShape;
bool m_strokeAdjustment;
const PDFDictionary* m_softMask;
BlackPointCompensationMode m_blackPointCompensationMode;
PDFObject m_blackGenerationFunction;
PDFObject m_undercolorRemovalFunction;
PDFObject m_transferFunction;
PDFObject m_halftone;
QPointF m_halftoneOrigin;
StateFlags m_stateFlags;
};

View File

@ -527,6 +527,61 @@ private:
QString m_errorMessage;
};
template<typename Enum>
class PDFFlags
{
public:
using Integer = typename std::underlying_type<Enum>::type;
constexpr inline PDFFlags() noexcept = default;
constexpr inline PDFFlags(Integer flags) noexcept : m_flags(flags) { }
constexpr inline PDFFlags(Enum flag) noexcept : m_flags(flag) { }
constexpr inline PDFFlags& operator|=(Integer flags) { m_flags |= flags; return *this; }
constexpr inline PDFFlags& operator|=(PDFFlags flags) { m_flags |= flags.m_flags; return *this; }
constexpr inline PDFFlags& operator|=(Enum flag) { m_flags |= flag; return *this; }
constexpr inline PDFFlags& operator&=(Integer flags) { m_flags &= flags; return *this; }
constexpr inline PDFFlags& operator&=(PDFFlags flags) { m_flags &= flags.m_flags; return *this; }
constexpr inline PDFFlags& operator&=(Enum flag) { m_flags &= flag; return *this; }
constexpr inline PDFFlags& operator^=(Integer flags) { m_flags ^= flags; return *this; }
constexpr inline PDFFlags& operator^=(PDFFlags flags) { m_flags ^= flags.m_flags; return *this; }
constexpr inline PDFFlags& operator^=(Enum flag) { m_flags ^= flag; return *this; }
constexpr inline operator Integer() const { return m_flags; }
constexpr inline PDFFlags operator|(Integer flags) const { return PDFFlags(m_flags | flags); }
constexpr inline PDFFlags operator|(PDFFlags flags) const { return PDFFlags(m_flags | flags.m_flags); }
constexpr inline PDFFlags operator|(Enum flag) const { return PDFFlags(m_flags | flag); }
constexpr inline PDFFlags operator&(Integer flags) const { return PDFFlags(m_flags & flags); }
constexpr inline PDFFlags operator&(PDFFlags flags) const { return PDFFlags(m_flags & flags.m_flags); }
constexpr inline PDFFlags operator&(Enum flag) const { return PDFFlags(m_flags & flag); }
constexpr inline PDFFlags operator^(Integer flags) const { return PDFFlags(m_flags ^ flags); }
constexpr inline PDFFlags operator^(PDFFlags flags) const { return PDFFlags(m_flags ^ flags.m_flags); }
constexpr inline PDFFlags operator^(Enum flag) const { return PDFFlags(m_flags ^ flag); }
constexpr inline PDFFlags operator~() const { return PDFFlags(~m_flags); }
// Explicit bool operator to disallow implicit conversion
constexpr inline explicit operator bool() const { return m_flags != 0; }
constexpr inline bool operator!() const { return m_flags == 0; }
constexpr inline bool testFlag(Enum flag) const { return (m_flags & flag) == flag; }
constexpr inline PDFFlags& setFlag(Enum flag, bool on = true)
{
if (on)
{
m_flags |= Integer(flag);
}
else
{
m_flags &= ~Integer(flag);
}
return *this;
}
private:
Integer m_flags = Integer();
};
/// Set of closed intervals
class PDFClosedIntervalSet
{