Editor plugin: SVG images

This commit is contained in:
Jakub Melka 2024-06-16 18:32:38 +02:00
parent 758e5fe7bb
commit c52e487b04
5 changed files with 268 additions and 0 deletions

View File

@ -210,6 +210,9 @@ bool EditorPlugin::save()
const pdf::PDFPage* page = m_document->getCatalog()->getPage(pageIndex);
const pdf::PDFEditedPageContent& editedPageContent = m_editedPageContent.at(pageIndex);
QRectF mediaBox = page->getMediaBox();
QRectF mediaBoxMM = page->getMediaBoxMM();
pdf::PDFPageContentEditorContentStreamBuilder contentStreamBuilder(m_document);
contentStreamBuilder.setFontDictionary(editedPageContent.getFontDictionary());
@ -290,8 +293,24 @@ bool EditorPlugin::save()
else
{
// It is probably an SVG image
pdf::PDFContentEditorPaintDevice paintDevice(&contentStreamBuilder, mediaBox, mediaBoxMM);
QPainter painter(&paintDevice);
QList<pdf::PDFRenderError> errors;
pdf::PDFTextLayoutGetter textLayoutGetter(nullptr, pageIndex);
elementImage->drawPage(&painter, &m_scene, pageIndex, nullptr, textLayoutGetter, QTransform(), errors);
}
}
if (elementTextBox)
{
pdf::PDFContentEditorPaintDevice paintDevice(&contentStreamBuilder, mediaBox, mediaBoxMM);
QPainter painter(&paintDevice);
QList<pdf::PDFRenderError> errors;
pdf::PDFTextLayoutGetter textLayoutGetter(nullptr, pageIndex);
elementTextBox->drawPage(&painter, &m_scene, pageIndex, nullptr, textLayoutGetter, QTransform(), errors);
}
}
}

View File

