View Issue Details

IDProjectCategoryView StatusLast Update
0016077ScribusPDFpublic2020-04-05 19:37
ReporterPontobart Assigned Tojghali  
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Product Version1.5.6.svn 
Target VersionFixed in Version1.5.6.svn 
Summary0016077: Rotate imported path's and tiling patterns
DescriptionFurther improvements to PDF imports that fix most of the issues reported in issue 0016073.

The main change is that paths and tiling patterns are now imported as rotated objects. This allows to set transparency masks and patterns without having to deal with their rotation for easy cases. (There are probably still bugs for non-simple cases).

In addition it adds some heuristics to work around the broken QPainterPath::intersected function.
TagsNo tags attached.
PatchNo

Activities

Pontobart

2020-03-29 21:30

reporter  

rotate_path_and_tiling.diff (8,972 bytes)
Index: scribus/plugins/import/pdf/slaoutput.h
===================================================================
--- scribus/plugins/import/pdf/slaoutput.h	(Revision 23542)
+++ scribus/plugins/import/pdf/slaoutput.h	(Arbeitskopie)
@@ -332,11 +332,12 @@
 		GBool isolated;
 		GBool alpha;
 		QString maskName;
-		QRectF maskBBox;
+		QPointF maskPos;
 		bool inverted;
 	};
 	QStack<groupEntry> m_groupStack;
 	QString m_currentMask;
+	QPointF m_currentMaskPosition;
 	ScribusDoc* m_doc;
 	Selection* tmpSel;
 	QList<PageItem*> *m_Elements;
Index: scribus/plugins/import/pdf/slaoutput.cpp
===================================================================
--- scribus/plugins/import/pdf/slaoutput.cpp	(Revision 23542)
+++ scribus/plugins/import/pdf/slaoutput.cpp	(Arbeitskopie)
@@ -20,6 +20,42 @@
 #include "util_math.h"
 #include <tiffio.h>
 
