View Issue Details

IDProjectCategoryView StatusLast Update
0017438ScribusImport / Exportpublic2025-03-06 19:13
Reporterale Assigned To 
PrioritynormalSeverityminorReproducibilityN/A
Status newResolutionopen 
Product Version1.7.1.svn 
Summary0017438: [PATCH] Export selection
DescriptionFor the export that matter (SVG, image, EPS) it should be possible to optionally export the current selection only.
TagsNo tags attached.
PatchYes

Activities

ale

2025-03-06 19:07

manager   ~0052165

Here is a patch for exporting the selection to SVG

- The option is only active, if there is a selection and all its items are on the same page (it might be possible to relax the second condition)
- It creates SVG layers for each Scribus layer in the selection (if items from other layers can be selected)
- I've refactored a little bit of code that is used by both the export of the whole page and the one for the selection
export-selection-to-svg.diff (8,198 bytes)   
From f035fd4e2fdfaff942a8bb2869b877e859c6a3d7 Mon Sep 17 00:00:00 2001
From: ale rimoldi <ale@graphicslab.org>
Date: Thu, 6 Mar 2025 20:03:38 +0100
Subject: add an option to save the current selection to SVG


diff --git a/scribus/plugins/export/svgexplugin/svgexplugin.cpp b/scribus/plugins/export/svgexplugin/svgexplugin.cpp
index 3d515db63..359d1be3a 100644
--- a/scribus/plugins/export/svgexplugin/svgexplugin.cpp
+++ b/scribus/plugins/export/svgexplugin/svgexplugin.cpp
@@ -41,6 +41,7 @@ for which a new license (GPL+exception) is in place.
 #include "prefsfile.h"
 #include "prefscontext.h"
 #include "qtiocompressor.h"
+#include "selection.h"
 #include "scpage.h"
 #include "scpattern.h"
 #include "scribus.h"
@@ -134,15 +135,26 @@ bool SVGExportPlugin::run(ScribusDoc* doc, const QString& filename)
 		openDia->setSelection(getFileNameByPage(doc, doc->currentPage()->pageNr(), "svg"));
 		openDia->setExtension("svg");
 		openDia->setZipExtension("svgz");
+
 		QCheckBox* compress = new QCheckBox(openDia.data());
 		compress->setText( tr("Compress File"));
 		compress->setChecked(false);
 		openDia->addWidgets(compress);
+
 		QCheckBox* inlineImages = new QCheckBox(openDia.data());
 		inlineImages->setText( tr("Save Images inline"));
 		inlineImages->setToolTip( tr("Adds all Images on the Page inline to the SVG.\nCaution: this will increase the file size!"));
 		inlineImages->setChecked(true);
 		openDia->addWidgets(inlineImages);
+
+		// TODO: only if there is a selection
+		QCheckBox* selectionOnly = new QCheckBox(openDia.data());
+		selectionOnly->setText( tr("Current selection only"));
+		selectionOnly->setToolTip( tr("Adds the items in the current selection to the SVG.\nAll items shall be on the same page."));
+		selectionOnly->setEnabled(!doc->m_Selection->isEmpty() && doc->m_Selection->itemsAreOnSamePage());
+		selectionOnly->setChecked(selectionOnly->isEnabled());
+		openDia->addWidgets(selectionOnly);
+
 		QCheckBox* exportBack = new QCheckBox(openDia.data());
 		exportBack->setText( tr("Export Page background"));
 		exportBack->setToolTip( tr("Adds the Page itself as background to the SVG"));
@@ -161,6 +173,7 @@ bool SVGExportPlugin::run(ScribusDoc* doc, const QString& filename)
 
 		SVGOptions Options;
 		Options.inlineImages = inlineImages->isChecked();
+		Options.selectionOnly = selectionOnly->isChecked();
 		Options.exportPageBackground = exportBack->isChecked();
 		Options.compressFile = compress->isChecked();
 
