View Issue Details

IDProjectCategoryView StatusLast Update
0017438ScribusImport / Exportpublic2025-05-17 07:33
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

Relationships

related to 0003031 new Add an option to export a selection as bitmap, EPS, or SVG 

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)

ale

2025-03-27 08:45

manager   ~0052347

any feedback?

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
2025-03-27 08:45 ale Note Added: 0052347
2025-05-17 07:33 ale Relationship added related to 0003031