+namespace
+{
+	// Compute the intersection of two paths while considering the fillrule of each of them.
+	// QPainterPath has the right interface to do the operation but is currently buggy.
+	// See for example https://bugreports.qt.io/browse/QTBUG-83102. Thus this function
+	// applies some heuristics to find the best result. As soon QPainterPath is fixed
+	// one can just use a.intersected(b) wherever this function is called.
+	// TODO: Find an alternative to QPainterPath that works for different fill rules.
+	QPainterPath intersection(QPainterPath const &a, QPainterPath const &b)
+	{
+		// An empty path is treated like the whole area.
+		if (a.elementCount() == 0)
+			return b;
+		if (b.elementCount() == 0)
+			return a;
+
+		QPainterPath ret_a = a.intersected(b);
+		QPainterPath ret_b = b.intersected(a);
+		// Sometimes the resulting paths are not closed even though they should.
+		// Close them now.
+		ret_a.closeSubpath();
+		ret_b.closeSubpath();
+
+		// Most of the time one of the two operations returns an empty path while the other
+		// gives us the desired result. Return the non-empty one.
+		if (ret_a.elementCount() == 0)
+			return ret_b;
+		if (ret_b.elementCount() == 0)
+			return ret_a;
+
+		// There are cases where both intersections are not empty but one of them is quite
+		// complicated with several subpaths, etc. We return the simpler one.
+		return (ret_a.elementCount() <= ret_b.elementCount()) ? ret_a : ret_b;
+	}
+}
+
 LinkSubmitForm::LinkSubmitForm(Object *actionObj)
 {
 	if (!actionObj->isDict())
@@ -1411,6 +1447,7 @@
 
 void SlaOutputDev::beginTransparencyGroup(GfxState *state, POPPLER_CONST_070 double *bbox, GfxColorSpace * /*blendingColorSpace*/, GBool isolated, GBool knockout, GBool forSoftMask)
 {
+// 	qDebug() << "SlaOutputDev::beginTransparencyGroup isolated:" << isolated << "knockout:" << knockout << "forSoftMask:" << forSoftMask;
 	pushGroup("", forSoftMask);
 	m_groupStack.top().isolated = isolated;
 }
@@ -1417,6 +1454,7 @@
 
 void SlaOutputDev::paintTransparencyGroup(GfxState *state, POPPLER_CONST_070 double *bbox)
 {
+// 	qDebug() << "SlaOutputDev::paintTransparencyGroup";
 	if (m_groupStack.count() != 0)
 	{
 		if ((m_groupStack.top().Items.count() != 0) && (!m_groupStack.top().forSoftMask))
@@ -1430,6 +1468,7 @@
 
 void SlaOutputDev::endTransparencyGroup(GfxState *state)
 {
+// 	qDebug() << "SlaOutputDev::endTransparencyGroup";
 	if (m_groupStack.count() <= 0)
 		return;
 
@@ -1458,6 +1497,7 @@
 		m_doc->DoDrawing = false;
 		pat.width = ite->width();
 		pat.height = ite->height();
+		m_currentMaskPosition = QPointF(ite->xPos(), ite->yPos());
 		ite->gXpos = 0;
 		ite->gYpos = 0;
 		ite->setXYPos(ite->gXpos, ite->gYpos, true);
@@ -1528,8 +1568,8 @@
 	else
 		m_groupStack.top().inverted = true;
 	m_groupStack.top().maskName = m_currentMask;
-	// Remember the mask's bounding box as it might not align with the image to which the mask is later assigned.
-	m_groupStack.top().maskBBox = QRectF(QPointF(bbox[0], m_doc->currentPage()->height() - bbox[1]), QPointF(bbox[2], m_doc->currentPage()->height() - bbox[3]));
+	// Remember the mask's position as it might not align with the image to which the mask is later assigned.
+	m_groupStack.top().maskPos = m_currentMaskPosition;
 	m_groupStack.top().alpha = alpha;
 	if (m_groupStack.top().Items.count() != 0)
 		applyMask(m_groupStack.top().Items.last());
@@ -1585,7 +1625,7 @@
 		// this information.
 		QPainterPath pathN = out.toQPainterPath(false);
 		pathN.setFillRule(fillRule);
-		m_currentClipPath = pathN.intersected(m_currentClipPath);
+		m_currentClipPath = intersection(pathN, m_currentClipPath);
 	}
 	else
 		m_currentClipPath = out.toQPainterPath(false);
@@ -1699,19 +1739,25 @@
 {
 	const double *ctm;
 	ctm = state->getCTM();
+	m_ctm = QTransform(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]);
 	double xCoor = m_doc->currentPage()->xOffset();
 	double yCoor = m_doc->currentPage()->yOffset();
 	FPointArray out;
 	QString output = convertPath(state->getPath());
 	out.parseSVG(output);
-	m_ctm = QTransform(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]);
 	out.map(m_ctm);
 
 	// Clip the new path first and only add it if it is not empty.
 	QPainterPath path = out.toQPainterPath(false);
 	path.setFillRule(fillRule);
-	QPainterPath clippedPath = path.intersected(m_currentClipPath);
+	QPainterPath clippedPath = intersection(m_currentClipPath, path);
 
+	// Undo the rotation of the clipping path as it is rotated together with the item.
+	double angle = m_ctm.map(QLineF(0, 0, 1, 0)).angle();
+	QTransform mm;
+	mm.rotate(angle);
+	clippedPath = mm.map(clippedPath);
+
 	Coords = output;
 	QRectF bbox = clippedPath.boundingRect();
 	if (!clippedPath.isEmpty() && !bbox.isNull())
@@ -1728,6 +1774,7 @@
 		ite->FrameType = 3;
 		ite->setFillShade(CurrFillShade);
 		ite->setLineShade(100);
+		ite->setRotation(-angle);
 		// Only the new path has to be interpreted according to fillRule. QPainterPath
 		// could decide to create a final path according to the other rule. Thus
 		// we have to set this from the final path.
@@ -2242,7 +2289,11 @@
 
 	gfx = new Gfx(pdfDoc, this, resDict, &box, nullptr);
 	inPattern++;
+	// Unset the clip path as it is unrelated to the pattern's coordinate space.
+	QPainterPath savedClip = m_currentClipPath;
+	m_currentClipPath = QPainterPath();
 	gfx->display(str);
+	m_currentClipPath = savedClip;
 	inPattern--;
 	gElements = m_groupStack.pop();
 	m_doc->m_Selection->clear();
@@ -2300,13 +2351,20 @@
 	Coords = output;
 	int z = m_doc->itemAdd(PageItem::Polygon, PageItem::Rectangle, xCoor + crect.x(), yCoor + crect.y(), crect.width(), crect.height(), 0, CurrColorFill, CommonStrings::None);
 	ite = m_doc->Items->at(z);
+
+	m_ctm = QTransform(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]);
+	double angle = m_ctm.map(QLineF(0, 0, 1, 0)).angle();
+	ite->setRotation(-angle);
 	if (checkClip())
 	{
-		QPainterPath out = m_currentClipPath;
-		out.translate(m_doc->currentPage()->xOffset(), m_doc->currentPage()->yOffset());
-		out.translate(-ite->xPos(), -ite->yPos());
-		ite->PoLine.fromQPainterPath(out, true);
-		ite->setFillEvenOdd(out.fillRule() == Qt::OddEvenFill);
+		QPainterPath outline = m_currentClipPath;
+		outline.translate(xCoor - ite->xPos(), yCoor - ite->yPos());
+		// Undo the rotation of the clipping path as it is rotated together with the item.
+		QTransform mm;
+		mm.rotate(angle);
+		outline = mm.map(outline);
+		ite->PoLine.fromQPainterPath(outline, true);
+		ite->setFillEvenOdd(outline.fillRule() == Qt::OddEvenFill);
 	}
 	ite->ClipEdited = true;
 	ite->FrameType = 3;
@@ -2621,6 +2679,7 @@
 
 void SlaOutputDev::createImageFrame(QImage& image, GfxState *state, int numColorComponents)
 {
+//	qDebug() << "SlaOutputDev::createImageFrame";
 	const double *ctm = state->getCTM();
 	double xCoor = m_doc->currentPage()->xOffset();
 	double yCoor = m_doc->currentPage()->yOffset();
@@ -2647,8 +2706,7 @@
 	QPainterPath outline;
 	outline.addRect(0, 0, 1, 1);
 	outline = m_ctm.map(outline);
-	if (!m_currentClipPath.isEmpty())
-		outline = m_currentClipPath.intersected(outline);
+	outline = intersection(outline, m_currentClipPath);
 
 	if ((inPattern == 0) && (outline.isEmpty() || outline.boundingRect().isNull()))
 		return;
@@ -3627,10 +3685,10 @@
 		if (!m_groupStack.top().maskName.isEmpty())
 		{
 			ite->setPatternMask(m_groupStack.top().maskName);
-			QRectF bbox = m_groupStack.top().maskBBox; 
+			QPointF maskPos = m_groupStack.top().maskPos;
 			double sx, sy, px, py, r, shx, shy;
 			ite->maskTransform(sx, sy, px, py, r, shx, shy);
-			ite->setMaskTransform(sx, sy, bbox.x() - (ite->xPos() - m_doc->currentPage()->xOffset()) , bbox.y() - (ite->yPos() - m_doc->currentPage()->yOffset()) , r, shx, shy);
+			ite->setMaskTransform(sx, sy, maskPos.x() - ite->xPos(), maskPos.y() - ite->yPos(), r, shx, shy);
 			if (m_groupStack.top().alpha)
 			{
 				if (m_groupStack.top().inverted)

jghali

2020-03-31 14:53

administrator   ~0047488

Applied, thanks!

Issue History

Date Modified Username Field Change
2020-03-29 21:30 Pontobart New Issue
2020-03-29 21:30 Pontobart File Added: rotate_path_and_tiling.diff
2020-03-29 22:40 jghali Summary Rotate importet path's and tiling patterns => Rotate imported path's and tiling patterns
2020-03-29 22:40 jghali Description Updated View Revisions
2020-03-31 14:53 jghali Assigned To => jghali
2020-03-31 14:53 jghali Status new => resolved
2020-03-31 14:53 jghali Resolution open => fixed
2020-03-31 14:53 jghali Fixed in Version => 1.5.6.svn
2020-03-31 14:53 jghali Note Added: 0047488
2020-04-05 19:37 cbradney Status resolved => closed