@@ -188,6 +201,7 @@ SVGExPlug::SVGExPlug( ScribusDoc* doc )
 {
 	m_Doc = doc;
 	Options.inlineImages = true;
+	Options.selectionOnly = false;
 	Options.exportPageBackground = false;
 	Options.compressFile = false;
 	m_glyphNames.clear();
@@ -211,12 +225,12 @@ bool SVGExPlug::doExport( const QString& fName, SVGOptions &Opts )
 	m_domDoc.setContent(st);
 
 	ScPage *page = m_Doc->currentPage();
-	double pageWidth  = page->width();
-	double pageHeight = page->height();
+	double svgWidth  = Options.selectionOnly ? m_Doc->m_Selection->width() : page->width();
+	double svgHeight = Options.selectionOnly ? m_Doc->m_Selection->height() : page->height();
 	m_domElem = m_domDoc.documentElement();
-	m_domElem.setAttribute("width", FToStr(pageWidth) + "pt");
-	m_domElem.setAttribute("height", FToStr(pageHeight) + "pt");
-	m_domElem.setAttribute("viewBox", QString("0 0 %1 %2").arg(pageWidth).arg(pageHeight));
+	m_domElem.setAttribute("width", FToStr(svgWidth) + "pt");
+	m_domElem.setAttribute("height", FToStr(svgHeight) + "pt");
+	m_domElem.setAttribute("viewBox", QString("0 0 %1 %2").arg(svgWidth).arg(svgHeight));
 	m_domElem.setAttribute("xmlns", "http://www.w3.org/2000/svg");
 	m_domElem.setAttribute("xmlns:inkscape","http://www.inkscape.org/namespaces/inkscape");
 	m_domElem.setAttribute("xmlns:xlink","http://www.w3.org/1999/xlink");
@@ -239,29 +253,27 @@ bool SVGExPlug::doExport( const QString& fName, SVGOptions &Opts )
 	writeBasePatterns();
 	writeBaseSymbols();
 	m_domElem.appendChild(m_globalDefs);
+
 	if (Options.exportPageBackground)
 	{
 		QDomElement backG = m_domDoc.createElement("rect");
 		backG.setAttribute("x", "0");
 		backG.setAttribute("y", "0");
-		backG.setAttribute("width", FToStr(pageWidth));
-		backG.setAttribute("height", FToStr(pageHeight));
+		backG.setAttribute("width", FToStr(svgWidth));
+		backG.setAttribute("height", FToStr(svgHeight));
 		backG.setAttribute("style", "fill:" + m_Doc->paperColor().name() + ";" + "stroke:none;");
 		m_domElem.appendChild(backG);
 	}
-	ScLayer ll;
-	ll.isPrintable = false;
-	for (int la = 0; la < m_Doc->Layers.count(); la++)
+
+	if (Options.selectionOnly)
 	{
-		m_Doc->Layers.levelToLayer(ll, la);
-		if (ll.isPrintable)
-		{
-			page = m_Doc->MasterPages.at(m_Doc->MasterNames[m_Doc->currentPage()->masterPageName()]);
-			processPageLayer(page, ll);
-			page = m_Doc->currentPage();
-			processPageLayer(page, ll);
-		}
+		processSelection();
 	}
+	else
+	{
+		processPage(page);
+	}
+
 	if(Options.compressFile)
 	{
 		// zipped saving
@@ -290,9 +302,61 @@ bool SVGExPlug::doExport( const QString& fName, SVGOptions &Opts )
 	return true;
 }
 
+
+QDomElement SVGExPlug::createSvgLayer(const ScLayer& layer)
+{
+	QDomElement svgLayer = m_domDoc.createElement("g");
+	svgLayer.setAttribute("id", layer.Name);
+	svgLayer.setAttribute("inkscape:label", layer.Name);
+	svgLayer.setAttribute("inkscape:groupmode", "layer");
+	if (layer.transparency != 1.0)
+		svgLayer.setAttribute("opacity", FToStr(layer.transparency));
+	return svgLayer;
+};
+
+void SVGExPlug::processSelection()
+{
+	double sel_x0, sel_y0, sel_w, sel_h;
+	m_Doc->m_Selection->getGroupRect(&sel_x0, &sel_y0, &sel_w, &sel_h);
+
+	QMap<QString, QDomElement> svgLayers;
+
+	for (const auto item: m_Doc->m_Selection->items())
+	{
+		if (!item->printEnabled())
+			continue;
+		auto layer = *m_Doc->Layers.byID(item->m_layerID);
+		if (!svgLayers.contains(layer.Name))
+		{
+			svgLayers[layer.Name] = createSvgLayer(layer);
+		}
+		processItemOnPage(item->xPos() - sel_x0, item->yPos() - sel_y0, item, &svgLayers[layer.Name]);
+	}
+	for (auto [lanyerName, svgLayer] : svgLayers.asKeyValueRange())
+	{
+		m_domElem.appendChild(svgLayer);
+	}
+};
+
+void SVGExPlug::processPage(ScPage *page)
+{
+	ScLayer ll;
+	ll.isPrintable = false;
+	for (int la = 0; la < m_Doc->Layers.count(); la++)
+	{
+		m_Doc->Layers.levelToLayer(ll, la);
+		if (ll.isPrintable)
+		{
+			page = m_Doc->MasterPages.at(m_Doc->MasterNames[m_Doc->currentPage()->masterPageName()]);
+			processPageLayer(page, ll);
+			page = m_Doc->currentPage();
+			processPageLayer(page, ll);
+		}
+	}
+}
+
 void SVGExPlug::processPageLayer(ScPage *page, ScLayer& layer)
 {
-	QDomElement layerGroup;
 	PageItem *item;
 	QList<PageItem*> items;
 	ScPage* SavedAct = m_Doc->currentPage();
@@ -306,12 +370,8 @@ void SVGExPlug::processPageLayer(ScPage *page, ScLayer& layer)
 		return;
 	m_Doc->setCurrentPage(page);
 
-	layerGroup = m_domDoc.createElement("g");
-	layerGroup.setAttribute("id", layer.Name);
-	layerGroup.setAttribute("inkscape:label", layer.Name);
-	layerGroup.setAttribute("inkscape:groupmode", "layer");
-	if (layer.transparency != 1.0)
-		layerGroup.setAttribute("opacity", FToStr(layer.transparency));
+	QDomElement layerGroup = createSvgLayer(layer);
+
 	for (int j = 0; j < items.count(); ++j)
 	{
 		item = items.at(j);
diff --git a/scribus/plugins/export/svgexplugin/svgexplugin.h b/scribus/plugins/export/svgexplugin/svgexplugin.h
index d2517f32f..f45bc58d9 100644
--- a/scribus/plugins/export/svgexplugin/svgexplugin.h
+++ b/scribus/plugins/export/svgexplugin/svgexplugin.h
@@ -24,6 +24,7 @@ class ScText;
 struct SVGOptions
 {
 	bool inlineImages;
+	bool selectionOnly;
 	bool exportPageBackground;
 	bool compressFile;
 };
@@ -87,6 +88,19 @@ private:
 	QDomElement m_globalDefs;
 	QList<QString> m_glyphNames;
 
+
+	/**
+	 * Creates an SVG layer, based on the information in the Scribus layer.
+	 */
+	QDomElement createSvgLayer(const ScLayer& layer);
+	/**
+	 * Add all items in the selection to the SVG.
+	 */
+	void processSelection();
+	/**
+	 * Add all items on the page to the SVG.
+	 */
+	void processPage(ScPage *page);
 	/*!
 	\author Franz Schmid
 	\brief Process a page to export to SVG format
export-selection-to-svg.diff (8,198 bytes)   

ale

2025-03-06 19:13

manager   ~0052167

Further work to be done once the patch is accepted:

- add the option to the scripter
- think if it's useful to add the same option to the export to bitmaps (it's probably ok if it's easy; otherwise a screenshot or an export from Inkscape can be enough)
- check if the same code can be used to copy a selection from scribu and paste it in inkscape (does it need a new command "copy as SVG" or can we simply create create SVG when Scribus puts multiple items in an "external" clipboard? ... I must admit that I have no clue about how copy pasting between applications works)

Issue History

Date Modified Username Field Change
2025-03-06 07:25 ale New Issue
2025-03-06 19:07 ale Note Added: 0052165
2025-03-06 19:07 ale File Added: export-selection-to-svg.diff
2025-03-06 19:09 ale Summary Export selection => [PATCH] Export selection
2025-03-06 19:09 ale Patch No => Yes
2025-03-06 19:13 ale Note Added: 0052167