@ -168,6 +168,19 @@ QPainter::CompositionMode PDFBlendModeInfo::getCompositionModeFromBlendMode(Blen
return QPainter::CompositionMode_SourceOver;
}
BlendMode PDFBlendModeInfo::getBlendModeFromCompositionMode(QPainter::CompositionMode mode)
{
for (BlendMode blendMode : getBlendModes())
{
if (mode == getCompositionModeFromBlendMode(blendMode))
{
return blendMode;
}
}
return BlendMode::Normal;
}
QString PDFBlendModeInfo::getBlendModeName(BlendMode mode)
{
for (const std::pair<const char*, BlendMode>& info : BLEND_MODE_INFOS)

View File

@ -86,6 +86,9 @@ public:
/// \param mode Blend mode
static QPainter::CompositionMode getCompositionModeFromBlendMode(BlendMode mode);
/// Returns blend mode from QPainter's composition mode.
static BlendMode getBlendModeFromCompositionMode(QPainter::CompositionMode mode);
/// Returns blend mode name
/// \param mode Blend mode
static QString getBlendModeName(BlendMode mode);

View File

@ -24,10 +24,185 @@
#include <QBuffer>
#include <QStringBuilder>
#include <QXmlStreamReader>
#include <QPaintEngine>
namespace pdf
{
class PDFContentEditorPaintEngine : public QPaintEngine
{
public:
PDFContentEditorPaintEngine(PDFPageContentEditorContentStreamBuilder* builder) :
QPaintEngine(PrimitiveTransform | AlphaBlend | PorterDuff | PainterPaths | ConstantOpacity | BlendModes | PaintOutsidePaintEvent),
m_builder(builder)
{
}
virtual Type type() const override { return User; }
virtual bool begin(QPaintDevice*) override;
virtual bool end() override;
virtual void updateState(const QPaintEngineState& state) override;
virtual void drawPixmap(const QRectF& r, const QPixmap& pm, const QRectF& sr) override;
virtual void drawPath(const QPainterPath& path);
virtual void drawPolygon(const QPointF* points, int pointCount, PolygonDrawMode mode);
private:
PDFPageContentProcessorState m_state;
PDFPageContentEditorContentStreamBuilder* m_builder = nullptr;
bool m_isFillActive = false;
bool m_isStrokeActive = false;
};
bool PDFContentEditorPaintEngine::begin(QPaintDevice*)
{
return !isActive();
}
bool PDFContentEditorPaintEngine::end()
{
return true;
}
void PDFContentEditorPaintEngine::updateState(const QPaintEngineState& newState)
{
QPaintEngine::DirtyFlags stateFlags = newState.state();
if (stateFlags.testFlag(QPaintEngine::DirtyPen))
{
PDFPainterHelper::applyPenToGraphicState(&m_state, newState.pen());
m_isStrokeActive = newState.pen().style() != Qt::NoPen;
}
if (stateFlags.testFlag(QPaintEngine::DirtyBrush))
{
PDFPainterHelper::applyBrushToGraphicState(&m_state, newState.brush());
m_isFillActive = newState.brush().style() != Qt::NoBrush;
}
if (stateFlags.testFlag(QPaintEngine::DirtyTransform))
{
m_state.setCurrentTransformationMatrix(newState.transform());
}
if (stateFlags.testFlag(QPaintEngine::DirtyCompositionMode))
{
m_state.setBlendMode(PDFBlendModeInfo::getBlendModeFromCompositionMode(newState.compositionMode()));
}
if (stateFlags.testFlag(QPaintEngine::DirtyOpacity))
{
m_state.setAlphaFilling(newState.opacity());
m_state.setAlphaStroking(newState.opacity());
}
}
void PDFContentEditorPaintEngine::drawPixmap(const QRectF& r, const QPixmap& pm, const QRectF& sr)
{
QPixmap pixmap = pm.copy(sr.toRect());
m_builder->writeImage(pixmap.toImage(), m_state.getCurrentTransformationMatrix(), r);
}
void PDFContentEditorPaintEngine::drawPath(const QPainterPath& path)
{
m_builder->writeStyledPath(path, m_state, m_isStrokeActive, m_isFillActive);
}
void PDFContentEditorPaintEngine::drawPolygon(const QPointF* points,
int pointCount,
PolygonDrawMode mode)
{
bool isStroking = m_isStrokeActive;
bool isFilling = m_isFillActive && mode != PolylineMode;
QPolygonF polygon;
for (int i = 0; i < pointCount; ++i)
{
polygon << points[i];
}
QPainterPath path;
path.addPolygon(polygon);
Qt::FillRule fillRule = Qt::OddEvenFill;
switch (mode)
{
case QPaintEngine::OddEvenMode:
fillRule = Qt::OddEvenFill;
break;
case QPaintEngine::WindingMode:
fillRule = Qt::WindingFill;
break;
case QPaintEngine::ConvexMode:
break;
case QPaintEngine::PolylineMode:
break;
}
path.setFillRule(fillRule);
m_builder->writeStyledPath(path, m_state, isStroking, isFilling);
}
PDFContentEditorPaintDevice::PDFContentEditorPaintDevice(PDFPageContentEditorContentStreamBuilder* builder, QRectF mediaRect, QRectF mediaRectMM) :
m_paintEngine(new PDFContentEditorPaintEngine(builder)),
m_mediaRect(mediaRect),
m_mediaRectMM(mediaRectMM)
{
}
int PDFContentEditorPaintDevice::metric(PaintDeviceMetric metric) const
{
switch (metric)
{
case QPaintDevice::PdmWidth:
return m_mediaRect.width();
case QPaintDevice::PdmHeight:
return m_mediaRect.height();
case QPaintDevice::PdmWidthMM:
return m_mediaRectMM.width();
case QPaintDevice::PdmHeightMM:
return m_mediaRectMM.height();
case QPaintDevice::PdmNumColors:
return INT_MAX;
case QPaintDevice::PdmDepth:
return 8;
case QPaintDevice::PdmDpiX:
case QPaintDevice::PdmPhysicalDpiX:
return m_mediaRect.width() * 25.4 / m_mediaRectMM.width();
case QPaintDevice::PdmDpiY:
case QPaintDevice::PdmPhysicalDpiY:
return m_mediaRect.height() * 25.4 / m_mediaRectMM.height();
case QPaintDevice::PdmDevicePixelRatio:
case QPaintDevice::PdmDevicePixelRatioScaled:
return 1.0;
default:
Q_ASSERT(false);
break;
}
return 0;
}
PDFContentEditorPaintDevice::~PDFContentEditorPaintDevice()
{
delete m_paintEngine;
}
int PDFContentEditorPaintDevice::devType() const
{
return QInternal::Picture;
}
QPaintEngine* PDFContentEditorPaintDevice::paintEngine() const
{
return m_paintEngine;
}
PDFPageContentEditorContentStreamBuilder::PDFPageContentEditorContentStreamBuilder(PDFDocument* document) :
m_document(document)
{
@ -818,6 +993,26 @@ void PDFPageContentEditorContentStreamBuilder::writeStyledPath(const QPainterPat
}
}
void PDFPageContentEditorContentStreamBuilder::writeStyledPath(const QPainterPath& path, const PDFPageContentProcessorState& state, bool isStroking, bool isFilling)
{
QTextStream stream(&m_outputContent, QDataStream::WriteOnly | QDataStream::Append);
writeStateDifference(stream, state);
bool isNeededToWriteCurrentTransformationMatrix = this->isNeededToWriteCurrentTransformationMatrix();
if (isNeededToWriteCurrentTransformationMatrix)
{
stream << "q" << Qt::endl;
writeCurrentTransformationMatrix(stream);
}
writePainterPath(stream, path, isStroking, isFilling);
if (isNeededToWriteCurrentTransformationMatrix)
{
stream << "Q" << Qt::endl;
}
}
void PDFPageContentEditorContentStreamBuilder::writeImage(const QImage& image,
const QRectF& rectangle)
{
@ -849,6 +1044,14 @@ void PDFPageContentEditorContentStreamBuilder::writeImage(const QImage& image,
stream << "Q" << Qt::endl;
}
void PDFPageContentEditorContentStreamBuilder::writeImage(const QImage& image, QTransform transform, const QRectF& rectangle)
{
QTransform oldTransform = m_currentState.getCurrentTransformationMatrix();
m_currentState.setCurrentTransformationMatrix(transform);
writeImage(image, rectangle);
m_currentState.setCurrentTransformationMatrix(oldTransform);
}
bool PDFPageContentEditorContentStreamBuilder::isNeededToWriteCurrentTransformationMatrix() const
{
return !m_currentState.getCurrentTransformationMatrix().isIdentity();

View File

@ -20,9 +20,31 @@
#include "pdfpagecontenteditorprocessor.h"
#include <QPaintDevice>
namespace pdf
{
class PDFPageContentElement;
class PDFContentEditorPaintEngine;
class PDFPageContentEditorContentStreamBuilder;
class PDF4QTLIBCORESHARED_EXPORT PDFContentEditorPaintDevice : public QPaintDevice
{
public:
PDFContentEditorPaintDevice(PDFPageContentEditorContentStreamBuilder* builder, QRectF mediaRect, QRectF mediaRectMM);
virtual ~PDFContentEditorPaintDevice() override;
virtual int devType() const override;
virtual QPaintEngine* paintEngine() const override;
protected:
virtual int metric(PaintDeviceMetric metric) const override;
private:
PDFContentEditorPaintEngine* m_paintEngine;
QRectF m_mediaRect;
QRectF m_mediaRectMM;
};
class PDF4QTLIBCORESHARED_EXPORT PDFPageContentEditorContentStreamBuilder
{
@ -48,7 +70,15 @@ public:
bool isStroking,
bool isFilling);
void writeStyledPath(const QPainterPath& path,
const PDFPageContentProcessorState& state,
bool isStroking,
bool isFilling);
void writeImage(const QImage& image, const QRectF& rectangle);
void writeImage(const QImage& image, QTransform transform, const QRectF& rectangle);
const PDFPageContentProcessorState& getCurrentState() { return m_currentState; }
private:
bool isNeededToWriteCurrentTransformationMatrix() const;