View Issue Details

IDProjectCategoryView StatusLast Update
0017986ScribusMaster Pagespublic2026-10-02 08:47
Reporterbuttar Assigned To 
PrioritynormalSeverityfeatureReproducibilityN/A
Status newResolutionopen 
PlatformLinuxOSFedora WorkstationOS Version44
Product Version1.7.4.svn 
Summary0017986: [patch] Add True Spread Master Pages
DescriptionThis very useful feature has been asked for a lot in the past. All industry giants have this feature. Here is my much tested attempt on the same; a series of 6 small patches. You can build and test after each patch if you like (or the whole series at once).

The TSM (true master spread) series depends on `mid-foundation-v1.00.patch` (from 0017984), which provides the persistent master-page ID and ID-based assignment foundation used to represent, track, rename, and apply logical master spreads safely.

## `tsm1-master-model-persistence-v1.01.patch`
- Extends the master model with `MasterPageSide` (`Single`, `Left`, `Right`).
- Allows two physical master pages to share one master ID, representing a logical facing spread.
- Adds `masterPageIndices()`, side-aware `masterPageIndex()`, `masterPageHasSide()` and `isMasterSpread()`.
- Persists master/applied side information in SLA and undo state.

## `tsm2-spread-editor-v1.16.patch`
- Adds creation, duplication, editing and deletion support for logical two-page master spreads.
- `ScribusDoc::addMasterSpread()` creates the paired Left/Right master pages under one ID.
- Makes master-edit canvas/view handling spread-aware.
- New Master UI exposes the valid facing choices directly: Spread, Single Left, Single Right; Duplicate Master follows the applicable subset.

## `tsm3-application-reflow-v1.05.patch`
- Adds side-aware master application and document-spread targeting.
- `documentSpreadPages()` resolves the physical left/right document pages; `applyMasterSpread()` applies both master halves as one undoable operation.
- `matchingMasterPageSide()` and `reconcileMasterPageSides()` keep assignments valid across page movement and layout changes.
- Preserves appropriate master margins during page-layout changes.

## `tsm4-cross-spread-rendering-v1.01.patch`
- Makes on-canvas master rendering aware of both halves of an applied spread.
- `masterItemsForAppliedPage()` resolves the master items relevant to each document page.
- Allows artwork to cross the master spine while clipping its visible result to the appropriate physical page.

## `tsm5-output-projection-v1.06.patch`
- Extends true-spread projection to PDF, PostScript/EPS, SVG, XPS and `ScPageOutput`.
- `masterItemsForMasterPage()` provides the logical master item set used by output backends.
- PDF/PS intersection and bleed handling project cross-spine artwork into each physical page's output/bleed region without exposing the opposite page.
- SVG/XPS similarly clip each exported physical page to its own projection.

## `tsm6-default-master-lifecycle-normal-spread-v1.04.patch`
- Adds the built-in Normal Spread master for two-column facing documents.
- Centralizes built-in master roles/names and protection in `ScribusDoc`.
- `ensureDefaultMasterPages()`, `reconcileDefaultMasterPages()` and `reconcileMasterPages()` keep required defaults and page assignments consistent after interactive layout changes.
- Lifecycle handling is deliberately non-destructive: layout changes add/reconcile required defaults rather than silently deleting preserved masters.
Tags#please_test
Attached Files
tsm1-master-model-persistence-v1.01.patch (20,959 bytes)   
Index: scribus/plugins/fileloader/scribus171format/scribus171format.cpp
===================================================================
--- scribus/plugins/fileloader/scribus171format/scribus171format.cpp	(revision 27864)
+++ scribus/plugins/fileloader/scribus171format/scribus171format.cpp	(working copy)
@@ -4815,13 +4815,15 @@ bool Scribus171Format::readPage(ScribusDoc* doc, ScXmlStreamReader& reader)
 	bool savedMasterPageMode = m_Doc->masterPageMode();
 	m_Doc->setMasterPageMode(!pageName.isEmpty());
 	const QUuid storedMasterId(attrs.valueAsString("MasterPageId"));
-	ScPage* newPage = pageName.isEmpty() ? doc->addPage(pageNum) : doc->addMasterPage(pageNum, pageName, storedMasterId);
+	const MasterPageSide masterSide = masterPageSideFromInt(attrs.valueAsInt("MasterPageSide", static_cast<int>(MasterPageSide::Single)));
+	ScPage* newPage = pageName.isEmpty() ? doc->addPage(pageNum) : doc->addMasterPage(pageNum, pageName, storedMasterId, masterSide);
 
 	//Remove uppercase in 1.8
 	if (attrs.hasAttribute("LEFT"))
 		newPage->LeftPg = attrs.valueAsInt("LEFT", 0);
 	else
 		newPage->LeftPg = attrs.valueAsInt("LeftPage", 0);
+	newPage->setMasterPageSide(masterSide);
 	//Remove uppercase in 1.8
 	QString mpName;
 	if (attrs.hasAttribute("MNAM"))
@@ -4835,6 +4837,7 @@ bool Scribus171Format::readPage(ScribusDoc* doc, ScXmlStreamReader& reader)
 		if (appliedId.isNull())
 			appliedId = doc->masterPageId(mpName);
 		newPage->setAppliedMasterPageId(appliedId);
+		newPage->setAppliedMasterPageSide(masterPageSideFromInt(attrs.valueAsInt("AppliedMasterPageSide", static_cast<int>(MasterPageSide::Single))));
 	}
 	if (attrs.hasAttribute("Size"))
 		newPage->setSize(attrs.valueAsString("Size"));
@@ -8363,11 +8366,13 @@ bool Scribus171Format::loadPage(const QString & fileName, int pageNumber, bool M
 			}
 			if (Mpage)
 			{
+				const MasterPageSide masterSide = masterPageSideFromInt(attrs.valueAsInt("MasterPageSide", static_cast<int>(MasterPageSide::Single)));
 				//Remove uppercase in 1.8
 				if (attrs.hasAttribute("LEFT"))
 					newPage->LeftPg = attrs.valueAsInt("LEFT", 0);
 				else
 					newPage->LeftPg = attrs.valueAsInt("LeftPage", 0);
+				newPage->setMasterPageSide(masterSide);
 				if (!renamedPageName.isEmpty())
 					newPage->setPageName(renamedPageName);
 				else
Index: scribus/plugins/fileloader/scribus171format/scribus171format_save.cpp
===================================================================
--- scribus/plugins/fileloader/scribus171format/scribus171format_save.cpp	(revision 27864)
+++ scribus/plugins/fileloader/scribus171format/scribus171format_save.cpp	(working copy)
@@ -1797,13 +1797,24 @@ void Scribus171Format::WritePages(ScribusDoc *doc, ScXmlStreamWriter& docu, QPro
 		docu.writeAttribute("PageName",page->pageName());
 		docu.writeAttribute("MasterPageName",page->masterPageName());
 		if (!page->masterPageId().isNull())
+		{
 			docu.writeAttribute("MasterPageId", page->masterPageId().toString(QUuid::WithoutBraces));
+			docu.writeAttribute("MasterPageSide", static_cast<int>(page->masterPageSide()));
+		}
 		if (!page->appliedMasterPageId().isNull())
+		{
 			docu.writeAttribute("AppliedMasterPageId", page->appliedMasterPageId().toString(QUuid::WithoutBraces));
+			docu.writeAttribute("AppliedMasterPageSide", static_cast<int>(page->appliedMasterPageSide()));
+		}
 		PageSize ps(page->width(), page->height());
 		docu.writeAttribute("Size", ps.name()); // legacy for Scribus < 1.7.4
 		docu.writeAttribute("Orientation", page->orientation());
-		docu.writeAttribute("LeftPage", page->LeftPg);
+		int legacyLeftPage = page->LeftPg;
+		if (page->masterPageSide() == MasterPageSide::Left)
+			legacyLeftPage = 1;
+		else if (page->masterPageSide() == MasterPageSide::Right)
+			legacyLeftPage = 0;
+		docu.writeAttribute("LeftPage", legacyLeftPage);
 		docu.writeAttribute("Preset", page->marginPreset);
 		docu.writeAttribute("VerticalGuides", GuideManagerIO::writeVerticalGuides(page, GuideManagerCore::Standard));
 		docu.writeAttribute("HorizontalGuides", GuideManagerIO::writeHorizontalGuides(page, GuideManagerCore::Standard));
Index: scribus/scpage.cpp
===================================================================
--- scribus/scpage.cpp	(revision 27864)
+++ scribus/scpage.cpp	(working copy)
@@ -135,6 +135,7 @@ QString ScPage::masterPageName() const
 void ScPage::setMasterPageName(const QString& newName)
 {
 	m_masterPageName = newName;
+	m_appliedMasterPageSide = MasterPageSide::Single;
 	if (newName.isEmpty())
 		m_appliedMasterPageId = QUuid();
 	else if (m_Doc)
@@ -154,6 +155,7 @@ void ScPage::clearMasterPageName()
 {
 	m_masterPageName.clear();
 	m_appliedMasterPageId = QUuid();
+	m_appliedMasterPageSide = MasterPageSide::Single;
 }
 
 void ScPage::setSize(const QString& newSize)
Index: scribus/scpage.h
===================================================================
--- scribus/scpage.h	(revision 27864)
+++ scribus/scpage.h	(working copy)
@@ -40,6 +40,27 @@ for which a new license (GPL+exception) is in place.
 
 class PageItem;
 class ScribusDoc;
+
+enum class MasterPageSide : int
+{
+	Single = 0,
+	Left,
+	Right
+};
+
+inline MasterPageSide masterPageSideFromInt(int value)
+{
+	switch (value)
+	{
+		case static_cast<int>(MasterPageSide::Left):
+			return MasterPageSide::Left;
+		case static_cast<int>(MasterPageSide::Right):
+			return MasterPageSide::Right;
+		default:
+			return MasterPageSide::Single;
+	}
+}
+
 class UndoManager;
 class UndoState;
 
@@ -95,8 +116,19 @@ public:
 	void setMasterPageName(const QString& newName);
 	const QUuid& masterPageId() const { return m_masterPageId; }
 	void setMasterPageId(const QUuid& id) { m_masterPageId = id; }
+	MasterPageSide masterPageSide() const { return m_masterPageSide; }
+	void setMasterPageSide(MasterPageSide side)
+	{
+		m_masterPageSide = side;
+		if (side == MasterPageSide::Left)
+			LeftPg = 1;
+		else if (side == MasterPageSide::Right)
+			LeftPg = 0;
+	}
 	const QUuid& appliedMasterPageId() const { return m_appliedMasterPageId; }
 	void setAppliedMasterPageId(const QUuid& id) { m_appliedMasterPageId = id; }
+	MasterPageSide appliedMasterPageSide() const { return m_appliedMasterPageSide; }
+	void setAppliedMasterPageSide(MasterPageSide side) { m_appliedMasterPageSide = side; }
 	void setMasterPageNameNormal();
 	void clearMasterPageName();
 	const QString& size() const {return m_pageSize;}
@@ -136,7 +168,9 @@ protected:
 	QString m_pageName;
 	QString m_masterPageName;
 	QUuid m_masterPageId;
+	MasterPageSide m_masterPageSide { MasterPageSide::Single };
 	QUuid m_appliedMasterPageId;
+	MasterPageSide m_appliedMasterPageSide { MasterPageSide::Single };
 	QString m_pageSize;
 	QString m_pageSectionNumber;
 	ScribusDoc* m_Doc {nullptr};
Index: scribus/scribus.cpp
===================================================================
--- scribus/scribus.cpp	(revision 27864)
+++ scribus/scribus.cpp	(working copy)
@@ -6168,7 +6168,9 @@ void ScribusMainWindow::deletePage(int from, int to)
 			ss->set("PAGENAME",   doc->Pages->at(a)->pageName());
 			ss->set("MASTERPAGE", doc->Pages->at(a)->masterPageName());
 			ss->set("MASTER_PAGE_ID", doc->Pages->at(a)->masterPageId().toString(QUuid::WithoutBraces));
+			ss->set("MASTER_PAGE_SIDE", static_cast<int>(doc->Pages->at(a)->masterPageSide()));
 			ss->set("APPLIED_MASTER_PAGE_ID", doc->Pages->at(a)->appliedMasterPageId().toString(QUuid::WithoutBraces));
+			ss->set("APPLIED_MASTER_PAGE_SIDE", static_cast<int>(doc->Pages->at(a)->appliedMasterPageSide()));
 			ss->set("MASTER_PAGE_MODE",  doc->masterPageMode());
 			// replace the deleted page in the undostack by a dummy object that will
 			// replaced with the "undone" page if user choose to undo the action
@@ -7963,6 +7965,8 @@ void ScribusMainWindow::restoreDeletePage(SimpleState *state, bool isUndo)
 			const QUuid restoredId(state->get("MASTER_PAGE_ID"));
 			if (!restoredId.isNull())
 				doc->Pages->at(pagenr - 1)->setMasterPageId(restoredId);
+			if (state->contains("MASTER_PAGE_SIDE"))
+				doc->Pages->at(pagenr - 1)->setMasterPageSide(static_cast<MasterPageSide>(state->getInt("MASTER_PAGE_SIDE")));
 		}
 		else
 		{
@@ -7970,6 +7974,8 @@ void ScribusMainWindow::restoreDeletePage(SimpleState *state, bool isUndo)
 			const QUuid restoredId(state->get("APPLIED_MASTER_PAGE_ID"));
 			if (!restoredId.isNull())
 				doc->Pages->at(pagenr - 1)->setAppliedMasterPageId(restoredId);
+			if (state->contains("APPLIED_MASTER_PAGE_SIDE"))
+				doc->Pages->at(pagenr - 1)->setAppliedMasterPageSide(static_cast<MasterPageSide>(state->getInt("APPLIED_MASTER_PAGE_SIDE")));
 		}
 		UndoObject *tmp =
 			m_undoManager->replaceObject(state->getUInt("DUMMY_ID"), doc->Pages->at(pagenr - 1));
Index: scribus/scribusdoc.cpp
===================================================================
--- scribus/scribusdoc.cpp	(revision 27864)
+++ scribus/scribusdoc.cpp	(working copy)
@@ -2061,22 +2061,28 @@ void ScribusDoc::restoreAddMasterPage(SimpleState* ss, bool isUndo)
 	ScPage* oldPage = currentPage();
 
 	setMasterPageMode(true);
+	const QUuid masterId(ss->get("MASTERPAGE_ID"));
+	const MasterPageSide side = ss->contains("MASTERPAGE_SIDE") ? static_cast<MasterPageSide>(ss->getInt("MASTERPAGE_SIDE")) : MasterPageSide::Single;
 	if (isUndo)
 	{
-		auto *duo = new DummyUndoObject();
-		uint did = static_cast<uint>(duo->getUId());
-		m_undoManager->replaceObject(Pages->at(MasterNames[pageName])->getUId(), duo);
-		ss->set("DUMMY_ID", did);
+		const int masterIndex = masterId.isNull() ? MasterNames.value(pageName, -1) : masterPageIndex(masterId, side);
+		if (masterIndex >= 0)
+		{
+			auto *duo = new DummyUndoObject();
+			uint did = static_cast<uint>(duo->getUId());
+			m_undoManager->replaceObject(Pages->at(masterIndex)->getUId(), duo);
+			ss->set("DUMMY_ID", did);
 
-		scMW()->deletePage2(MasterNames[pageName]);
-		rebuildMasterNames();
-	} 
-	else 
+			scMW()->deletePage2(masterIndex);
+			rebuildMasterNames();
+		}
+	}
+	else
 	{
-		ScPage* Mpage = addMasterPage(pageNr, pageName, QUuid(ss->get("MASTERPAGE_ID")));
+		ScPage* Mpage = addMasterPage(pageNr, pageName, masterId, side);
 		setCurrentPage(Mpage);
 		UndoObject *tmp = m_undoManager->replaceObject(
-					ss->getUInt("DUMMY_ID"), Pages->at(MasterNames[pageName]));
+					ss->getUInt("DUMMY_ID"), Mpage);
 		delete tmp;
 	}
 	setMasterPageMode(oldMPMode);
@@ -2745,7 +2751,7 @@ ScPage* ScribusDoc::addPage(int pageNumber, const QString& masterPageName, bool
 }
 
 
-ScPage* ScribusDoc::addMasterPage(int pageNumber, const QString& pageName, const QUuid& masterId)
+ScPage* ScribusDoc::addMasterPage(int pageNumber, const QString& pageName, const QUuid& masterId, MasterPageSide side)
 {
 	ScPage* addedPage = new ScPage(m_docPrefsData.displayPrefs.scratch.left(), m_docPrefsData.displayPrefs.scratch.top(), m_docPrefsData.docSetupPrefs.pageWidth, m_docPrefsData.docSetupPrefs.pageHeight);
 	assert(addedPage != nullptr);
@@ -2762,9 +2768,16 @@ ScPage* ScribusDoc::addMasterPage(int pageNumber, const QString& pageName, const
 	addedPage->setPageName(pageName);
 	addedPage->setPageNr(pgN);
 	QUuid id = masterId;
-	if (id.isNull() || MasterPageIds.contains(id))
+	if (id.isNull())
 		id = QUuid::createUuid();
+	else if (MasterPageIds.contains(id))
+	{
+		const bool sameLogicalMaster = (masterPageName(id) == pageName);
+		if (!sameLogicalMaster || masterPageHasSide(id, side))
+			id = QUuid::createUuid();
+	}
 	addedPage->setMasterPageId(id);
+	addedPage->setMasterPageSide(side);
 	MasterNames.insert(pageName, pgN);
 	MasterPages.insert(pgN, addedPage);
 	assert(MasterPages.at(pgN) != nullptr);
@@ -2781,6 +2794,7 @@ ScPage* ScribusDoc::addMasterPage(int pageNumber, const QString& pageName, const
 		ss->set("MASTERPAGE_NAME", pageName);
 		ss->set("MASTERPAGE_NBR", pgN);
 		ss->set("MASTERPAGE_ID", id.toString(QUuid::WithoutBraces));
+		ss->set("MASTERPAGE_SIDE", static_cast<int>(side));
 		m_undoManager->action(this, ss);
 	}
 	return addedPage;
@@ -2796,16 +2810,18 @@ bool ScribusDoc::renameMasterPage(const QString& oldPageName, const QString& new
 
 bool ScribusDoc::renameMasterPage(const QUuid& masterId, const QString& newPageName)
 {
-	const int number = masterPageIndex(masterId);
-	if (number < 0 || MasterNames.contains(newPageName))
+	const QList<int> indices = masterPageIndices(masterId);
+	if (indices.isEmpty())
+		return false;
+	const QString oldPageName = MasterPages.at(indices.first())->pageName();
+	if (oldPageName != newPageName && MasterNames.contains(newPageName))
 		return false;
 
-	const QString oldPageName = MasterPages.at(number)->pageName();
 	Q_ASSERT(oldPageName != CommonStrings::masterPageNormal && oldPageName != CommonStrings::trMasterPageNormal);
 
-	MasterNames.remove(oldPageName);
-	MasterNames.insert(newPageName, number);
-	MasterPages.at(number)->setPageName(newPageName);
+	for (int index : indices)
+		MasterPages.at(index)->setPageName(newPageName);
+	rebuildMasterNames();
 
 	// Master item names remain as a compatibility mirror for legacy code and files.
 	for (PageItem* item : std::as_const(MasterItems))
@@ -2859,10 +2875,35 @@ void ScribusDoc::rebuildMasterNames()
 	for (int i = 0; i < MasterPages.count(); ++i)
 	{
 		ScPage* master = MasterPages.at(i);
-		if (master->masterPageId().isNull() || MasterPageIds.contains(master->masterPageId()))
-			master->setMasterPageId(QUuid::createUuid());
-		MasterNames[master->pageName()] = i;
-		MasterPageIds[master->masterPageId()] = i;
+		QUuid id = master->masterPageId();
+		if (id.isNull())
+		{
+			id = QUuid::createUuid();
+			master->setMasterPageId(id);
+		}
+
+		QList<int>& indices = MasterPageIds[id];
+		bool invalidSharedId = !indices.isEmpty() && MasterPages.at(indices.first())->pageName() != master->pageName();
+		for (int existingIndex : std::as_const(indices))
+		{
+			if (MasterPages.at(existingIndex)->masterPageSide() == master->masterPageSide())
+			{
+				invalidSharedId = true;
+				break;
+			}
+		}
+		if (invalidSharedId)
+		{
+			id = QUuid::createUuid();
+			master->setMasterPageId(id);
+			MasterPageIds[id].append(i);
+		}
+		else
+			indices.append(i);
+
+		// Names remain a compatibility lookup for the logical master.
+		if (!MasterNames.contains(master->pageName()))
+			MasterNames[master->pageName()] = i;
 	}
 }
 
@@ -2880,10 +2921,44 @@ QString ScribusDoc::masterPageName(const QUuid& masterId) const
 	return index >= 0 ? MasterPages.at(index)->pageName() : QString();
 }
 
+QList<int> ScribusDoc::masterPageIndices(const QUuid& masterId) const
+{
+	return MasterPageIds.value(masterId);
+}
+
 int ScribusDoc::masterPageIndex(const QUuid& masterId) const
 {
-	auto it = MasterPageIds.constFind(masterId);
-	return it == MasterPageIds.constEnd() ? -1 : it.value();
+	const QList<int> indices = masterPageIndices(masterId);
+	if (indices.isEmpty())
+		return -1;
+
+	for (int index : indices)
+	{
+		if (MasterPages.at(index)->masterPageSide() == MasterPageSide::Single)
+			return index;
+	}
+	return indices.first();
+}
+
+int ScribusDoc::masterPageIndex(const QUuid& masterId, MasterPageSide side) const
+{
+	const QList<int> indices = masterPageIndices(masterId);
+	for (int index : indices)
+	{
+		if (MasterPages.at(index)->masterPageSide() == side)
+			return index;
+	}
+	return -1;
+}
+
+bool ScribusDoc::masterPageHasSide(const QUuid& masterId, MasterPageSide side) const
+{
+	return masterPageIndex(masterId, side) >= 0;
+}
+
+bool ScribusDoc::isMasterSpread(const QUuid& masterId) const
+{
+	return masterPageHasSide(masterId, MasterPageSide::Left) && masterPageHasSide(masterId, MasterPageSide::Right);
 }
 
 
@@ -4892,14 +4967,25 @@ bool ScribusDoc::applyMasterPage(const QString& pageName, int pageNumber)
 
 bool ScribusDoc::applyMasterPage(const QUuid& masterId, int pageNumber)
 {
-	const int MpNr = masterPageIndex(masterId);
+	const int index = masterPageIndex(masterId);
+	if (index < 0)
+		return false;
+	return applyMasterPage(masterId, MasterPages.at(index)->masterPageSide(), pageNumber);
+}
+
+bool ScribusDoc::applyMasterPage(const QUuid& masterId, MasterPageSide side, int pageNumber)
+{
+	int MpNr = masterPageIndex(masterId, side);
+	if (MpNr < 0 && side != MasterPageSide::Single)
+		MpNr = masterPageIndex(masterId, MasterPageSide::Single);
 	if (MpNr < 0)
 		return false;
+	const MasterPageSide appliedSide = MasterPages.at(MpNr)->masterPageSide();
 	const QString pageName = MasterPages.at(MpNr)->pageName();
 
 	if (UndoManager::undoEnabled())
 	{
-		if (DocPages.at(pageNumber)->masterPageName() != pageName)
+		if (DocPages.at(pageNumber)->appliedMasterPageId() != masterId || DocPages.at(pageNumber)->appliedMasterPageSide() != appliedSide)
 		{
 			auto *ss = new SimpleState(Um::ApplyMasterPage, QString(Um::FromTo).arg(DocPages.at(pageNumber)->masterPageName(), pageName));
 			ss->set("PAGE_NUMBER", pageNumber);
@@ -4907,12 +4993,15 @@ bool ScribusDoc::applyMasterPage(const QUuid& masterId, int pageNumber)
 			ss->set("NEW_MASTERPAGE", pageName);
 			ss->set("OLD_MASTERPAGE_ID", DocPages.at(pageNumber)->appliedMasterPageId().toString(QUuid::WithoutBraces));
 			ss->set("NEW_MASTERPAGE_ID", masterId.toString(QUuid::WithoutBraces));
+			ss->set("OLD_MASTERPAGE_SIDE", static_cast<int>(DocPages.at(pageNumber)->appliedMasterPageSide()));
+			ss->set("NEW_MASTERPAGE_SIDE", static_cast<int>(appliedSide));
 			m_undoManager->action(this, ss);
 		}
 	}
 	ScPage* Ap = DocPages.at(pageNumber);
 	Ap->setMasterPageName(pageName);
 	Ap->setAppliedMasterPageId(masterId);
+	Ap->setAppliedMasterPageSide(appliedSide);
 	const ScPage* Mp = MasterPages.at(MpNr);
 	PageItem *currItem;
 	Ap->FromMaster.clear();
@@ -4998,9 +5087,15 @@ void ScribusDoc::restoreMasterPageApplying(SimpleState* ss, bool isUndo)
 	const QUuid oldId(ss->get("OLD_MASTERPAGE_ID"));
 	const QUuid newId(ss->get("NEW_MASTERPAGE_ID"));
 	if (isUndo)
-		applyMasterPage(oldId.isNull() ? masterPageId(ss->get("OLD_MASTERPAGE")) : oldId, pageNumber);
+	{
+		const MasterPageSide side = ss->contains("OLD_MASTERPAGE_SIDE") ? static_cast<MasterPageSide>(ss->getInt("OLD_MASTERPAGE_SIDE")) : MasterPageSide::Single;
+		applyMasterPage(oldId.isNull() ? masterPageId(ss->get("OLD_MASTERPAGE")) : oldId, side, pageNumber);
+	}
 	else
-		applyMasterPage(newId.isNull() ? masterPageId(ss->get("NEW_MASTERPAGE")) : newId, pageNumber);
+	{
+		const MasterPageSide side = ss->contains("NEW_MASTERPAGE_SIDE") ? static_cast<MasterPageSide>(ss->getInt("NEW_MASTERPAGE_SIDE")) : MasterPageSide::Single;
+		applyMasterPage(newId.isNull() ? masterPageId(ss->get("NEW_MASTERPAGE")) : newId, side, pageNumber);
+	}
 	reformPages();
 	scMW()->pagePalette->rebuildPages();
 }
Index: scribus/scribusdoc.h
===================================================================
--- scribus/scribusdoc.h	(revision 27864)
+++ scribus/scribusdoc.h	(working copy)
@@ -261,13 +261,17 @@ class SCRIBUS_API ScribusDoc : public QObject, public UndoObject, public Observa
 		ScPage* addPage(int pageNumber, const QString& masterPageName = QString(), bool addAutoFrame = false);
 		void deletePage(int);
 		//! @brief Add a master page with this function, do not use addPage
-		ScPage* addMasterPage(int, const QString&, const QUuid& masterId = QUuid());
+		ScPage* addMasterPage(int, const QString&, const QUuid& masterId = QUuid(), MasterPageSide side = MasterPageSide::Single);
 		void deleteMasterPage(int);
 		//! @brief Rebuild master name list
 		void rebuildMasterNames();
 		QUuid masterPageId(const QString& pageName) const;
 		QString masterPageName(const QUuid& masterId) const;
+		QList<int> masterPageIndices(const QUuid& masterId) const;
 		int masterPageIndex(const QUuid& masterId) const;
+		int masterPageIndex(const QUuid& masterId, MasterPageSide side) const;
+		bool masterPageHasSide(const QUuid& masterId, MasterPageSide side) const;
+		bool isMasterSpread(const QUuid& masterId) const;
 		//! @brief Replace a master page by default one
 		void replaceMasterPage(const QString& oldMasterPage);
 		//! @brief Rename a master page
@@ -839,6 +843,7 @@ class SCRIBUS_API ScribusDoc : public QObject, public UndoObject, public Observa
 		 */
 		bool applyMasterPage(const QString& pageName, int pageNumber);
 		bool applyMasterPage(const QUuid& masterId, int pageNumber);
+		bool applyMasterPage(const QUuid& masterId, MasterPageSide side, int pageNumber);
 		/**
 		 * @brief Undo function for applying a master page
 		 */
@@ -1415,7 +1420,7 @@ class SCRIBUS_API ScribusDoc : public QObject, public UndoObject, public Observa
 		QList<ScPage*> TempPages;
 		/** \brief Mapping Master Page Name to Master Page numbers */
 		QMap<QString,int> MasterNames;
-		QHash<QUuid, int> MasterPageIds;
+		QHash<QUuid, QList<int>> MasterPageIds;
 		/** \brief List of Objects */
 		//! Items is a pointer to the lists of items that exist in a document. In Normal mode, it points to DocItems. In Master Page mode it points to MasterItems. See ScribusDoc::assignPageModeLists()
 		QList<PageItem*>* Items {nullptr};
tsm2-spread-editor-v1.16.patch (76,195 bytes)   
Index: scribus/canvas.cpp
===================================================================
--- scribus/canvas.cpp	(revision 27864)
+++ scribus/canvas.cpp	(working copy)
@@ -17,6 +17,7 @@
 #define _USE_MATH_DEFINES
 #endif
 #include <cmath>
+#include <utility>
 
 // #include <QDebug>
 #include <QToolTip>
@@ -467,7 +468,7 @@
 	while (currNr >= 0)
 	{
 		currItem = itemList->at(currNr);
-		if ((m_doc->masterPageMode())  && (!((currItem->OwnPage == -1) || (currItem->OwnPage == m_doc->currentPage()->pageNr()))))
+		if (m_doc->masterPageMode() && !m_doc->isMasterItemOnCurrentMaster(currItem))
 		{
 			--currNr;
 			continue;
@@ -1429,7 +1430,6 @@
 	                   qRound(clip.width() / m_viewMode.scale + 0.5), qRound(clip.height() / m_viewMode.scale + 0.5));
 
 	PageItem *currItem { nullptr };
-	int docCurrPageNo = m_doc->currentPageNumber();
 	if (((layer.blendMode != 0) || (layer.transparency != 1.0)) && (!layer.outlineMode))
 		painter->beginLayer(layer.transparency, layer.blendMode);
 
@@ -1446,7 +1446,7 @@
 				|| (currItem->m_layerID != layer.ID)
 				|| (m_viewMode.previewMode && !currItem->printEnabled())
 				|| (m_viewMode.viewAsPreview && (!currItem->printEnabled()))
-				|| (m_doc->masterPageMode() && ((currItem->OwnPage != -1) && (currItem->OwnPage != docCurrPageNo)))
+				|| (m_doc->masterPageMode() && !m_doc->isMasterItemOnCurrentMaster(currItem))
 				|| ((!m_doc->masterPageMode() && !currItem->OnMasterPage.isEmpty()) && (currItem->OnMasterPage != m_doc->currentPage()->pageName())))
 				continue;
 			if (cullingArea.intersects(currItem->getBoundingRect().adjusted(0.0, 0.0, 1.0, 1.0)))
@@ -1466,7 +1466,7 @@
 			continue;
 		if ((m_viewMode.viewAsPreview) && (!currItem->printEnabled()))
 			continue;
-		if ((m_doc->masterPageMode()) && ((currItem->OwnPage != -1) && (currItem->OwnPage != docCurrPageNo)))
+		if (m_doc->masterPageMode() && !m_doc->isMasterItemOnCurrentMaster(currItem))
 			continue;
 		if (!m_doc->masterPageMode() && !currItem->OnMasterPage.isEmpty())
 		{
@@ -1505,43 +1505,67 @@
 		painter->endLayer();
 }
 
+QList<const ScPage*> Canvas::masterEditPages() const
+{
+	QList<const ScPage*> pages;
+	const ScPage* currentPage = m_doc->currentPage();
+	if (!currentPage)
+		return pages;
+
+	const QList<int> indices = m_doc->masterPageIndices(currentPage->masterPageId());
+	if (indices.isEmpty())
+	{
+		pages.append(currentPage);
+		return pages;
+	}
+
+	for (int index : indices)
+	{
+		if (index >= 0 && index < m_doc->MasterPages.count())
+			pages.append(m_doc->MasterPages.at(index));
+	}
+	return pages;
+}
+
+
 /**
   Draws the canvas background for masterpages, incl. bleeds
  */
 void Canvas::drawBackgroundMasterpage(ScPainter* painter, int clipx, int clipy, int clipw, int cliph)
 {
-	const ScPage* currentPage = m_doc->currentPage();
-	double x = currentPage->xOffset() * m_viewMode.scale;
-	double y = currentPage->yOffset() * m_viewMode.scale;
-	double w = currentPage->width() * m_viewMode.scale;
-	double h = currentPage->height() * m_viewMode.scale;
-	QRectF drawRect(x, y, w + 5, h + 5);
-	drawRect.translate(-m_doc->minCanvasCoordinate.x() * m_viewMode.scale, -m_doc->minCanvasCoordinate.y() * m_viewMode.scale);
-	if (!drawRect.intersects(QRectF(clipx, clipy, clipw, cliph)))
-		return;
+	for (const ScPage* page : masterEditPages())
+	{
+		double x = page->xOffset() * m_viewMode.scale;
+		double y = page->yOffset() * m_viewMode.scale;
+		double w = page->width() * m_viewMode.scale;
+		double h = page->height() * m_viewMode.scale;
+		QRectF drawRect(x, y, w + 5, h + 5);
+		drawRect.translate(-m_doc->minCanvasCoordinate.x() * m_viewMode.scale, -m_doc->minCanvasCoordinate.y() * m_viewMode.scale);
+		if (!drawRect.intersects(QRectF(clipx, clipy, clipw, cliph)))
+			continue;
 
-	painter->setFillMode(ScPainter::Solid);
-	painter->setBrush(QColor(128,128,128));
-	MarginStruct pageBleeds;
-	if (!m_doc->bleeds()->isNull() && m_doc->guidesPrefs().showBleed)
-		m_doc->getBleeds(currentPage, pageBleeds);
-	if (m_viewMode.viewAsPreview)
-		pageBleeds.resetToZero();
-	double px = currentPage->xOffset() - pageBleeds.left();
-	double py = currentPage->yOffset() - pageBleeds.top();
-	double pw = currentPage->width() + pageBleeds.left() + pageBleeds.right();
-	double ph = currentPage->height() + pageBleeds.bottom() + pageBleeds.top();
-	painter->setAntialiasing(false);
-	painter->setPen(Qt::black, 1 / m_viewMode.scale, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
-	if (PrefsManager::instance().appPrefs.displayPrefs.showPageShadow)
-		painter->drawRect(px + 5, py + 5, pw, ph);
-	painter->setBrush(m_doc->paperColor());
-	painter->drawRect(px, py, pw, ph);
-	painter->setAntialiasing(true);
+		painter->setFillMode(ScPainter::Solid);
+		painter->setBrush(QColor(128,128,128));
+		MarginStruct pageBleeds;
+		if (!m_doc->bleeds()->isNull() && m_doc->guidesPrefs().showBleed)
+			m_doc->getBleeds(page, pageBleeds);
+		if (m_viewMode.viewAsPreview)
+			pageBleeds.resetToZero();
+		double px = page->xOffset() - pageBleeds.left();
+		double py = page->yOffset() - pageBleeds.top();
+		double pw = page->width() + pageBleeds.left() + pageBleeds.right();
+		double ph = page->height() + pageBleeds.bottom() + pageBleeds.top();
+		painter->setAntialiasing(false);
+		painter->setPen(Qt::black, 1 / m_viewMode.scale, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
+		if (PrefsManager::instance().appPrefs.displayPrefs.showPageShadow)
+			painter->drawRect(px + 5, py + 5, pw, ph);
+		painter->setBrush(m_doc->paperColor());
+		painter->drawRect(px, py, pw, ph);
+		painter->setAntialiasing(true);
+	}
 }
 
 
-
 /**
   draws the page outlines on canvas, including bleed area
  */
@@ -1674,22 +1698,23 @@
 {
 	PoLine->resize(0);
 	bool first = true;
-	ScPage *actPg;
+	const ScPage *actPg;
 	
-	int firstPage = 0;
-	int lastPage  = m_doc->Pages->count();
+	QList<const ScPage*> pages;
 	if (m_doc->Pages == &m_doc->MasterPages)
+		pages = masterEditPages();
+	else
 	{
-		firstPage = m_doc->currentPage()->pageNr();
-		lastPage  = firstPage + 1;
+		for (const ScPage* page : std::as_const(*m_doc->Pages))
+			pages.append(page);
 	}
 
-	for (int i = firstPage; i < lastPage; ++i)
+	for (const ScPage* page : std::as_const(pages))
 	{
 		if (!first)
 			PoLine->setMarker();
 		first = false;
-		actPg = m_doc->Pages->at(i);
+		actPg = page;
 		double x = actPg->xOffset();
 		double y = actPg->yOffset();
 		double w = actPg->width();
@@ -1728,15 +1753,13 @@
 {
 	if (master)
 	{
-		const ScPage *page = m_doc->currentPage();
-		double x = m_doc->scratch()->left() * m_viewMode.scale;
-		double y = m_doc->scratch()->top() * m_viewMode.scale;
-		double w = page->width() * m_viewMode.scale;
-		double h = page->height() * m_viewMode.scale;
-		QRectF drawRect(x, y, w + 5, h + 5);
-		drawRect.translate(-m_doc->minCanvasCoordinate.x() * m_viewMode.scale, -m_doc->minCanvasCoordinate.y() * m_viewMode.scale);
-		if (drawRect.intersects(clip))
-			DrawPageBorderSub(p, page);
+		for (const ScPage* page : masterEditPages())
+		{
+			QRectF drawRect(page->xOffset() * m_viewMode.scale, page->yOffset() * m_viewMode.scale, page->width() * m_viewMode.scale + 5, page->height() * m_viewMode.scale + 5);
+			drawRect.translate(-m_doc->minCanvasCoordinate.x() * m_viewMode.scale, -m_doc->minCanvasCoordinate.y() * m_viewMode.scale);
+			if (drawRect.intersects(clip))
+				DrawPageBorderSub(p, page);
+		}
 	}
 	else
 	{
@@ -1790,15 +1813,13 @@
 {
 	if (master)
 	{
-		const ScPage *page = m_doc->currentPage();
-		double x = m_doc->scratch()->left() * m_viewMode.scale;
-		double y = m_doc->scratch()->top() * m_viewMode.scale;
-		double w = page->width() * m_viewMode.scale;
-		double h = page->height() * m_viewMode.scale;
-		QRectF drawRect(x, y, w + 5, h + 5);
-		drawRect.translate(-m_doc->minCanvasCoordinate.x() * m_viewMode.scale, -m_doc->minCanvasCoordinate.y() * m_viewMode.scale);
-		if (drawRect.intersects(clip))
-			DrawPageMarginsGridSub(p, page);
+		for (const ScPage* page : masterEditPages())
+		{
+			QRectF drawRect(page->xOffset() * m_viewMode.scale, page->yOffset() * m_viewMode.scale, page->width() * m_viewMode.scale + 5, page->height() * m_viewMode.scale + 5);
+			drawRect.translate(-m_doc->minCanvasCoordinate.x() * m_viewMode.scale, -m_doc->minCanvasCoordinate.y() * m_viewMode.scale);
+			if (drawRect.intersects(clip))
+				DrawPageMarginsGridSub(p, page);
+		}
 	}
 	else
 	{
@@ -1844,15 +1865,13 @@
 
 	if (master)
 	{
-		const ScPage *page = m_doc->currentPage();
-		double x = m_doc->scratch()->left() * m_viewMode.scale;
-		double y = m_doc->scratch()->top() * m_viewMode.scale;
-		double w = page->width() * m_viewMode.scale;
-		double h = page->height() * m_viewMode.scale;
-		QRectF drawRect(x, y, w + 5, h + 5);
-		drawRect.translate(-m_doc->minCanvasCoordinate.x() * m_viewMode.scale, -m_doc->minCanvasCoordinate.y() * m_viewMode.scale);
-		if (drawRect.intersects(clip))
-			DrawPageBaselineGridSub(p, page);
+		for (const ScPage* page : masterEditPages())
+		{
+			QRectF drawRect(page->xOffset() * m_viewMode.scale, page->yOffset() * m_viewMode.scale, page->width() * m_viewMode.scale + 5, page->height() * m_viewMode.scale + 5);
+			drawRect.translate(-m_doc->minCanvasCoordinate.x() * m_viewMode.scale, -m_doc->minCanvasCoordinate.y() * m_viewMode.scale);
+			if (drawRect.intersects(clip))
+				DrawPageBaselineGridSub(p, page);
+		}
 	}
 	else
 	{
@@ -1972,15 +1991,13 @@
 
 	if (master)
 	{
-		const ScPage *page = m_doc->currentPage();
-		double x = m_doc->scratch()->left() * m_viewMode.scale;
-		double y = m_doc->scratch()->top() * m_viewMode.scale;
-		double w = page->width() * m_viewMode.scale;
-		double h = page->height() * m_viewMode.scale;
-		QRectF drawRect(x, y, w + 5, h + 5);
-		drawRect.translate(-m_doc->minCanvasCoordinate.x() * m_viewMode.scale, -m_doc->minCanvasCoordinate.y() * m_viewMode.scale);
-		if (drawRect.intersects(clip))
-			DrawPageGridSub(p, page, clip);
+		for (const ScPage* page : masterEditPages())
+		{
+			QRectF drawRect(page->xOffset() * m_viewMode.scale, page->yOffset() * m_viewMode.scale, page->width() * m_viewMode.scale + 5, page->height() * m_viewMode.scale + 5);
+			drawRect.translate(-m_doc->minCanvasCoordinate.x() * m_viewMode.scale, -m_doc->minCanvasCoordinate.y() * m_viewMode.scale);
+			if (drawRect.intersects(clip))
+				DrawPageGridSub(p, page, clip);
+		}
 	}
 	else
 	{
@@ -2023,15 +2040,13 @@
 
 	if (master)
 	{
-		const ScPage *page = m_doc->currentPage();
-		double x = m_doc->scratch()->left() * m_viewMode.scale;
-		double y = m_doc->scratch()->top() * m_viewMode.scale;
-		double w = page->width() * m_viewMode.scale;
-		double h = page->height() * m_viewMode.scale;
-		QRectF drawRect(x, y, w + 5, h + 5);
-		drawRect.translate(-m_doc->minCanvasCoordinate.x() * m_viewMode.scale, -m_doc->minCanvasCoordinate.y() * m_viewMode.scale);
-		if (drawRect.intersects(clip))
-			DrawPageGuidesSub(p, page);
+		for (const ScPage* page : masterEditPages())
+		{
+			QRectF drawRect(page->xOffset() * m_viewMode.scale, page->yOffset() * m_viewMode.scale, page->width() * m_viewMode.scale + 5, page->height() * m_viewMode.scale + 5);
+			drawRect.translate(-m_doc->minCanvasCoordinate.x() * m_viewMode.scale, -m_doc->minCanvasCoordinate.y() * m_viewMode.scale);
+			if (drawRect.intersects(clip))
+				DrawPageGuidesSub(p, page);
+		}
 	}
 	else
 	{
@@ -2074,8 +2089,8 @@
 	if (master)
 	{
 		const ScPage *page = m_doc->currentPage();
-		double x = m_doc->scratch()->left() * m_viewMode.scale;
-		double y = m_doc->scratch()->top() * m_viewMode.scale;
+		double x = page->xOffset() * m_viewMode.scale;
+		double y = page->yOffset() * m_viewMode.scale;
 		double w = page->width() * m_viewMode.scale;
 		double h = page->height() * m_viewMode.scale;
 		QRectF drawRect(x, y, w + 5, h + 5);
Index: scribus/canvas.h
===================================================================
--- scribus/canvas.h	(revision 27864)
+++ scribus/canvas.h	(working copy)
@@ -25,6 +25,7 @@
 #define CANVAS_H
 
 #include <QApplication>
+#include <QList>
 //#include <QDebug>
 #include <QPolygon>
 #include <QRect>
@@ -248,6 +249,7 @@
 	 */
 	void fillBuffer(QPaintDevice* buffer, QPoint bufferOrigin, QRect clipRect);
 	void drawContents(QPainter *p, int clipx, int clipy, int clipw, int cliph);
+	QList<const ScPage*> masterEditPages() const;
 	void drawBackgroundMasterpage(ScPainter* painter, int clipx, int clipy, int clipw, int cliph);
 	void drawBackgroundPageOutlines(ScPainter* painter, int clipx, int clipy, int clipw, int cliph);
 	void drawControls(QPainter* p);
Index: scribus/canvasmode_edit.cpp
===================================================================
--- scribus/canvasmode_edit.cpp	(revision 27864)
+++ scribus/canvasmode_edit.cpp	(working copy)
@@ -909,7 +909,7 @@
 				m_canvas->Transform(docItem, p);
 				QRegion apr = QRegion(docItem->Clip * p);
 				QRect apr2(docItem->getRedrawBounding(1.0));
-				if ((m_doc->masterPageMode()) && (docItem->OnMasterPage != m_doc->currentPage()->pageName()))
+				if (m_doc->masterPageMode() && !m_doc->isMasterItemOnCurrentMaster(docItem))
 					continue;
 				if (((Sele.contains(apr.boundingRect())) || (Sele.contains(apr2))) && m_doc->canSelectItemOnLayer(docItem->m_layerID))
 				{
Index: scribus/canvasmode_normal.cpp
===================================================================
--- scribus/canvasmode_normal.cpp	(revision 27864)
+++ scribus/canvasmode_normal.cpp	(working copy)
@@ -1207,7 +1207,7 @@
 			for (int a = 0; a < docItemCount; ++a)
 			{
 				PageItem* docItem = m_doc->Items->at(a);
-				if ((m_doc->masterPageMode()) && (docItem->OnMasterPage != m_doc->currentPage()->pageName()))
+				if (m_doc->masterPageMode() && !m_doc->isMasterItemOnCurrentMaster(docItem))
 					continue;
 				if (m_doc->canSelectItemOnLayer(docItem->m_layerID))
 				{
Index: scribus/plugins/fileloader/scribus171format/scribus171format.cpp
===================================================================
--- scribus/plugins/fileloader/scribus171format/scribus171format.cpp	(revision 27864)
+++ scribus/plugins/fileloader/scribus171format/scribus171format.cpp	(working copy)
@@ -4995,7 +4995,10 @@
 	{
 		if (!readObjectParams.renamedMasterPage.isEmpty())
 			masterPageName = readObjectParams.renamedMasterPage;
-		doc->setCurrentPage(doc->MasterPages.at(doc->MasterNames[masterPageName]));
+		if (readObjectParams.destinationMasterPage >= 0 && readObjectParams.destinationMasterPage < doc->MasterPages.count())
+			doc->setCurrentPage(doc->MasterPages.at(readObjectParams.destinationMasterPage));
+		else
+			doc->setCurrentPage(doc->MasterPages.at(doc->MasterNames[masterPageName]));
 		pageNr = -2;
 	}
 	layerFound = false;
@@ -5010,7 +5013,20 @@
 	newItem->setMasterPageId(itemMasterId);
 	newItem->setRedrawBounding();
 	if (tagName == QLatin1String("MASTEROBJECT") || tagName == QLatin1String("MasterObject"))
-		newItem->setOwnerPage(doc->OnPage(newItem));
+	{
+		int ownerPage = readObjectParams.destinationMasterPage;
+		if (ownerPage < 0)
+		{
+			const int storedOwner = attrs.valueAsInt("OwnPage", -1);
+			if (storedOwner >= 0 && storedOwner < doc->MasterPages.count())
+			{
+				const ScPage* storedPage = doc->MasterPages.at(storedOwner);
+				if (itemMasterId.isNull() || storedPage->masterPageId() == itemMasterId)
+					ownerPage = storedOwner;
+			}
+		}
+		newItem->setOwnerPage(ownerPage >= 0 ? ownerPage : doc->OnPage(newItem));
+	}
 	else
 		newItem->setOwnerPage(attrs.valueAsInt("OwnPage"));
 	if ((tagName == QLatin1String("PAGEOBJECT") || tagName == QLatin1String("PageObject")) || (tagName == QLatin1String("ITEM") || tagName == QLatin1String("Item")))
@@ -8337,6 +8353,7 @@
 				continue;
 			int pageNr = m_Doc->currentPage()->pageNr();
 			newPage = m_Doc->Pages->at(pageNr);
+			readObjectParams.destinationMasterPage = Mpage ? pageNr : -1;
 			SimpleState *ss = nullptr;
 			if (UndoManager::undoEnabled())
 			{
@@ -8509,7 +8526,7 @@
 
 			PageItem* newItem = itemInfo.item;
 			newItem->moveBy(-pageX + newPage->xOffset(), - pageY + newPage->yOffset());
-			newItem->setOwnerPage(m_Doc->currentPageNumber());
+			newItem->setOwnerPage(Mpage ? newPage->pageNr() : m_Doc->currentPageNumber());
 			if (tagName == QLatin1String("PAGEOBJECT") || tagName == QLatin1String("PageObject"))
 				newItem->setMasterPageName(QString());
 			else if (Mpage && !renamedPageName.isEmpty())
Index: scribus/plugins/fileloader/scribus171format/scribus171format.h
===================================================================
--- scribus/plugins/fileloader/scribus171format/scribus171format.h	(revision 27864)
+++ scribus/plugins/fileloader/scribus171format/scribus171format.h	(working copy)
@@ -113,6 +113,7 @@
 			bool    loadingPage { false };
 			QString baseDir;
 			QString renamedMasterPage;
+			int destinationMasterPage {-1};
 		};
 
 		void registerFormats();
Index: scribus/plugins/scripter/api_document.cpp
===================================================================
--- scribus/plugins/scripter/api_document.cpp	(revision 27864)
+++ scribus/plugins/scripter/api_document.cpp	(working copy)
@@ -521,10 +521,7 @@
 		RAISE("Can not delete the Normal master page");
 		return;
 	}
-	bool oldMode = ScCore->primaryMainWindow()->doc->masterPageMode();
-	ScCore->primaryMainWindow()->doc->setMasterPageMode(true);
-	ScCore->primaryMainWindow()->deletePage2(ScCore->primaryMainWindow()->doc->MasterNames[name]);
-	ScCore->primaryMainWindow()->doc->setMasterPageMode(oldMode);
+	ScCore->primaryMainWindow()->deleteMasterPage(name);
 }
 
 void DocumentAPI::closeMasterPage(QString name)
Index: scribus/plugins/scriptplugin/cmddoc.cpp
===================================================================
--- scribus/plugins/scriptplugin/cmddoc.cpp	(revision 27864)
+++ scribus/plugins/scriptplugin/cmddoc.cpp	(working copy)
@@ -507,10 +507,7 @@
 		PyErr_SetString(PyExc_ValueError, "Can not delete the Normal master page");
 		return nullptr;
 	}
-	bool oldMode = currentDoc->masterPageMode();
-	currentDoc->setMasterPageMode(true);
-	ScCore->primaryMainWindow()->deletePage2(currentDoc->MasterNames[masterPageName]);
-	currentDoc->setMasterPageMode(oldMode);
+	ScCore->primaryMainWindow()->deleteMasterPage(masterPageName);
 
 	Py_RETURN_NONE;
 }
Index: scribus/scpage.cpp
===================================================================
--- scribus/scpage.cpp	(revision 27864)
+++ scribus/scpage.cpp	(working copy)
@@ -437,11 +437,43 @@
 		PageItem* oldItem = itemList.at(id2);
 		if (oldItem->Parent && oldItem->Parent->isGroup())
 			oldItem->Parent->asGroupFrame()->groupItemList.insert(id, oldItem);
+		else if (state->contains("STABLE_ITEM_ORDER") && state->getBool("STABLE_ITEM_ORDER"))
+		{
+			int insertIndex = -1;
+			if (state->contains("NEXT_ITEM_UID"))
+			{
+				const quint64 nextUid = state->getUInt64("NEXT_ITEM_UID");
+				for (int i = 0; i < m_Doc->Items->count(); ++i)
+				{
+					if (m_Doc->Items->at(i)->getUId() == nextUid)
+					{
+						insertIndex = i;
+						break;
+					}
+				}
+			}
+			if ((insertIndex < 0) && state->contains("PREV_ITEM_UID"))
+			{
+				const quint64 prevUid = state->getUInt64("PREV_ITEM_UID");
+				for (int i = 0; i < m_Doc->Items->count(); ++i)
+				{
+					if (m_Doc->Items->at(i)->getUId() == prevUid)
+					{
+						insertIndex = i + 1;
+						break;
+					}
+				}
+			}
+			if (insertIndex < 0)
+				insertIndex = qBound(0, id, m_Doc->Items->count());
+			m_Doc->Items->insert(insertIndex, oldItem);
+		}
 		else
 			m_Doc->Items->insert(id, oldItem);
 		if (oldItem->isBookmark)
 			m_Doc->scMW()->AddBookMark(oldItem);
-		m_Doc->m_Selection->addItems(itemList);
+		if (!state->contains("RESTORE_SELECTION") || state->getBool("RESTORE_SELECTION"))
+			m_Doc->m_Selection->addItems(itemList);
 	}
 	else
 	{
Index: scribus/scribus.cpp
===================================================================
--- scribus/scribus.cpp	(revision 27864)
+++ scribus/scribus.cpp	(working copy)
@@ -6104,6 +6104,46 @@
 	deletePage(pg + 1, pg + 1);
 }
 
+void ScribusMainWindow::deleteMasterPage(const QString& masterPageName)
+{
+	if (!HaveDoc)
+		return;
+
+	const QUuid masterId = doc->masterPageId(masterPageName);
+	QList<int> masterIndices = doc->masterPageIndices(masterId);
+	if (masterIndices.isEmpty())
+		return;
+
+	std::sort(masterIndices.begin(), masterIndices.end());
+	const bool oldMasterPageMode = doc->masterPageMode();
+	ScPage* oldPage = oldMasterPageMode ? nullptr : doc->currentPage();
+	doc->setMasterPageMode(true);
+
+	bool contiguous = true;
+	for (int i = 1; i < masterIndices.count(); ++i)
+	{
+		if (masterIndices.at(i) != masterIndices.at(i - 1) + 1)
+		{
+			contiguous = false;
+			break;
+		}
+	}
+
+	if (masterIndices.count() == 1)
+		deletePage2(masterIndices.first());
+	else if (contiguous)
+		deletePage(masterIndices.first() + 1, masterIndices.last() + 1);
+	else
+	{
+		for (int i = masterIndices.count() - 1; i >= 0; --i)
+			deletePage2(masterIndices.at(i));
+	}
+
+	doc->setMasterPageMode(oldMasterPageMode);
+	if (oldPage)
+		doc->setCurrentPage(oldPage);
+}
+
 void ScribusMainWindow::deletePage()
 {
 	if (HaveDoc && doc->appMode == modeEditClip)
@@ -6130,6 +6170,25 @@
 	PageItem* ite;
 	doc->m_Selection->clear();
 	Selection tmpSelection(this, false);
+	bool deletingMasterSpread = false;
+	QStringList deletedMasterNames;
+	if (doc->masterPageMode())
+	{
+		for (int a = to - 1; a >= from - 1; --a)
+		{
+			const ScPage* masterPage = doc->Pages->at(a);
+			if (doc->isMasterSpread(masterPage->masterPageId()))
+				deletingMasterSpread = true;
+			if (!deletedMasterNames.contains(masterPage->pageName()))
+				deletedMasterNames.append(masterPage->pageName());
+		}
+
+		// Reassign document pages while the masters still exist. The resulting
+		// ApplyMasterPage undo states then live in the same transaction as the
+		// master deletion, so Undo restores the masters before reapplying them.
+		for (const QString& masterName : std::as_const(deletedMasterNames))
+			doc->replaceMasterPage(masterName);
+	}
 	for (int a = to - 1; a >= from - 1; a--)
 	{
 		for (int d = 0; d < doc->Items->count(); ++d)
@@ -6155,7 +6214,11 @@
 		page->guides.clearVerticals(GuideManagerCore::Auto);
 	}
 	if (tmpSelection.count() != 0)
-		doc->itemSelection_DeleteItem(&tmpSelection);
+	{
+		const auto selectionPolicy = deletingMasterSpread ? ScribusDoc::ItemDeletionSelectionPolicy::DoNotRestore : ScribusDoc::ItemDeletionSelectionPolicy::Restore;
+		const auto orderPolicy = deletingMasterSpread ? ScribusDoc::ItemDeletionOrderPolicy::StableNeighbors : ScribusDoc::ItemDeletionOrderPolicy::OriginalIndex;
+		doc->itemSelection_DeleteItem(&tmpSelection, false, selectionPolicy, orderPolicy);
+	}
 	const QSignalBlocker blocker(pageSelector);
 	view->updatesOn(false);
 	for (int a = to - 1; a >= from - 1; a--)
@@ -6169,6 +6232,22 @@
 			ss->set("MASTERPAGE", doc->Pages->at(a)->masterPageName());
 			ss->set("MASTER_PAGE_ID", doc->Pages->at(a)->masterPageId().toString(QUuid::WithoutBraces));
 			ss->set("MASTER_PAGE_SIDE", static_cast<int>(doc->Pages->at(a)->masterPageSide()));
+			if (doc->masterPageMode())
+			{
+				const ScPage* masterPage = doc->Pages->at(a);
+				ss->set("MASTER_PAGE_LEFT", masterPage->LeftPg);
+				ss->set("MASTER_PAGE_INITIALTOP", masterPage->initialMargins.top());
+				ss->set("MASTER_PAGE_INITIALBOTTOM", masterPage->initialMargins.bottom());
+				ss->set("MASTER_PAGE_INITIALLEFT", masterPage->initialMargins.left());
+				ss->set("MASTER_PAGE_INITIALRIGHT", masterPage->initialMargins.right());
+				ss->set("MASTER_PAGE_INITIALHEIGHT", masterPage->initialHeight());
+				ss->set("MASTER_PAGE_INITIALWIDTH", masterPage->initialWidth());
+				ss->set("MASTER_PAGE_HEIGHT", masterPage->height());
+				ss->set("MASTER_PAGE_WIDTH", masterPage->width());
+				ss->set("MASTER_PAGE_ORIENTATION", masterPage->orientation());
+				ss->set("MASTER_PAGE_SIZE", masterPage->size());
+				ss->set("MASTER_PAGE_MARGINPRESET", masterPage->marginPreset);
+			}
 			ss->set("APPLIED_MASTER_PAGE_ID", doc->Pages->at(a)->appliedMasterPageId().toString(QUuid::WithoutBraces));
 			ss->set("APPLIED_MASTER_PAGE_SIDE", static_cast<int>(doc->Pages->at(a)->appliedMasterPageSide()));
 			ss->set("MASTER_PAGE_MODE",  doc->masterPageMode());
@@ -7959,26 +8038,44 @@
 
 	if (isUndo)
 	{
+		ScPage* restoredPage = nullptr;
 		if (savedMasterPageMode)
 		{
-			slotNewMasterPage(wo, pageName);
 			const QUuid restoredId(state->get("MASTER_PAGE_ID"));
-			if (!restoredId.isNull())
-				doc->Pages->at(pagenr - 1)->setMasterPageId(restoredId);
-			if (state->contains("MASTER_PAGE_SIDE"))
-				doc->Pages->at(pagenr - 1)->setMasterPageSide(static_cast<MasterPageSide>(state->getInt("MASTER_PAGE_SIDE")));
+			const MasterPageSide restoredSide = state->contains("MASTER_PAGE_SIDE")
+				? masterPageSideFromInt(state->getInt("MASTER_PAGE_SIDE"))
+				: MasterPageSide::Single;
+			restoredPage = doc->addMasterPage(wo, pageName, restoredId, restoredSide);
+			if (restoredSide == MasterPageSide::Single && state->contains("MASTER_PAGE_LEFT"))
+				restoredPage->LeftPg = state->getInt("MASTER_PAGE_LEFT");
+			if (state->contains("MASTER_PAGE_INITIALTOP"))
+			{
+				restoredPage->initialMargins.setTop(state->getDouble("MASTER_PAGE_INITIALTOP"));
+				restoredPage->initialMargins.setBottom(state->getDouble("MASTER_PAGE_INITIALBOTTOM"));
+				restoredPage->initialMargins.setLeft(state->getDouble("MASTER_PAGE_INITIALLEFT"));
+				restoredPage->initialMargins.setRight(state->getDouble("MASTER_PAGE_INITIALRIGHT"));
+				restoredPage->setInitialHeight(state->getDouble("MASTER_PAGE_INITIALHEIGHT"));
+				restoredPage->setInitialWidth(state->getDouble("MASTER_PAGE_INITIALWIDTH"));
+				restoredPage->setHeight(state->getDouble("MASTER_PAGE_HEIGHT"));
+				restoredPage->setWidth(state->getDouble("MASTER_PAGE_WIDTH"));
+				restoredPage->setOrientation(state->getInt("MASTER_PAGE_ORIENTATION"));
+				restoredPage->setSize(state->get("MASTER_PAGE_SIZE"));
+				restoredPage->marginPreset = state->getInt("MASTER_PAGE_MARGINPRESET");
+			}
+			view->addPage(wo);
 		}
 		else
 		{
 			addNewPages(wo, where, 1, doc->pageHeight(), doc->pageWidth(), doc->pageOrientation(), doc->pageSize(), true, &tmpl);
+			restoredPage = doc->Pages->at(pagenr - 1);
 			const QUuid restoredId(state->get("APPLIED_MASTER_PAGE_ID"));
 			if (!restoredId.isNull())
-				doc->Pages->at(pagenr - 1)->setAppliedMasterPageId(restoredId);
+				restoredPage->setAppliedMasterPageId(restoredId);
 			if (state->contains("APPLIED_MASTER_PAGE_SIDE"))
-				doc->Pages->at(pagenr - 1)->setAppliedMasterPageSide(static_cast<MasterPageSide>(state->getInt("APPLIED_MASTER_PAGE_SIDE")));
+				restoredPage->setAppliedMasterPageSide(static_cast<MasterPageSide>(state->getInt("APPLIED_MASTER_PAGE_SIDE")));
 		}
 		UndoObject *tmp =
-			m_undoManager->replaceObject(state->getUInt("DUMMY_ID"), doc->Pages->at(pagenr - 1));
+			m_undoManager->replaceObject(state->getUInt("DUMMY_ID"), restoredPage);
 		delete tmp;
 	}
 	else
@@ -7992,7 +8089,10 @@
 	if (currMasterPageMode != savedMasterPageMode)
 		doc->setMasterPageMode(currMasterPageMode);
 	doc->rebuildMasterNames();
-	pagePalette->updateMasterPageList();
+	if (isUndo && savedMasterPageMode)
+		editMasterPagesStart(pageName);
+	else
+		pagePalette->updateMasterPageList();
 	pagePalette->rebuildPages();
 	if (outlinePalette->isVisible())
 		outlinePalette->BuildTree();
Index: scribus/scribus.h
===================================================================
--- scribus/scribus.h	(revision 27864)
+++ scribus/scribus.h	(working copy)
@@ -450,6 +450,7 @@
 	 */
 	void deletePage(int from, int to);
 	void deletePage2(int pg);
+	void deleteMasterPage(const QString& masterPageName);
 	/** \brief Verschiebt Seiten */
 	void movePage();
 	void copyPage();
Index: scribus/scribusdoc.cpp
===================================================================
--- scribus/scribusdoc.cpp	(revision 27864)
+++ scribus/scribusdoc.cpp	(working copy)
@@ -2801,6 +2801,23 @@
 }
 
 
+QPair<ScPage*, ScPage*> ScribusDoc::addMasterSpread(int pageNumber, const QString& pageName)
+{
+	UndoTransaction activeTransaction;
+	if (UndoManager::undoEnabled())
+		activeTransaction = m_undoManager->beginTransaction(getUName(), Um::IDocument, Um::NewMasterPage, pageName, Um::ICreate);
+
+	const QUuid masterId = QUuid::createUuid();
+	ScPage* leftPage = addMasterPage(pageNumber, pageName, masterId, MasterPageSide::Left);
+	ScPage* rightPage = addMasterPage(pageNumber + 1, pageName, masterId, MasterPageSide::Right);
+
+	if (activeTransaction)
+		activeTransaction.commit();
+
+	return qMakePair(leftPage, rightPage);
+}
+
+
 bool ScribusDoc::renameMasterPage(const QString& oldPageName, const QString& newPageName)
 {
 	if (!MasterNames.contains(oldPageName))
@@ -2962,47 +2979,57 @@
 }
 
 
+bool ScribusDoc::isMasterItemOnCurrentMaster(const PageItem* item) const
+{
+	if (!item || !m_currentPage)
+		return false;
+
+	const QUuid currentMasterId = m_currentPage->masterPageId();
+	if (!currentMasterId.isNull() && !item->masterPageId().isNull())
+		return item->masterPageId() == currentMasterId;
+
+	return item->OnMasterPage == m_currentPage->pageName();
+}
+
+
 void ScribusDoc::replaceMasterPage(const QString& oldMasterPage)
 {
-	uint pageIndex = 0;
-	QMap<QString,int>::Iterator it = MasterNames.begin();
-	QListIterator<ScPage *> dpIt(DocPages);
-	ScPage* docPage = nullptr;
-	while (dpIt.hasNext())
+	for (int pageIndex = 0; pageIndex < DocPages.count(); ++pageIndex)
 	{
-		docPage = dpIt.next();
-		if (docPage->masterPageName() == oldMasterPage)
+		ScPage* docPage = DocPages.at(pageIndex);
+		if (docPage->masterPageName() != oldMasterPage)
+			continue;
+
+		QString replacementName;
+		const PageLocation pageLoc = locationOfPage(pageIndex);
+		if (pageLoc == LeftPage && MasterNames.contains(CommonStrings::trMasterPageNormalLeft)
+			&& CommonStrings::trMasterPageNormalLeft != oldMasterPage)
+			replacementName = CommonStrings::trMasterPageNormalLeft;
+		else if (pageLoc == RightPage && MasterNames.contains(CommonStrings::trMasterPageNormalRight)
+			&& CommonStrings::trMasterPageNormalRight != oldMasterPage)
+			replacementName = CommonStrings::trMasterPageNormalRight;
+		else if (pageLoc == MiddlePage && MasterNames.contains(CommonStrings::trMasterPageNormalMiddle)
+			&& CommonStrings::trMasterPageNormalMiddle != oldMasterPage)
+			replacementName = CommonStrings::trMasterPageNormalMiddle;
+		else if (MasterNames.contains(CommonStrings::trMasterPageNormal)
+			&& CommonStrings::trMasterPageNormal != oldMasterPage)
+			replacementName = CommonStrings::trMasterPageNormal;
+
+		if (replacementName.isEmpty())
 		{
-			PageLocation pageLoc = locationOfPage(pageIndex);
-			if (pageLoc == LeftPage)
-			{
-				if (MasterNames.contains(CommonStrings::trMasterPageNormalLeft))
-					docPage->setMasterPageName(CommonStrings::trMasterPageNormalLeft);
-				else if (MasterNames.contains(CommonStrings::trMasterPageNormal))
-					docPage->setMasterPageNameNormal();
-				else
-					docPage->setMasterPageName(it.key());
-			}
-			else if (pageLoc == RightPage)
+			for (auto it = MasterNames.cbegin(); it != MasterNames.cend(); ++it)
 			{
-				if (MasterNames.contains(CommonStrings::trMasterPageNormalRight))
-					docPage->setMasterPageName(CommonStrings::trMasterPageNormalRight);
-				else if (MasterNames.contains(CommonStrings::trMasterPageNormal))
-					docPage->setMasterPageNameNormal();
-				else
-					docPage->setMasterPageName(it.key());
-			}
-			else
-			{
-				if (MasterNames.contains(CommonStrings::trMasterPageNormalMiddle))
-					docPage->setMasterPageName(CommonStrings::trMasterPageNormalMiddle);
-				else if (MasterNames.contains(CommonStrings::trMasterPageNormal))
-					docPage->setMasterPageNameNormal();
-				else
-					docPage->setMasterPageName(it.key());
+				if (it.key() != oldMasterPage)
+				{
+					replacementName = it.key();
+					break;
+				}
 			}
 		}
-		pageIndex++;
+
+		const QUuid replacementId = masterPageId(replacementName);
+		if (!replacementId.isNull())
+			applyMasterPage(replacementId, pageIndex);
 	}
 }
 
@@ -5170,6 +5197,17 @@
 	if (pageNumber == -1 || m_currentPage == nullptr)
 		return false;
 
+	// A true-spread master half has an explicit, stable side. Keep the legacy
+	// LeftPg mirror in sync instead of allowing Page Properties or Undo/Redo to
+	// turn a Left half into a Right legacy page (or vice versa).
+	if (masterPageMode())
+	{
+		if (m_currentPage->masterPageSide() == MasterPageSide::Left)
+			pageType = 1;
+		else if (m_currentPage->masterPageSide() == MasterPageSide::Right)
+			pageType = 0;
+	}
+
 	QRectF pagebox(m_currentPage->xOffset(), m_currentPage->yOffset(), qMax( m_currentPage->width(), width), qMax(m_currentPage->height(), height));
 	if (UndoManager::undoEnabled())
 	{
@@ -6205,14 +6243,24 @@
 	int retw = -1;
 	if (masterPageMode())
 	{
+		QList<int> pageIndices = masterPageIndices(m_currentPage->masterPageId());
+		if (pageIndices.isEmpty())
+			pageIndices.append(m_currentPage->pageNr());
 		MarginStruct pageBleeds;
-		getBleeds(m_currentPage, pageBleeds);
-		int x = static_cast<int>(m_currentPage->xOffset() - pageBleeds.left());
-		int y = static_cast<int>(m_currentPage->yOffset() - pageBleeds.top());
-		int w = static_cast<int>(m_currentPage->width() + pageBleeds.left() + pageBleeds.right());
-		int h = static_cast<int>(m_currentPage->height() + pageBleeds.bottom() + pageBleeds.top());
-		if (QRect(x, y, w, h).contains(qRound(x2), qRound(y2)))
-			retw = m_currentPage->pageNr();
+		for (int pageIndex : std::as_const(pageIndices))
+		{
+			const ScPage* page = MasterPages.at(pageIndex);
+			getBleeds(page, pageBleeds);
+			int x = static_cast<int>(page->xOffset() - pageBleeds.left());
+			int y = static_cast<int>(page->yOffset() - pageBleeds.top());
+			int w = static_cast<int>(page->width() + pageBleeds.left() + pageBleeds.right());
+			int h = static_cast<int>(page->height() + pageBleeds.bottom() + pageBleeds.top());
+			if (QRect(x, y, w, h).contains(qRound(x2), qRound(y2)))
+			{
+				retw = pageIndex;
+				break;
+			}
+		}
 	}
 	else
 	{
@@ -6249,15 +6297,40 @@
 
 	if (masterPageMode())
 	{
-		MarginStruct pageBleeds;
-		getBleeds(m_currentPage, pageBleeds);
-		double x1 = m_currentPage->xOffset() - pageBleeds.left();
-		double y1 = m_currentPage->yOffset() - pageBleeds.top();
-		double w1 = m_currentPage->width() + pageBleeds.left() + pageBleeds.right();
-		double h1 = m_currentPage->height() + pageBleeds.bottom() + pageBleeds.top();
-		QRectF pageRect(x1, y1, w1, h1);
-		if (itemRect.intersects(pageRect))
-			retw = m_currentPage->pageNr();
+		QUuid logicalMasterId = currItem->masterPageId();
+		if (logicalMasterId.isNull())
+			logicalMasterId = m_currentPage->masterPageId();
+		QList<int> pageIndices = masterPageIndices(logicalMasterId);
+		if (pageIndices.isEmpty())
+			pageIndices.append(m_currentPage->pageNr());
+
+		auto intersectsMasterPage = [this, &itemRect](int pageIndex)
+		{
+			MarginStruct pageBleeds;
+			const ScPage* page = MasterPages.at(pageIndex);
+			getBleeds(page, pageBleeds);
+			const QRectF pageRect(page->xOffset() - pageBleeds.left(),
+				page->yOffset() - pageBleeds.top(),
+				page->width() + pageBleeds.left() + pageBleeds.right(),
+				page->height() + pageBleeds.bottom() + pageBleeds.top());
+			return itemRect.intersects(pageRect);
+		};
+
+		// A crossing master item keeps its physical half while it still intersects
+		// that half. Geometry only chooses a new owner after the old half is left.
+		if (pageIndices.contains(currItem->OwnPage) && intersectsMasterPage(currItem->OwnPage))
+			retw = currItem->OwnPage;
+		else
+		{
+			for (int pageIndex : std::as_const(pageIndices))
+			{
+				if (intersectsMasterPage(pageIndex))
+				{
+					retw = pageIndex;
+					break;
+				}
+			}
+		}
 	}
 	else
 	{
@@ -6475,9 +6548,22 @@
 		page->setPageNr(i);
 		if (masterPageMode())
 		{
-			page->setXOffset(m_docPrefsData.displayPrefs.scratch.left());
+			double masterX = m_docPrefsData.displayPrefs.scratch.left();
+			if (page->masterPageSide() == MasterPageSide::Right && isMasterSpread(page->masterPageId()))
+			{
+				const int leftIndex = masterPageIndex(page->masterPageId(), MasterPageSide::Left);
+				if (leftIndex >= 0)
+					masterX += MasterPages.at(leftIndex)->initialWidth();
+			}
+			page->setXOffset(masterX);
 			page->setYOffset(m_docPrefsData.displayPrefs.scratch.top());
-			if (page->LeftPg == 0)
+			const MasterPageSide masterSide = page->masterPageSide();
+			if (masterSide == MasterPageSide::Left)
+			{
+				page->Margins.setLeft(page->initialMargins.right());
+				page->Margins.setRight(page->initialMargins.left());
+			}
+			else if (masterSide == MasterPageSide::Right || page->LeftPg == 0)
 			{
 				page->Margins.setRight(page->initialMargins.right());
 				page->Margins.setLeft(page->initialMargins.left());
@@ -6697,9 +6783,13 @@
 		PageLocation pageLocation = MiddlePage;
 		if (page->pageNameEmpty()) // Standard page
 			pageLocation = locationOfPage(page->pageNr());
-		else if (page->LeftPg == 1) // Left Master page
+		else if (page->masterPageSide() == MasterPageSide::Left)
+			pageLocation = LeftPage;
+		else if (page->masterPageSide() == MasterPageSide::Right)
+			pageLocation = RightPage;
+		else if (page->LeftPg == 1) // Legacy Left Master page
 			pageLocation = LeftPage;
-		else if (page->LeftPg == 0) // Right Master page
+		else if (page->LeftPg == 0) // Legacy Right Master page
 			pageLocation = RightPage;
 		else // Middle Master page
 			pageLocation = MiddlePage;
@@ -12126,6 +12216,13 @@
 
 void ScribusDoc::itemSelection_DeleteItem(Selection* customSelection, bool forceDeletion)
 {
+	itemSelection_DeleteItem(customSelection, forceDeletion, ItemDeletionSelectionPolicy::Restore, ItemDeletionOrderPolicy::OriginalIndex);
+}
+
+void ScribusDoc::itemSelection_DeleteItem(Selection* customSelection, bool forceDeletion, ItemDeletionSelectionPolicy selectionPolicy, ItemDeletionOrderPolicy orderPolicy)
+{
+	const bool restoreSelection = (selectionPolicy == ItemDeletionSelectionPolicy::Restore);
+	const bool stableItemOrder = (orderPolicy == ItemDeletionOrderPolicy::StableNeighbors);
 	if (appMode == modeEditClip)
 		return;
 	Selection* itemSelection = (customSelection != nullptr) ? customSelection : m_Selection;
@@ -12255,8 +12352,18 @@
 			auto *is = new ScItemState< QList<PageItem*> >(Um::Delete + " " + currItem->getUName(), "", Um::IDelete);
 			is->setItem(delItems);
 			is->set("DELETE_ITEM");
-			is->set("ITEMID", itemList->indexOf(currItem));
+			const int itemIndex = itemList->indexOf(currItem);
+			is->set("ITEMID", itemIndex);
 			is->set("ID", selectedItemCount - (i + 1));
+			is->set("RESTORE_SELECTION", restoreSelection);
+			is->set("STABLE_ITEM_ORDER", stableItemOrder);
+			if (stableItemOrder)
+			{
+				if (itemIndex > 0)
+					is->set("PREV_ITEM_UID", static_cast<quint64>(itemList->at(itemIndex - 1)->getUId()));
+				if (itemIndex + 1 < itemList->count())
+					is->set("NEXT_ITEM_UID", static_cast<quint64>(itemList->at(itemIndex + 1)->getUId()));
+			}
 			m_undoManager->action(Pages->at(0), is, currItem->getUPixmap());
 		}
 		itemList->removeAll(currItem);
@@ -17400,6 +17507,11 @@
 
 void ScribusDoc::setNewPrefs(const ApplicationPrefs& prefsData, const ApplicationPrefs& oldPrefsData, bool resizePages, bool resizeMasterPages, bool resizePageMargins, bool resizeMasterPageMargins)
 {
+	QList<QPointF> oldMasterOffsets;
+	oldMasterOffsets.reserve(MasterPages.count());
+	for (const ScPage* masterPage : std::as_const(MasterPages))
+		oldMasterOffsets.append(QPointF(masterPage->xOffset(), masterPage->yOffset()));
+
 	m_docPrefsData = prefsData;
 	double topDisplacement = prefsData.displayPrefs.scratch.top() - oldPrefsData.displayPrefs.scratch.top();
 	double leftDisplacement = prefsData.displayPrefs.scratch.left() - oldPrefsData.displayPrefs.scratch.left();
@@ -17409,7 +17521,15 @@
 	for (int i = 0; i < docItemsCount; ++i)
 	{
 		PageItem *item = MasterItems.at(i);
-		item->moveBy(leftDisplacement, topDisplacement);
+		double xDisplacement = leftDisplacement;
+		double yDisplacement = topDisplacement;
+		if (item->OwnPage >= 0 && item->OwnPage < MasterPages.count() && item->OwnPage < oldMasterOffsets.count())
+		{
+			const ScPage* masterPage = MasterPages.at(item->OwnPage);
+			xDisplacement = masterPage->xOffset() - oldMasterOffsets.at(item->OwnPage).x();
+			yDisplacement = masterPage->yOffset() - oldMasterOffsets.at(item->OwnPage).y();
+		}
+		item->moveBy(xDisplacement, yDisplacement);
 		item->setRedrawBounding();
 	}
 
@@ -17570,7 +17690,14 @@
 			pp->initialMargins = m_docPrefsData.docSetupPrefs.margins;
 			pp->marginPreset = m_docPrefsData.docSetupPrefs.marginPreset;
 		}
-		pp->setXOffset(scratch()->left());
+		double masterX = scratch()->left();
+		if (pp->masterPageSide() == MasterPageSide::Right && isMasterSpread(pp->masterPageId()))
+		{
+			const int leftIndex = masterPageIndex(pp->masterPageId(), MasterPageSide::Left);
+			if (leftIndex >= 0)
+				masterX += MasterPages.at(leftIndex)->initialWidth();
+		}
+		pp->setXOffset(masterX);
 		pp->setYOffset(scratch()->top());
 	}
 }
Index: scribus/scribusdoc.h
===================================================================
--- scribus/scribusdoc.h	(revision 27864)
+++ scribus/scribusdoc.h	(working copy)
@@ -35,6 +35,7 @@
 #include <QMap>
 #include <QObject>
 #include <QPixmap>
+#include <QPair>
 #include <QRectF>
 #include <QStringList>
 #include <QTimer>
@@ -262,6 +263,7 @@
 		void deletePage(int);
 		//! @brief Add a master page with this function, do not use addPage
 		ScPage* addMasterPage(int, const QString&, const QUuid& masterId = QUuid(), MasterPageSide side = MasterPageSide::Single);
+		QPair<ScPage*, ScPage*> addMasterSpread(int pageNumber, const QString& pageName);
 		void deleteMasterPage(int);
 		//! @brief Rebuild master name list
 		void rebuildMasterNames();
@@ -272,6 +274,7 @@
 		int masterPageIndex(const QUuid& masterId, MasterPageSide side) const;
 		bool masterPageHasSide(const QUuid& masterId, MasterPageSide side) const;
 		bool isMasterSpread(const QUuid& masterId) const;
+		bool isMasterItemOnCurrentMaster(const PageItem* item) const;
 		//! @brief Replace a master page by default one
 		void replaceMasterPage(const QString& oldMasterPage);
 		//! @brief Rename a master page
@@ -1169,11 +1172,24 @@
 		int columnOfPage(int pageIndex) const;
 
 
+		enum class ItemDeletionSelectionPolicy
+		{
+			Restore,
+			DoNotRestore
+		};
+
+		enum class ItemDeletionOrderPolicy
+		{
+			OriginalIndex,
+			StableNeighbors
+		};
+
 		QList<PageItem*>* groupOfItem(QList<PageItem*>* itemList, PageItem* item);
 		PageItem* groupObjectsSelection(Selection* customSelection = nullptr);
 		PageItem* groupObjectsList(QList<PageItem*> &itemList);
 		void groupObjectsToItem(PageItem* groupItem, QList<PageItem*> &itemList);
 		PageItem * itemSelection_GroupObjects  (bool changeLock, bool lock, Selection* customSelection = nullptr, PageItem_Group* groupItem = nullptr);
+		void itemSelection_DeleteItem(Selection* customSelection, bool forceDeletion, ItemDeletionSelectionPolicy selectionPolicy, ItemDeletionOrderPolicy orderPolicy);
 		void itemSelection_UnGroupObjects(Selection* customSelection = nullptr);
 		void addToGroup(PageItem* group, PageItem* item);
 		void removeFromGroup(PageItem* item);
Index: scribus/scribusview.cpp
===================================================================
--- scribus/scribusview.cpp	(revision 27864)
+++ scribus/scribusview.cpp	(working copy)
@@ -624,7 +624,7 @@
 		PageItem* item = m_doc->Items->at(i);
 		if (item->m_layerID != m_doc->activeLayer())
 			continue;
-		if ((m_doc->masterPageMode())  && (!((item->OwnPage == -1) || (item->OwnPage == m_doc->currentPage()->pageNr()))))
+		if (m_doc->masterPageMode() && !m_doc->isMasterItemOnCurrentMaster(item))
 			continue;
 		if (m_canvas->frameHitTest(dropPosDocQ, item) >= Canvas::INSIDE)
 		{
@@ -733,7 +733,7 @@
 			PageItem* item = m_doc->Items->at(i);
 			if (item->m_layerID != m_doc->activeLayer())
 				continue;
-			if ((m_doc->masterPageMode()) && (!((item->OwnPage == -1) || (item->OwnPage == m_doc->currentPage()->pageNr()))))
+			if (m_doc->masterPageMode() && !m_doc->isMasterItemOnCurrentMaster(item))
 				continue;
 			if ((m_canvas->frameHitTest(dropPosDocQ, item) >= Canvas::INSIDE) && (item->itemType() == PageItem::Symbol))
 			{
@@ -2337,7 +2337,11 @@
 	if (!m_doc->masterPageMode())
 		this->requestMode(modeNormal);
 	m_doc->setMasterPageMode(true);
-	m_doc->setCurrentPage(m_doc->Pages->at(nr));
+	ScPage* masterPage = m_doc->Pages->at(nr);
+	const int leftIndex = m_doc->masterPageIndex(masterPage->masterPageId(), MasterPageSide::Left);
+	if (leftIndex >= 0)
+		masterPage = m_doc->MasterPages.at(leftIndex);
+	m_doc->setCurrentPage(masterPage);
 	m_ScMW->pageSelector->setEnabled(false);
 	updateOn = false;
 	setZoom();
@@ -2433,11 +2437,31 @@
 {
 	ScLayer layer;
 	layer.isViewable = false;
-	int Nr = m_doc->MasterNames.value(name);
-	int clipx = static_cast<int>(m_doc->scratch()->left());
-	int clipy = static_cast<int>(m_doc->scratch()->top());
-	int clipw = qRound(m_doc->MasterPages.at(Nr)->width());
-	int cliph = qRound(m_doc->MasterPages.at(Nr)->height());
+	const QUuid masterId = m_doc->masterPageId(name);
+	QList<int> masterIndices = m_doc->masterPageIndices(masterId);
+	if (masterIndices.isEmpty())
+		return QImage();
+
+	int Nr = m_doc->masterPageIndex(masterId, MasterPageSide::Left);
+	if (Nr < 0)
+		Nr = masterIndices.first();
+	const ScPage* firstMasterPage = m_doc->MasterPages.at(masterIndices.first());
+	double minX = firstMasterPage->xOffset();
+	double minY = firstMasterPage->yOffset();
+	double maxX = firstMasterPage->xOffset() + firstMasterPage->width();
+	double maxY = firstMasterPage->yOffset() + firstMasterPage->height();
+	for (int masterIndex : std::as_const(masterIndices))
+	{
+		const ScPage* masterPage = m_doc->MasterPages.at(masterIndex);
+		minX = qMin(minX, masterPage->xOffset());
+		minY = qMin(minY, masterPage->yOffset());
+		maxX = qMax(maxX, masterPage->xOffset() + masterPage->width());
+		maxY = qMax(maxY, masterPage->yOffset() + masterPage->height());
+	}
+	int clipx = static_cast<int>(std::floor(minX));
+	int clipy = static_cast<int>(std::floor(minY));
+	int clipw = static_cast<int>(std::ceil(maxX)) - clipx;
+	int cliph = static_cast<int>(std::ceil(maxY)) - clipy;
 	if (clipw <= 0 || cliph <= 0)
 		return QImage();
 
@@ -2467,7 +2491,11 @@
 	{
 		painter->setPen(Qt::black, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
 		painter->setBrush(m_doc->paperColor());
-		painter->drawRect(clipx, clipy, clipw, cliph);
+		for (int masterIndex : std::as_const(masterIndices))
+		{
+			const ScPage* masterPage = m_doc->MasterPages.at(masterIndex);
+			painter->drawRect(masterPage->xOffset(), masterPage->yOffset(), masterPage->width(), masterPage->height());
+		}
 	}
 	painter->beginLayer(1.0, 0);
 	int layerCount = m_doc->layerCount();
@@ -3412,6 +3440,23 @@
 	if (obj == widget() && event->type() == QEvent::MouseButtonPress)
 	{
 		auto* m = dynamic_cast<QMouseEvent*> (event);
+		if (m_doc->masterPageMode() && m_doc->currentPage())
+		{
+			const auto masterId = m_doc->currentPage()->masterPageId();
+			if (m_doc->isMasterSpread(masterId))
+			{
+				const FPoint mousePoint = m_canvas->globalToCanvas(m->globalPosition());
+				const int pageIndex = m_doc->OnPage(mousePoint.x(), mousePoint.y());
+				if (pageIndex >= 0 && pageIndex < m_doc->MasterPages.count()
+					&& m_doc->MasterPages.at(pageIndex)->masterPageId() == masterId
+					&& m_doc->currentPage()->pageNr() != pageIndex)
+				{
+					m_doc->setCurrentPage(m_doc->MasterPages.at(pageIndex));
+					setRulerPos(contentsX(), contentsY());
+					m_canvas->update();
+				}
+			}
+		}
 		bool linkmode = (m_doc->appMode == modeLinkFrames);
 		firstFrame = m_doc->m_Selection->itemAt(0);
 		m_canvasMode->mousePressEvent(m);
Index: scribus/ui/mergedoc.cpp
===================================================================
--- scribus/ui/mergedoc.cpp	(revision 27864)
+++ scribus/ui/mergedoc.cpp	(working copy)
@@ -31,6 +31,8 @@
 #include <QToolTip>
 #include <QVBoxLayout>
 
+#include <utility>
+
 MergeDoc::MergeDoc(QWidget* parent, bool importMasterPages, int targetDocPageCount, int currentPage) : QDialog(parent)
 {
 	masterPages = importMasterPages;
@@ -158,9 +160,16 @@
 				importButton->setDefault(true);
 				if (masterPages)
 				{
+					m_masterPageNames = masterPageNames;
 					masterPageNameData->clear();
 					masterPageNameData->setEnabled(true);
-					masterPageNameData->addItems(masterPageNames);
+					QStringList logicalMasterNames;
+					for (const QString& masterName : std::as_const(masterPageNames))
+					{
+						if (!logicalMasterNames.contains(masterName))
+							logicalMasterNames.append(masterName);
+					}
+					masterPageNameData->addItems(logicalMasterNames);
 					masterPageNameData->setCurrentRow(0);
 				}
 				else
@@ -181,6 +190,7 @@
 		cancelButton->setDefault(true);
 		if (masterPages)
 		{
+			m_masterPageNames.clear();
 			masterPageNameData->clear();
 			masterPageNameData->setEnabled(false);
 		}
@@ -230,8 +240,25 @@
 QList<int> MergeDoc::getMasterPageIndexes() const
 {
 	QList<int> result;
-	for (const auto& row: masterPageNameData->selectionModel()->selectedRows())
-		result << row.row();
+	const QList<QList<int>> groups = getMasterPageIndexGroups();
+	for (const QList<int>& group : groups)
+		result.append(group);
+	return result;
+}
+
+QList<QList<int>> MergeDoc::getMasterPageIndexGroups() const
+{
+	QList<QList<int>> result;
+	for (QListWidgetItem* item : masterPageNameData->selectedItems())
+	{
+		QList<int> indices;
+		for (int i = 0; i < m_masterPageNames.count(); ++i)
+		{
+			if (m_masterPageNames.at(i) == item->text())
+				indices.append(i);
+		}
+		result.append(indices);
+	}
 	return result;
 }
 
Index: scribus/ui/mergedoc.h
===================================================================
--- scribus/ui/mergedoc.h	(revision 27864)
+++ scribus/ui/mergedoc.h	(working copy)
@@ -12,6 +12,8 @@
 
 #include <QDialog>
 #include <QDialogButtonBox>
+#include <QList>
+#include <QStringList>
 #include "scribusapi.h"
 
 class QCheckBox;
@@ -38,6 +40,7 @@
 	QString getFromDoc() const;
 	QStringList getMasterPageNames() const;
 	QList<int> getMasterPageIndexes() const;
+	QList<QList<int>> getMasterPageIndexGroups() const;
 	int getImportWhere() const;
 	int getImportWherePage() const;
 	bool getCreatePageChecked() const;
@@ -62,6 +65,7 @@
 	QGridLayout* fromInfoLayout {nullptr};
 	int count {0};
 	bool masterPages {false};
+	QStringList m_masterPageNames;
 
 private slots:
 	void changeFile();
Index: scribus/ui/newtemp.cpp
===================================================================
--- scribus/ui/newtemp.cpp	(revision 27864)
+++ scribus/ui/newtemp.cpp	(working copy)
@@ -6,14 +6,18 @@
 */
 #include "newtemp.h"
 
-#include <QPixmap>
-#include <QSpacerItem>
-#include <QHBoxLayout>
-#include <QVBoxLayout>
-#include <QLineEdit>
+#include <QButtonGroup>
 #include <QComboBox>
+#include <QFont>
+#include <QGridLayout>
+#include <QHBoxLayout>
 #include <QLabel>
+#include <QLineEdit>
 #include <QPushButton>
+#include <QSpacerItem>
+#include <QStyle>
+#include <QToolButton>
+#include <QVBoxLayout>
 
 #include "commonstrings.h"
 #include "iconmanager.h"
@@ -21,72 +25,201 @@
 #include "scpage.h"
 #include "scribusdoc.h"
 
-NewTm::NewTm( QWidget* parent, const QString& text, const QString& title, ScribusDoc *doc, const QString& answerText)
+NewTm::NewTm( QWidget* parent, const QString& text, const QString& title, ScribusDoc *doc, const QString& answerText, bool allowFacingSpread)
 		: QDialog( parent )
 {
 	setModal(true);
 	setWindowTitle(title);
 	setWindowIcon(IconManager::instance().loadIcon("app-icon"));
-	QueryLayout = new QVBoxLayout( this );
+	QueryLayout = new QVBoxLayout(this);
 	QueryLayout->setSpacing(6);
 	QueryLayout->setContentsMargins(9, 9, 9, 9);
-	Layout2 = new QHBoxLayout;
-	Layout2->setSpacing(6);
-	Layout2->setContentsMargins(0, 0, 0, 0);
-	Answer = new QLineEdit( this );
-	Frage = new QLabel( text, this );
+
+	int layoutSpacing = style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing, nullptr, this);
+	if (layoutSpacing < 0)
+		layoutSpacing = QueryLayout->spacing();
+	const int compactSpacing = qMax(1, layoutSpacing / 2);
+	const int fieldSpacing = qMax(layoutSpacing * 2, fontMetrics().height() / 2);
+
+	QFont compactButtonFont(font());
+	if (compactButtonFont.pointSizeF() > 0)
+		compactButtonFont.setPointSizeF(compactButtonFont.pointSizeF() * 0.85);
+	else if (compactButtonFont.pixelSize() > 0)
+		compactButtonFont.setPixelSize(qMax(1, qRound(compactButtonFont.pixelSize() * 0.85)));
+
+	FormLayout = new QGridLayout;
+	FormLayout->setContentsMargins(0, 0, 0, 0);
+	FormLayout->setHorizontalSpacing(layoutSpacing);
+	FormLayout->setVerticalSpacing(fieldSpacing);
+
+	Answer = new QLineEdit(this);
+	Frage = new QLabel(text, this);
 	Frage->setBuddy(Answer);
-	Frage->adjustSize();
-	Layout2->addWidget( Frage );
-	Layout2->addWidget( Answer );
-	QueryLayout->addLayout( Layout2 );
+	FormLayout->addWidget(Frage, 0, 0, Qt::AlignRight | Qt::AlignVCenter);
+	FormLayout->addWidget(Answer, 0, 1);
+
+	const int columns = doc->pageSets()[doc->pagePositioning()].Columns;
+	const bool twoPageFacing = (columns == 2);
 
-	Layout3 = nullptr;
-	Links = nullptr;
 	if (doc->pagePositioning() != singlePage)
 	{
-		Layout3 = new QHBoxLayout;
-		Layout3->setSpacing(6);
-		Layout3->setContentsMargins(0, 0, 0, 0);
-		Links = new QComboBox( this );
-		QStringList::Iterator pNames;
-		QList<PageSet> pageSet(doc->pageSets());
-		for (pNames = pageSet[doc->pagePositioning()].pageNames.begin(); pNames != pageSet[doc->pagePositioning()].pageNames.end(); ++pNames )
+		const QList<PageSet>& pageSet = doc->pageSets();
+		if (twoPageFacing)
 		{
-			//Links->insertItem((*pNames));
-			Links->addItem(CommonStrings::translatePageSetLocString((*pNames)));
+			TypeLabel = new QLabel(tr("&Type:"), this);
+			ChoiceLayout = new QHBoxLayout;
+			ChoiceLayout->setSpacing(compactSpacing);
+			ChoiceLayout->setContentsMargins(0, 0, 0, 0);
+
+			TypeButtons = new QButtonGroup(this);
+			TypeButtons->setExclusive(true);
+
+			if (allowFacingSpread)
+			{
+				SpreadButton = new QToolButton(this);
+				SpreadButton->setText(tr("Spread"));
+				SpreadButton->setToolTip(tr("Create a two-page facing master."));
+				SpreadButton->setToolButtonStyle(Qt::ToolButtonTextOnly);
+				SpreadButton->setFont(compactButtonFont);
+				SpreadButton->setCheckable(true);
+				ChoiceLayout->addWidget(SpreadButton);
+				TypeButtons->addButton(SpreadButton, 2);
+			}
+
+			LeftButton = new QToolButton(this);
+			LeftButton->setText(tr("Single Left"));
+			LeftButton->setToolTip(tr("Create a single-page master for left-hand pages."));
+			LeftButton->setToolButtonStyle(Qt::ToolButtonTextOnly);
+			LeftButton->setFont(compactButtonFont);
+			LeftButton->setCheckable(true);
+			ChoiceLayout->addWidget(LeftButton);
+			TypeButtons->addButton(LeftButton, 0);
+
+			RightButton = new QToolButton(this);
+			RightButton->setText(tr("Single Right"));
+			RightButton->setToolTip(tr("Create a single-page master for right-hand pages."));
+			RightButton->setToolButtonStyle(Qt::ToolButtonTextOnly);
+			RightButton->setFont(compactButtonFont);
+			RightButton->setCheckable(true);
+			ChoiceLayout->addWidget(RightButton);
+			TypeButtons->addButton(RightButton, 1);
+			ChoiceLayout->addStretch(1);
+
+			if (doc->currentPage()->LeftPg == 1)
+				LeftButton->setChecked(true);
+			else
+				RightButton->setChecked(true);
+
+			TypeLabel->setBuddy(SpreadButton ? SpreadButton : LeftButton);
+			FormLayout->addWidget(TypeLabel, 1, 0, Qt::AlignRight | Qt::AlignVCenter);
+			FormLayout->addLayout(ChoiceLayout, 1, 1);
 		}
-		if (doc->currentPage()->LeftPg == 1)
-			Links->setCurrentIndex(0);
-		else if (doc->currentPage()->LeftPg == 0)
-			Links->setCurrentIndex(Links->count()-1);
 		else
-			Links->setCurrentIndex(doc->currentPage()->LeftPg-1);
-		Links->setEditable(false);
-		Layout3->addWidget( Links );
-		QueryLayout->addLayout( Layout3 );
+		{
+			SideLabel = new QLabel(tr("&Page side:"), this);
+			Links = new QComboBox(this);
+			SideLabel->setBuddy(Links);
+			for (auto pNames = pageSet[doc->pagePositioning()].pageNames.cbegin(); pNames != pageSet[doc->pagePositioning()].pageNames.cend(); ++pNames)
+				Links->addItem(CommonStrings::translatePageSetLocString((*pNames)));
+			if (doc->currentPage()->LeftPg == 1)
+				Links->setCurrentIndex(0);
+			else if (doc->currentPage()->LeftPg == 0)
+				Links->setCurrentIndex(Links->count()-1);
+			else
+				Links->setCurrentIndex(doc->currentPage()->LeftPg-1);
+			Links->setEditable(false);
+			FormLayout->addWidget(SideLabel, 1, 0, Qt::AlignRight | Qt::AlignVCenter);
+			FormLayout->addWidget(Links, 1, 1);
+		}
 	}
+
+	// Keep the form labels on one visual grid so their colons line up exactly.
+	QLabel* secondRowLabel = TypeLabel ? TypeLabel : SideLabel;
+	if (secondRowLabel)
+	{
+		const int labelWidth = qMax(Frage->sizeHint().width(), secondRowLabel->sizeHint().width());
+		Frage->setMinimumWidth(labelWidth);
+		Frage->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+		secondRowLabel->setMinimumWidth(labelWidth);
+		secondRowLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+	}
+
+	QueryLayout->addLayout(FormLayout);
+	// Keep the top form together, but give it clear separation from the
+	// action row. Extra dialog height is absorbed below this fixed gap.
+	const int actionSpacing = qMax(fieldSpacing * 2, fontMetrics().height());
+	QueryLayout->addSpacing(actionSpacing);
+	QueryLayout->addStretch(1);
+
 	Layout1 = new QHBoxLayout;
 	Layout1->setSpacing(6);
 	Layout1->setContentsMargins(0, 0, 0, 0);
-	QSpacerItem* spacer = new QSpacerItem( 2, 2, QSizePolicy::Expanding, QSizePolicy::Minimum );
-	Layout1->addItem( spacer );
-	PushButton1 = new QPushButton( CommonStrings::tr_OK, this );
-	PushButton1->setDefault( true );
-	Layout1->addWidget( PushButton1 );
-	PushButton2 = new QPushButton( CommonStrings::tr_Cancel, this );
-	Layout1->addWidget( PushButton2 );
-	QueryLayout->addLayout( Layout1 );
+	QSpacerItem* spacer = new QSpacerItem(2, 2, QSizePolicy::Expanding, QSizePolicy::Minimum);
+	Layout1->addItem(spacer);
+	PushButton1 = new QPushButton(CommonStrings::tr_OK, this);
+	PushButton1->setDefault(true);
+	Layout1->addWidget(PushButton1);
+	PushButton2 = new QPushButton(CommonStrings::tr_Cancel, this);
+	Layout1->addWidget(PushButton2);
+	QueryLayout->addLayout(Layout1);
+
 	if (!answerText.isEmpty())
 	{
 		Answer->setText(answerText);
-		Answer->setMinimumWidth(sizeHint().width());
 		Answer->selectAll();
 	}
-	setMinimumSize(sizeHint());
+
+	// Keep these dialogs compact while still allowing translated labels and
+	// larger application fonts to determine a wider natural size when needed.
+	int answerMinimumWidth = fontMetrics().horizontalAdvance(QLatin1Char('M')) * 19;
+	if (ChoiceLayout)
+		answerMinimumWidth = qMax(answerMinimumWidth, ChoiceLayout->sizeHint().width());
+	else if (Links)
+		answerMinimumWidth = qMax(answerMinimumWidth, Links->sizeHint().width());
+	Answer->setMinimumWidth(qMax(Answer->minimumSizeHint().width(), answerMinimumWidth));
+	QueryLayout->activate();
+	adjustSize();
+	setMinimumWidth(sizeHint().width());
 	Answer->setFocus();
 
 	// signals and slots connections
-	connect( PushButton1, SIGNAL( clicked() ), this, SLOT( accept() ) );
-	connect( PushButton2, SIGNAL( clicked() ), this, SLOT( reject() ) );
+	connect(PushButton1, SIGNAL(clicked()), this, SLOT(accept()));
+	connect(PushButton2, SIGNAL(clicked()), this, SLOT(reject()));
+}
+
+
+bool NewTm::facingPages() const
+{
+	return TypeButtons && TypeButtons->checkedId() == 2;
+}
+
+int NewTm::pageSideIndex() const
+{
+	if (TypeButtons)
+	{
+		const int index = TypeButtons->checkedId();
+		return (index == 0 || index == 1) ? index : -1;
+	}
+	return Links ? Links->currentIndex() : -1;
+}
+
+int NewTm::pageSideCount() const
+{
+	if (TypeButtons && LeftButton && RightButton)
+		return 2;
+	return Links ? Links->count() : 0;
+}
+
+void NewTm::setPageSideVisible(bool visible)
+{
+	if (LeftButton)
+		LeftButton->setVisible(visible);
+	if (RightButton)
+		RightButton->setVisible(visible);
+	if (TypeLabel && !SpreadButton)
+		TypeLabel->setVisible(visible);
+	if (SideLabel)
+		SideLabel->setVisible(visible);
+	if (Links)
+		Links->setVisible(visible);
 }
Index: scribus/ui/newtemp.h
===================================================================
--- scribus/ui/newtemp.h	(revision 27864)
+++ scribus/ui/newtemp.h	(working copy)
@@ -8,12 +8,15 @@
 #define QUERY2_H
 
 #include <QDialog>
-class QHBoxLayout;
-class QVBoxLayout;
-class QLineEdit;
+class QButtonGroup;
 class QComboBox;
+class QGridLayout;
+class QHBoxLayout;
 class QLabel;
+class QLineEdit;
 class QPushButton;
+class QToolButton;
+class QVBoxLayout;
 
 #include "scribusapi.h"
 class ScribusDoc;
@@ -23,20 +26,30 @@
 	Q_OBJECT
 
 public:
-	NewTm(QWidget* parent, const QString& text, const QString& title, ScribusDoc* doc, const QString& answerText = QString());
+	NewTm(QWidget* parent, const QString& text, const QString& title, ScribusDoc* doc, const QString& answerText = QString(), bool allowFacingSpread = false);
 	~NewTm() {};
+	bool facingPages() const;
+	int pageSideIndex() const;
+	int pageSideCount() const;
+	void setPageSideVisible(bool visible);
 
 	QPushButton* PushButton1;
 	QPushButton* PushButton2;
 	QLineEdit* Answer;
 	QLabel* Frage;
-	QComboBox* Links;
 
 protected:
 	QVBoxLayout* QueryLayout;
-	QHBoxLayout* Layout3;
-	QHBoxLayout* Layout2;
+	QGridLayout* FormLayout { nullptr };
+	QHBoxLayout* ChoiceLayout { nullptr };
 	QHBoxLayout* Layout1;
+	QLabel* TypeLabel { nullptr };
+	QLabel* SideLabel { nullptr };
+	QComboBox* Links { nullptr };
+	QButtonGroup* TypeButtons { nullptr };
+	QToolButton* SpreadButton { nullptr };
+	QToolButton* LeftButton { nullptr };
+	QToolButton* RightButton { nullptr };
 };
 
 #endif // QUERY_H
Index: scribus/ui/pagepalette_masterpages.cpp
===================================================================
--- scribus/ui/pagepalette_masterpages.cpp	(revision 27864)
+++ scribus/ui/pagepalette_masterpages.cpp	(working copy)
@@ -189,12 +189,7 @@
 
 		if (m_doc->appMode == modeEditClip)
 			m_view->requestMode(submodeEndNodeEdit);
-		m_doc->scMW()->deletePage2(m_doc->MasterNames[m_currentPage]);
-		//<<CB TODO Move back into ScribusDoc::deleteMasterPage();
-		//This must happen after the pages have been reformed (view/doc)
-		m_doc->rebuildMasterNames();
-		// Fix up any pages that refer to the deleted master page
-		m_doc->replaceMasterPage(m_currentPage);
+		m_doc->scMW()->deleteMasterPage(m_currentPage);
 	}
 
 	// set the 1st MP for the other slots
@@ -206,8 +201,14 @@
 
 void PagePalette_MasterPages::duplicateMasterPage()
 {
+	const QString sourceName = m_currentPage;
+	const QUuid sourceId = m_doc->masterPageId(sourceName);
+	const bool sourceIsSpread = m_doc->isMasterSpread(sourceId);
+
 	QString name = getUniqueName(m_currentPage, m_doc->MasterNames);
 	QScopedPointer<NewTm> dia(new NewTm(this, tr("&Name:"), tr("New Master Page"), m_doc, name));
+	if (sourceIsSpread)
+		dia->setPageSideVisible(false);
 	if (!dia->exec())
 		return;
 
@@ -222,37 +223,73 @@
 	name = getNonReservedName(name);
 	name = getUniqueName(name, m_doc->MasterNames);
 
-//	PrefsManager& prefsManager = PrefsManager::instance();
-	int inde = m_doc->MasterNames.value(m_currentPage);
-	int nr = m_doc->Pages->count();
-	ScPage* from = m_doc->Pages->at(inde);
-	ScPage* destination = m_doc->addMasterPage(nr, name);
-	if (m_doc->pagePositioning() != singlePage)
-	{
-		int lp = dia->Links->currentIndex();
-		if (lp == 0)
-			lp = 1;
-		else if (lp == static_cast<int>(dia->Links->count()-1))
-			lp = 0;
-		else
-			lp++;
-		destination->LeftPg = lp;
-	}
-	destination->initialMargins.setTop(from->initialMargins.top());
-	destination->initialMargins.setBottom(from->initialMargins.bottom());
-	if (m_doc->pageSets()[m_doc->pagePositioning()].Columns == 1)
+	const int nr = m_doc->Pages->count();
+	ScPage* destination = nullptr;
+	int sourceLeftIndex = -1;
+	int sourceRightIndex = -1;
+	int destinationLeftIndex = -1;
+	int destinationRightIndex = -1;
+
+	if (sourceIsSpread)
 	{
-		destination->initialMargins.setLeft(from->initialMargins.left());
-		destination->initialMargins.setRight(from->initialMargins.right());
+		const auto spread = m_doc->addMasterSpread(nr, name);
+		ScPage* destLeft = spread.first;
+		ScPage* destRight = spread.second;
+		sourceLeftIndex = m_doc->masterPageIndex(sourceId, MasterPageSide::Left);
+		sourceRightIndex = m_doc->masterPageIndex(sourceId, MasterPageSide::Right);
+		destinationLeftIndex = destLeft->pageNr();
+		destinationRightIndex = destRight->pageNr();
+		if (sourceLeftIndex >= 0)
+		{
+			const ScPage* sourceLeft = m_doc->MasterPages.at(sourceLeftIndex);
+			destLeft->copySizingProperties(sourceLeft, sourceLeft->Margins);
+			sourceLeft->guides.copy(&destLeft->guides);
+		}
+		if (sourceRightIndex >= 0)
+		{
+			const ScPage* sourceRight = m_doc->MasterPages.at(sourceRightIndex);
+			destRight->copySizingProperties(sourceRight, sourceRight->Margins);
+			sourceRight->guides.copy(&destRight->guides);
+		}
+		destination = destLeft;
 	}
 	else
 	{
-		if (destination->LeftPg != from->LeftPg)
+		const int sourceIndex = m_doc->masterPageIndex(sourceId);
+		ScPage* from = m_doc->Pages->at(sourceIndex);
+		destination = m_doc->addMasterPage(nr, name);
+		if (m_doc->pagePositioning() != singlePage)
 		{
-			if (destination->LeftPg > 1)
+			int lp = dia->pageSideIndex();
+			if (lp == 0)
+				lp = 1;
+			else if (lp == dia->pageSideCount()-1)
+				lp = 0;
+			else
+				lp++;
+			destination->LeftPg = lp;
+		}
+		destination->initialMargins.setTop(from->initialMargins.top());
+		destination->initialMargins.setBottom(from->initialMargins.bottom());
+		if (m_doc->pageSets()[m_doc->pagePositioning()].Columns == 1)
+		{
+			destination->initialMargins.setLeft(from->initialMargins.left());
+			destination->initialMargins.setRight(from->initialMargins.right());
+		}
+		else
+		{
+			if (destination->LeftPg != from->LeftPg)
 			{
-				destination->initialMargins.setRight(from->initialMargins.left());
-				destination->initialMargins.setLeft(from->initialMargins.left());
+				if (destination->LeftPg > 1)
+				{
+					destination->initialMargins.setRight(from->initialMargins.left());
+					destination->initialMargins.setLeft(from->initialMargins.left());
+				}
+				else
+				{
+					destination->initialMargins.setLeft(from->initialMargins.left());
+					destination->initialMargins.setRight(from->initialMargins.right());
+				}
 			}
 			else
 			{
@@ -260,21 +297,19 @@
 				destination->initialMargins.setRight(from->initialMargins.right());
 			}
 		}
-		else
-		{
-			destination->initialMargins.setLeft(from->initialMargins.left());
-			destination->initialMargins.setRight(from->initialMargins.right());
-		}
+		from->guides.copy(&destination->guides);
 	}
-	//#8321 : incorrect selection of master page on new mp creation/duplication
-	//m_doc->setCurrentPage(destination);
+
+	m_doc->setCurrentPage(destination);
+	m_view->reformPages();
 	updateMasterPageList(name);
 	selectMasterPage(name);
+
 	uint oldItems = m_doc->Items->count();
 	uint end2 = m_doc->MasterItems.count();
 	int GrMax = m_doc->GroupCounter;
 	m_doc->m_Selection->clear();
-	if (oldItems>0)
+	if (oldItems > 0)
 	{
 		ScLayers::iterator it;
 		m_doc->m_Selection->delaySignalsOn();
@@ -282,8 +317,10 @@
 		{
 			for (uint ite = 0; ite < oldItems; ++ite)
 			{
-				PageItem *itemToCopy = m_doc->Items->at(ite);
-				if (itemToCopy->OwnPage == inde && (it->ID == itemToCopy->m_layerID))
+				PageItem* itemToCopy = m_doc->Items->at(ite);
+				const bool sourceItem = (!sourceId.isNull() && itemToCopy->masterPageId() == sourceId)
+					|| (itemToCopy->masterPageId().isNull() && itemToCopy->OnMasterPage == sourceName);
+				if (sourceItem && it->ID == itemToCopy->m_layerID)
 					m_doc->m_Selection->addItem(itemToCopy);
 			}
 			if (m_doc->m_Selection->count() != 0)
@@ -297,26 +334,41 @@
 		m_doc->m_Selection->clear();
 		m_doc->m_Selection->delaySignalsOff();
 	}
+
+	const QUuid destinationId = destination->masterPageId();
 	uint end3 = m_doc->MasterItems.count();
 	for (uint a = end2; a < end3; ++a)
 	{
-		PageItem *newItem = m_doc->MasterItems.at(a);
-		int masterPageIndex = m_doc->MasterNames.value(name);
-		newItem->setMasterPage(masterPageIndex, name);
+		PageItem* newItem = m_doc->MasterItems.at(a);
+		const int sourceOwner = newItem->OwnPage;
+		newItem->setMasterPageId(destinationId);
+		newItem->setMasterPageName(name);
+		if (sourceIsSpread && sourceOwner == sourceLeftIndex)
+			newItem->OwnPage = destinationLeftIndex;
+		else if (sourceIsSpread && sourceOwner == sourceRightIndex)
+			newItem->OwnPage = destinationRightIndex;
+		else
+			newItem->OwnPage = m_doc->OnPage(newItem);
 	}
-	from->guides.copy(&destination->guides);
+
 	m_doc->GroupCounter = GrMax + 1;
 	m_view->deselectItems(true);
 	m_doc->setLoading(false);
 	m_view->reformPages();
 	m_view->DrawNew();
 }
-
 void PagePalette_MasterPages::newMasterPage()
 {
 	int nr = m_doc->Pages->count();
+	int masterNumber = 1;
+	QString defaultName;
+	do
+	{
+		defaultName = tr("Master %1").arg(masterNumber++);
+	}
+	while (m_doc->MasterNames.contains(defaultName));
 
-	QScopedPointer<NewTm> dia(new NewTm(this, tr("Name:"), tr("New Master Page"), m_doc, tr("New Master Page %1").arg(nr)));
+	QScopedPointer<NewTm> dia(new NewTm(this, tr("Name:"), tr("New Master Page"), m_doc, defaultName, true));
 	if (!dia->exec())
 		return;
 
@@ -331,25 +383,31 @@
 	name = getNonReservedName(name);
 	name = getUniqueName(name, m_doc->MasterNames);
 
-	m_doc->setCurrentPage(m_doc->addMasterPage(nr, name));
-	if (m_doc->pagePositioning() != singlePage)
+	if (dia->facingPages())
 	{
-		int lp = dia->Links->currentIndex();
-		if (lp == 0)
-			lp = 1;
-		else if (lp == static_cast<int>(dia->Links->count()-1))
-			lp = 0;
-		else
-			lp++;
-		m_doc->Pages->at(nr)->LeftPg = lp;
+		const auto spread = m_doc->addMasterSpread(nr, name);
+		m_doc->setCurrentPage(spread.first);
+	}
+	else
+	{
+		m_doc->setCurrentPage(m_doc->addMasterPage(nr, name));
+		if (m_doc->pagePositioning() != singlePage)
+		{
+			int lp = dia->pageSideIndex();
+			if (lp == 0)
+				lp = 1;
+			else if (lp == dia->pageSideCount()-1)
+				lp = 0;
+			else
+				lp++;
+			m_doc->Pages->at(nr)->LeftPg = lp;
+		}
 	}
+
+	m_view->reformPages();
 	updateMasterPageList(name);
-	//#8321 : incorrect selection of master page on new mp creation/duplictation
-	//m_view->showMasterPage(m_doc->MasterNames[name]);
 	selectMasterPage(name);
-	m_view->reformPages();
 }
-
 void PagePalette_MasterPages::importPage()
 {
 	QScopedPointer<MergeDoc> dia(new MergeDoc(this, true));
@@ -362,22 +420,40 @@
 	qApp->setOverrideCursor(QCursor(Qt::WaitCursor));
 	int nr = m_doc->Pages->count();
 
-	auto indexes = dia->getMasterPageIndexes();
+	const QStringList selectedNames = dia->getMasterPageNames();
+	const QList<QList<int>> indexGroups = dia->getMasterPageIndexGroups();
 	QString lastName;
-	for (auto name: dia->getMasterPageNames())
+	for (int selectionIndex = 0; selectionIndex < selectedNames.count(); ++selectionIndex)
 	{
-		name = getUniqueName(name, m_doc->MasterNames);
-		m_doc->setCurrentPage(m_doc->addMasterPage(nr, name));
+		QString name = getUniqueName(selectedNames.at(selectionIndex), m_doc->MasterNames);
+		const QList<int> sourceIndices = indexGroups.value(selectionIndex);
+		if (sourceIndices.isEmpty())
+			continue;
 
-		nr++;
-		qApp->processEvents();
+		QList<ScPage*> destinationPages;
+		if (sourceIndices.count() == 2)
+		{
+			const auto spread = m_doc->addMasterSpread(nr, name);
+			destinationPages << spread.first << spread.second;
+			nr += 2;
+		}
+		else
+		{
+			destinationPages << m_doc->addMasterPage(nr, name);
+			++nr;
+		}
 
-		//CB TODO: If we are loading to a new name, we rely on doc->onpage in
-		//FileLoader::PasteItem as this call doesn't pass in the new destination page
-		m_doc->scMW()->loadPage(dia->getFromDoc(), indexes.first(), true, name);
-		qApp->processEvents();
+		for (int sourceOffset = 0; sourceOffset < sourceIndices.count() && sourceOffset < destinationPages.count(); ++sourceOffset)
+		{
+			m_doc->setCurrentPage(destinationPages.at(sourceOffset));
+			qApp->processEvents();
+
+			//CB TODO: If we are loading to a new name, we rely on doc->onpage in
+			//FileLoader::PasteItem as this call doesn't pass in the new destination page
+			m_doc->scMW()->loadPage(dia->getFromDoc(), sourceIndices.at(sourceOffset), true, name);
+			qApp->processEvents();
+		}
 
-		indexes.removeFirst();
 		lastName = name;
 	}
 
@@ -462,6 +538,10 @@
 		if (it.key() == CommonStrings::masterPageNormal)
 			mpName = CommonStrings::trMasterPageNormal;
 		QListWidgetItem* mpItem = new QListWidgetItem(mpName);
+		const QUuid masterId = m_doc->masterPageId(it.key());
+		const bool spread = m_doc->isMasterSpread(masterId);
+		mpItem->setIcon(IconManager::instance().loadIcon(spread ? "page-type-facing" : "page-type-single"));
+		mpItem->setToolTip(spread ? tr("Facing master spread") : tr("One-page master"));
 
 		if (!isReservedName(mpName))
 			mpItem->setFlags(mpItem->flags() |= Qt::ItemIsEditable);
Index: scribus/ui/pagepalette_pages.cpp
===================================================================
--- scribus/ui/pagepalette_pages.cpp	(revision 27864)
+++ scribus/ui/pagepalette_pages.cpp	(working copy)
@@ -103,19 +103,11 @@
 									 QMessageBox::Yes);     // batch default
 	if (exit == QMessageBox::Yes)
 	{
-		bool oldMPMode = currView->m_doc->masterPageMode();
 		int storedPageNum = currView->m_doc->currentPageNumber();
 		int storedViewXCoor = currView->horizontalScrollBar()->value();
 		int storedViewYCoor = currView->verticalScrollBar()->value();
 
-		currView->m_doc->setMasterPageMode(true);
-		currView->m_doc->scMW()->deletePage2(currView->m_doc->MasterNames[tmp]);
-		//<<CB TODO Move back into ScribusDoc::deleteMasterPage();
-		//This must happen after the pages have been reformed (view/doc)
-		currView->m_doc->rebuildMasterNames();
-		// Fix up any pages that refer to the deleted master page
-		currView->m_doc->replaceMasterPage(tmp);
-		currView->m_doc->setMasterPageMode(oldMPMode);
+		currView->m_doc->scMW()->deleteMasterPage(tmp);
 		currView->m_doc->setModified(true);
 
 		currView->m_doc->setCurrentPage(currView->m_doc->DocPages.at(storedPageNum));
Index: scribus/ui/pagepropertiesdialog.cpp
===================================================================
--- scribus/ui/pagepropertiesdialog.cpp	(revision 27864)
+++ scribus/ui/pagepropertiesdialog.cpp	(working copy)
@@ -93,12 +93,18 @@
 		}
 		Links->setEditable(false);
 		dsGroupBox7Layout->addWidget( Links, 5, 1);
-		if (doc->currentPage()->LeftPg == 0)
+		const MasterPageSide masterSide = doc->currentPage()->masterPageSide();
+		if (masterSide == MasterPageSide::Left)
+			Links->setCurrentIndex(0);
+		else if (masterSide == MasterPageSide::Right)
+			Links->setCurrentIndex(Links->count() - 1);
+		else if (doc->currentPage()->LeftPg == 0)
 			Links->setCurrentIndex(Links->count()-1);
 		else if (doc->currentPage()->LeftPg == 1)
 			Links->setCurrentIndex(0);
 		else
 			Links->setCurrentIndex(doc->currentPage()->LeftPg-1);
+		Links->setEnabled(masterSide == MasterPageSide::Single);
 	}
 	dialogLayout->addWidget( dsGroupBox7 );
 	
tsm2-spread-editor-v1.16.patch (76,195 bytes)   
tsm3-application-reflow-v1.05.patch (31,571 bytes)   
Index: scribus/canvas.cpp
===================================================================
--- scribus/canvas.cpp	(revision 27864)
+++ scribus/canvas.cpp	(working copy)
@@ -408,7 +408,10 @@ PageItem* Canvas::itemUnderCursor(QPointF globalPos, PageItem* itemAbove, bool a
 	// look for masterpage items first
 	if (allowMasterItems && !m_doc->masterPageMode() && m_doc->currentPage()->FromMaster.count() != 0)
 	{
-		const ScPage* Mp = m_doc->MasterPages.at(m_doc->MasterNames.value(m_doc->currentPage()->masterPageName()));
+		const int masterIndex = m_doc->appliedMasterPageIndex(m_doc->currentPage());
+		if (masterIndex < 0)
+			return nullptr;
+		const ScPage* Mp = m_doc->MasterPages.at(masterIndex);
 		// if itemAbove is given, we expect to find it among the masterpage items of this page
 		int currNr = itemAbove ? m_doc->currentPage()->FromMaster.indexOf(itemAbove) - 1 : m_doc->currentPage()->FromMaster.count() - 1;
 		if (currNr < 0)
@@ -1331,7 +1334,10 @@ void Canvas::DrawMasterItems(ScPainter *painter, const ScPage *page, const ScLay
 	                   qRound(clip.width() / m_viewMode.scale + 0.5), qRound(clip.height() / m_viewMode.scale + 0.5));
 
 	PageItem *currItem;
-	ScPage* Mp = m_doc->MasterPages.at(m_doc->MasterNames.value(page->masterPageName()));
+	const int masterIndex = m_doc->appliedMasterPageIndex(page);
+	if (masterIndex < 0)
+		return;
+	ScPage* Mp = m_doc->MasterPages.at(masterIndex);
 	if (((layer.blendMode != 0) || (layer.transparency != 1.0)) && (!layer.outlineMode))
 		painter->beginLayer(layer.transparency, layer.blendMode);
 	int pageFromMasterCount = page->FromMaster.count();
Index: scribus/documentchecker.cpp
===================================================================
--- scribus/documentchecker.cpp	(revision 27864)
+++ scribus/documentchecker.cpp	(working copy)
@@ -115,12 +115,29 @@ void DocumentChecker::checkPages(ScribusDoc *currDoc, const CheckerPrefs& checke
 		if (checkerSettings.checkAppliedMasterDifferentSide)
 		{
 			bool error = false;
-			int masterPageNumber = -1, masterPageLocation = -1;
+			int masterPageLocation = -1;
+			MasterPageSide masterSide = MasterPageSide::Single;
 			PageLocation pageLoc = currDoc->locationOfPage(i);
-			masterPageNumber = currDoc->MasterNames.value(currDoc->DocPages[i]->masterPageName(), -1);
+			const int masterPageNumber = currDoc->appliedMasterPageIndex(currDoc->DocPages.at(i));
 			if (masterPageNumber >= 0)
-				masterPageLocation = currDoc->MasterPages[masterPageNumber]->LeftPg;
-			if (currDoc->pagePositioning() == singlePage)
+			{
+				const ScPage* masterPage = currDoc->MasterPages.at(masterPageNumber);
+				masterPageLocation = masterPage->LeftPg;
+				masterSide = masterPage->masterPageSide();
+			}
+			const bool explicitMasterSide = masterSide == MasterPageSide::Left || masterSide == MasterPageSide::Right;
+			if (currDoc->pagePositioning() == singlePage && explicitMasterSide)
+				error = false;
+			else if (explicitMasterSide)
+			{
+				if (pageLoc == LeftPage)
+					error = masterSide != MasterPageSide::Left;
+				else if (pageLoc == RightPage)
+					error = masterSide != MasterPageSide::Right;
+				else
+					error = true;
+			}
+			else if (currDoc->pagePositioning() == singlePage)
 			{
 				if ((pageLoc != LeftPage) || (masterPageLocation != 0))
 					error = true;
Index: scribus/scribus.cpp
===================================================================
--- scribus/scribus.cpp	(revision 27864)
+++ scribus/scribus.cpp	(working copy)
@@ -5287,6 +5287,8 @@ void ScribusMainWindow::slotNewPageP(int wo, const QString& templ)
 		doc->addPageToSection(wo + 1, where, 1);
 	}
 
+	doc->reconcileMasterPageSides();
+	view->reformPages(false);
 	doc->updateEndnotesFrames();
 	doc->changed();
 	updateGUIAfterPagesChanged();
@@ -5419,6 +5421,8 @@ void ScribusMainWindow::addNewPages(int wo, int where, int numPages, double heig
 	doc->changed();
 	doc->addPageToAnnotLinks(wot, where, numPages);
 	doc->addPageToSection(wo, where, numPages);
+	if (!m_deferMasterPageSideReconcile)
+		doc->reconcileMasterPageSides();
 	doc->reformPages();
 	doc->updateEndnotesFrames();
 	updateGUIAfterPagesChanged();
@@ -6228,6 +6232,8 @@ void ScribusMainWindow::deletePage(int from, int to)
 			auto *ss = new SimpleState(Um::DeletePage, QString(), Um::ICreate);
 			ss->set("DELETE_PAGE");
 			ss->set("PAGENR", a + 1);
+			ss->set("DELETE_RANGE_FROM", from);
+			ss->set("DELETE_RANGE_TO", to);
 			ss->set("PAGENAME",   doc->Pages->at(a)->pageName());
 			ss->set("MASTERPAGE", doc->Pages->at(a)->masterPageName());
 			ss->set("MASTER_PAGE_ID", doc->Pages->at(a)->masterPageId().toString(QUuid::WithoutBraces));
@@ -6270,6 +6276,11 @@ void ScribusMainWindow::deletePage(int from, int to)
 			doc->removePageFromSection(a);
 		}
 	}
+	if (!doc->masterPageMode() && !m_deferMasterPageSideReconcile)
+	{
+		doc->reconcileMasterPageSides();
+		doc->reformPages();
+	}
 	pageSelector->setMaximum(doc->Pages->count());
 	m_undoManager->setUndoEnabled(false); // ugly hack to disable object moving when undoing page deletion
 	view->reformPagesView();
@@ -7889,6 +7900,17 @@ void ScribusMainWindow::ApplyMasterPage()
 		return;
 
 	QString masterPageName(dia->getMasterPageName());
+	if (dia->applyToSpread())
+	{
+		const QUuid masterId = doc->masterPageId(masterPageName);
+		doc->applyMasterSpread(masterId, doc->currentPageNumber());
+		view->reformPages();
+		view->DrawNew();
+		pagePalette->rebuild();
+		guidePalette->setupPage(false);
+		return;
+	}
+
 	int pageSelection = dia->getPageSelection(); //0=current, 1=even, 2=odd, 3=all
 
 	int startPage, endPage;
@@ -8018,6 +8040,12 @@ void ScribusMainWindow::restoreDeletePage(SimpleState *state, bool isUndo)
 	QString pageName = state->get("PAGENAME");
 	bool savedMasterPageMode = state->getBool("MASTER_PAGE_MODE");
 	bool currMasterPageMode = doc->masterPageMode();
+	const int deleteRangeFrom = state->contains("DELETE_RANGE_FROM") ? state->getInt("DELETE_RANGE_FROM") : pagenr;
+	const int deleteRangeTo = state->contains("DELETE_RANGE_TO") ? state->getInt("DELETE_RANGE_TO") : pagenr;
+	const bool batchStart = isUndo ? (pagenr == deleteRangeFrom) : (pagenr == deleteRangeTo);
+	const bool batchEnd = isUndo ? (pagenr == deleteRangeTo) : (pagenr == deleteRangeFrom);
+	if (!savedMasterPageMode && batchStart)
+		m_deferMasterPageSideReconcile = true;
 	if (currMasterPageMode != savedMasterPageMode)
 		doc->setMasterPageMode(savedMasterPageMode);
 	if (pagenr == 1)
@@ -8088,6 +8116,12 @@ void ScribusMainWindow::restoreDeletePage(SimpleState *state, bool isUndo)
 	}
 	if (currMasterPageMode != savedMasterPageMode)
 		doc->setMasterPageMode(currMasterPageMode);
+	if (!savedMasterPageMode && batchEnd)
+	{
+		m_deferMasterPageSideReconcile = false;
+		doc->reconcileMasterPageSides();
+		doc->reformPages();
+	}
 	doc->rebuildMasterNames();
 	if (isUndo && savedMasterPageMode)
 		editMasterPagesStart(pageName);
Index: scribus/scribus.h
===================================================================
--- scribus/scribus.h	(revision 27864)
+++ scribus/scribus.h	(working copy)
@@ -662,6 +662,7 @@ private:
 	//bool m_arrowKeyDown;
 	/** @brief tells the undo mode */
 	bool m_objectSpecificUndo { false };
+	bool m_deferMasterPageSideReconcile { false };
 
 	//CB: #8212: add overrideMasterPageSizing, however default to true for compatibility with other calls.. for now
 	void addNewPages(int wo, int where, int numPages, double height, double width, int orient, const QString& siz, bool mov, QStringList* basedOn = 0, bool overrideMasterPageSizing = true);
Index: scribus/scribusdoc.cpp
===================================================================
--- scribus/scribusdoc.cpp	(revision 27864)
+++ scribus/scribusdoc.cpp	(working copy)
@@ -2978,6 +2978,157 @@ bool ScribusDoc::isMasterSpread(const QUuid& masterId) const
 	return masterPageHasSide(masterId, MasterPageSide::Left) && masterPageHasSide(masterId, MasterPageSide::Right);
 }
 
+int ScribusDoc::appliedMasterPageIndex(const ScPage* page) const
+{
+	if (!page)
+		return -1;
+
+	const QUuid masterId = page->appliedMasterPageId();
+	if (!masterId.isNull())
+	{
+		int index = masterPageIndex(masterId, page->appliedMasterPageSide());
+		if (index >= 0)
+			return index;
+		if (isMasterSpread(masterId))
+		{
+			const int pageNumber = page->pageNr();
+			const MasterPageSide side = matchingMasterPageSide(masterId, pageNumber);
+			index = masterPageIndex(masterId, side);
+			if (index >= 0)
+				return index;
+		}
+		index = masterPageIndex(masterId);
+		if (index >= 0)
+			return index;
+	}
+
+	auto it = MasterNames.constFind(page->masterPageName());
+	return it == MasterNames.constEnd() ? -1 : it.value();
+}
+
+MasterPageSide ScribusDoc::matchingMasterPageSide(const QUuid& masterId, int pageNumber) const
+{
+	const int masterIndex = masterPageIndex(masterId);
+	if (masterIndex < 0)
+		return MasterPageSide::Single;
+	if (!isMasterSpread(masterId))
+		return MasterPages.at(masterIndex)->masterPageSide();
+
+	if (pageNumber >= 0 && pageNumber < DocPages.count())
+	{
+		const int columns = pageSets()[m_docPrefsData.docSetupPrefs.pagePositioning].Columns;
+		if (columns == 2)
+		{
+			const PageLocation location = locationOfPage(pageNumber);
+			if (location == LeftPage)
+				return MasterPageSide::Left;
+			if (location == RightPage)
+				return MasterPageSide::Right;
+		}
+
+		// Outside a two-page document spread there is no corresponding side to
+		// remap to. Preserve an already assigned side instead of silently flipping it.
+		const ScPage* page = DocPages.at(pageNumber);
+		const QUuid assignedId = page->appliedMasterPageId().isNull() ? masterPageId(page->masterPageName()) : page->appliedMasterPageId();
+		if (assignedId == masterId)
+		{
+			const MasterPageSide currentSide = page->appliedMasterPageSide();
+			if (currentSide == MasterPageSide::Left || currentSide == MasterPageSide::Right)
+				return currentSide;
+		}
+	}
+
+	return MasterPageSide::Left;
+}
+
+bool ScribusDoc::documentSpreadPages(int pageNumber, int& leftPage, int& rightPage) const
+{
+	leftPage = -1;
+	rightPage = -1;
+	if (pageNumber < 0 || pageNumber >= DocPages.count())
+		return false;
+	if (pageSets()[m_docPrefsData.docSetupPrefs.pagePositioning].Columns != 2)
+		return false;
+
+	const bool rtlBinding = m_docPrefsData.docSetupPrefs.bindingDirection == 1;
+	const PageLocation location = locationOfPage(pageNumber);
+	if (location == LeftPage)
+	{
+		leftPage = pageNumber;
+		const int rightCandidate = rtlBinding ? pageNumber - 1 : pageNumber + 1;
+		if (rightCandidate >= 0 && rightCandidate < DocPages.count() && locationOfPage(rightCandidate) == RightPage)
+			rightPage = rightCandidate;
+	}
+	else if (location == RightPage)
+	{
+		rightPage = pageNumber;
+		const int leftCandidate = rtlBinding ? pageNumber + 1 : pageNumber - 1;
+		if (leftCandidate >= 0 && leftCandidate < DocPages.count() && locationOfPage(leftCandidate) == LeftPage)
+			leftPage = leftCandidate;
+	}
+
+	return leftPage >= 0 && rightPage >= 0;
+}
+
+void ScribusDoc::preserveMasterPageMarginsForLayoutChange(int oldPagePositioning)
+{
+	if (oldPagePositioning < 0 || oldPagePositioning >= pageSets().count())
+		return;
+
+	const int oldColumns = pageSets().at(oldPagePositioning).Columns;
+	const int newColumns = pageSets().at(m_docPrefsData.docSetupPrefs.pagePositioning).Columns;
+	if (!((oldColumns == 1 && newColumns == 2) || (oldColumns == 2 && newColumns == 1)))
+		return;
+
+	for (int pageNumber = 0; pageNumber < DocPages.count(); ++pageNumber)
+	{
+		ScPage* page = DocPages.at(pageNumber);
+		QUuid masterId = page->appliedMasterPageId();
+		if (masterId.isNull())
+			masterId = masterPageId(page->masterPageName());
+		if (masterId.isNull() || !isMasterSpread(masterId))
+			continue;
+
+		const MasterPageSide newSide = matchingMasterPageSide(masterId, pageNumber);
+		if (newSide != page->appliedMasterPageSide())
+			continue;
+
+		const double physicalLeft = page->Margins.left();
+		const double physicalRight = page->Margins.right();
+		if (newColumns == 1 || locationOfPage(pageNumber) == RightPage)
+		{
+			page->initialMargins.setLeft(physicalLeft);
+			page->initialMargins.setRight(physicalRight);
+		}
+		else if (locationOfPage(pageNumber) == LeftPage)
+		{
+			page->initialMargins.setLeft(physicalRight);
+			page->initialMargins.setRight(physicalLeft);
+		}
+	}
+}
+
+void ScribusDoc::reconcileMasterPageSides()
+{
+	if (masterPageMode())
+		return;
+
+	for (int pageNumber = 0; pageNumber < DocPages.count(); ++pageNumber)
+	{
+		ScPage* page = DocPages.at(pageNumber);
+		QUuid masterId = page->appliedMasterPageId();
+		if (masterId.isNull())
+			masterId = masterPageId(page->masterPageName());
+		if (masterId.isNull() || !isMasterSpread(masterId))
+			continue;
+
+		const MasterPageSide side = matchingMasterPageSide(masterId, pageNumber);
+		if (side == page->appliedMasterPageSide())
+			continue;
+		applyMasterPageInternal(masterId, side, pageNumber, false);
+	}
+}
+
 
 bool ScribusDoc::isMasterItemOnCurrentMaster(const PageItem* item) const
 {
@@ -3056,6 +3207,7 @@ void ScribusDoc::swapPage(int a, int b)
 		m_undoManager->action(this, ss);
 	}
 	Pages->swapItemsAt(a, b);
+	reconcileMasterPageSides();
 	reformPages();
 	changed();
 }
@@ -3109,6 +3261,7 @@ void ScribusDoc::movePage(int fromPage, int toPage, int dest, int position)
 		ss->set("PAGE_MOVE_NEWPOS", position);
 		m_undoManager->action(this, ss);
 	}
+	reconcileMasterPageSides();
 	reformPages();
 	if (m_View && m_ScMW)
 	{
@@ -4994,14 +5147,21 @@ bool ScribusDoc::applyMasterPage(const QString& pageName, int pageNumber)
 
 bool ScribusDoc::applyMasterPage(const QUuid& masterId, int pageNumber)
 {
-	const int index = masterPageIndex(masterId);
-	if (index < 0)
+	if (masterPageIndex(masterId) < 0)
 		return false;
-	return applyMasterPage(masterId, MasterPages.at(index)->masterPageSide(), pageNumber);
+	return applyMasterPage(masterId, matchingMasterPageSide(masterId, pageNumber), pageNumber);
 }
 
 bool ScribusDoc::applyMasterPage(const QUuid& masterId, MasterPageSide side, int pageNumber)
 {
+	return applyMasterPageInternal(masterId, side, pageNumber, true);
+}
+
+bool ScribusDoc::applyMasterPageInternal(const QUuid& masterId, MasterPageSide side, int pageNumber, bool recordUndo)
+{
+	if (pageNumber < 0 || pageNumber >= DocPages.count())
+		return false;
+
 	int MpNr = masterPageIndex(masterId, side);
 	if (MpNr < 0 && side != MasterPageSide::Single)
 		MpNr = masterPageIndex(masterId, MasterPageSide::Single);
@@ -5010,7 +5170,7 @@ bool ScribusDoc::applyMasterPage(const QUuid& masterId, MasterPageSide side, int
 	const MasterPageSide appliedSide = MasterPages.at(MpNr)->masterPageSide();
 	const QString pageName = MasterPages.at(MpNr)->pageName();
 
-	if (UndoManager::undoEnabled())
+	if (recordUndo && UndoManager::undoEnabled())
 	{
 		if (DocPages.at(pageNumber)->appliedMasterPageId() != masterId || DocPages.at(pageNumber)->appliedMasterPageSide() != appliedSide)
 		{
@@ -5049,17 +5209,37 @@ bool ScribusDoc::applyMasterPage(const QUuid& masterId, MasterPageSide side, int
 
 		Ap->initialMargins.setTop(Mp->Margins.top());
 		Ap->initialMargins.setBottom(Mp->Margins.bottom());
+		const bool explicitMasterSide = appliedSide == MasterPageSide::Left || appliedSide == MasterPageSide::Right;
 		if (pageSets()[m_docPrefsData.docSetupPrefs.pagePositioning].Columns == 1)
 		{
-			Ap->initialMargins.setLeft(Mp->Margins.left());
-			Ap->initialMargins.setRight(Mp->Margins.right());
+			if (explicitMasterSide)
+			{
+				// A true-spread half has a stable side independent of legacy LeftPg.
+				// Preserve the physical appearance of that half on a single page.
+				if (appliedSide == MasterPageSide::Left)
+				{
+					Ap->initialMargins.setLeft(Mp->initialMargins.right());
+					Ap->initialMargins.setRight(Mp->initialMargins.left());
+				}
+				else
+				{
+					Ap->initialMargins.setLeft(Mp->initialMargins.left());
+					Ap->initialMargins.setRight(Mp->initialMargins.right());
+				}
+			}
+			else
+			{
+				Ap->initialMargins.setLeft(Mp->Margins.left());
+				Ap->initialMargins.setRight(Mp->Margins.right());
+			}
 		}
 		else
 		{
+			const bool masterIsLeft = explicitMasterSide ? appliedSide == MasterPageSide::Left : Mp->LeftPg != 0;
 			PageLocation pageLoc = locationOfPage(pageNumber);
 			if (pageLoc == LeftPage) //Left hand page
 			{
-				if (Mp->LeftPg != 0)
+				if (masterIsLeft)
 				{
 					Ap->initialMargins.setRight(Mp->initialMargins.right());
 					Ap->initialMargins.setLeft(Mp->initialMargins.left());
@@ -5072,7 +5252,7 @@ bool ScribusDoc::applyMasterPage(const QUuid& masterId, MasterPageSide side, int
 			}
 			else if (pageLoc == RightPage) // Right hand page
 			{
-				if (Mp->LeftPg != 0)
+				if (masterIsLeft)
 				{
 					Ap->initialMargins.setLeft(Mp->initialMargins.right());
 					Ap->initialMargins.setRight(Mp->initialMargins.left());
@@ -5098,7 +5278,6 @@ bool ScribusDoc::applyMasterPage(const QUuid& masterId, MasterPageSide side, int
 		Ap->setOrientation(Mp->orientation());
 		Ap->setSize(Mp->size());
 	}
-	//TODO make a return false if not possible to apply the master page
 	if (!isLoading())
 	{
 		changed();
@@ -5107,6 +5286,28 @@ bool ScribusDoc::applyMasterPage(const QUuid& masterId, MasterPageSide side, int
 	return true;
 }
 
+bool ScribusDoc::applyMasterSpread(const QUuid& masterId, int pageNumber)
+{
+	if (!isMasterSpread(masterId))
+		return false;
+
+	int leftPage = -1;
+	int rightPage = -1;
+	if (!documentSpreadPages(pageNumber, leftPage, rightPage))
+		return false;
+
+	UndoTransaction trans;
+	if (UndoManager::undoEnabled())
+		trans = m_undoManager->beginTransaction(QString(), nullptr, Um::ApplyMasterPage, masterPageName(masterId));
+
+	const bool leftApplied = applyMasterPageInternal(masterId, MasterPageSide::Left, leftPage, true);
+	const bool rightApplied = applyMasterPageInternal(masterId, MasterPageSide::Right, rightPage, true);
+
+	if (trans)
+		trans.commit();
+	return leftApplied && rightApplied;
+}
+
 
 void ScribusDoc::restoreMasterPageApplying(SimpleState* ss, bool isUndo)
 {
@@ -5150,17 +5351,12 @@ void ScribusDoc::restoreCopyPage(SimpleState* ss, bool isUndo)
 
 	if (isUndo)
 	{
-		int destLocation = extPage + 1;
+		int deleteFrom = extPage + 1;
 		if (whereTo == 0)
-			--destLocation;
+			--deleteFrom;
 		else if (whereTo == 2)
-			destLocation = DocPages.count();
-		for (int i = 0; i < copyCount; ++i)
-		{
-			m_ScMW->deletePage(destLocation, destLocation);
-			if (whereTo == 2)
-				--destLocation;
-		}
+			deleteFrom = DocPages.count() - copyCount + 1;
+		m_ScMW->deletePage(deleteFrom, deleteFrom + copyCount - 1);
 	}
 	else
 	{
@@ -5533,16 +5729,11 @@ bool ScribusDoc::copyPageToMasterPage(int pageNumber, int leftPage, int maxLeftP
 	//Copy the items from our current document page's applied *master* page
 	if (copyFromAppliedMaster)
 	{
-		if (!sourcePage->masterPageNameEmpty() && MasterNames.contains(sourcePage->masterPageName()))
+		const int pageMasterIndex = appliedMasterPageIndex(sourcePage);
+		if (pageMasterIndex >= 0)
 		{
-			ScPage* pageMaster = nullptr;
-			for (int i = 0; i < MasterPages.count(); ++i )
-			{
-				pageMaster = MasterPages[i];
-				if (pageMaster->pageName() == sourcePage->masterPageName())
-					break;
-			}
-			if (Layers.count() != 0 && pageMaster != nullptr)
+			ScPage* pageMaster = MasterPages.at(pageMasterIndex);
+			if (Layers.count() != 0)
 			{
 				int currActiveLayer = activeLayer();
 				for (auto it = Layers.begin(); it != Layers.end(); ++it)
@@ -5551,7 +5742,7 @@ bool ScribusDoc::copyPageToMasterPage(int pageNumber, int leftPage, int maxLeftP
 					for (int ite = 0; ite < masterItemsOldCount; ++ite)
 					{
 						PageItem *itemToCopy = MasterItems.at(ite);
-						if ((itemToCopy->OnMasterPage == pageMaster->pageName()) && (it->ID == itemToCopy->m_layerID))
+						if ((itemToCopy->OwnPage == pageMasterIndex) && (it->ID == itemToCopy->m_layerID))
 							tempSelection.addItem(itemToCopy);
 					}
 					if (tempSelection.count() != 0)
@@ -7758,6 +7949,7 @@ void ScribusDoc::copyPage(int pageNumberToCopy, int existingPage, int whereToIns
 	addPageToAnnotLinks(annotLinksInsert, whereToInsert, copyCount);
 	addPageToSection(existingPage, whereToInsert, copyCount);
 
+	reconcileMasterPageSides();
 	if (lastDest != nullptr)
 		setCurrentPage(lastDest);
 	else
@@ -17516,6 +17708,7 @@ void ScribusDoc::setNewPrefs(const ApplicationPrefs& prefsData, const Applicatio
 	double topDisplacement = prefsData.displayPrefs.scratch.top() - oldPrefsData.displayPrefs.scratch.top();
 	double leftDisplacement = prefsData.displayPrefs.scratch.left() - oldPrefsData.displayPrefs.scratch.left();
 	applyPrefsPageSizingAndMargins(resizePages, resizeMasterPages, resizePageMargins, resizeMasterPageMargins);
+	reconcileMasterPageSides();
 
 	int docItemsCount = MasterItems.count();
 	for (int i = 0; i < docItemsCount; ++i)
@@ -17661,15 +17854,18 @@ void ScribusDoc::applyPrefsPageSizingAndMargins(bool resizePages, bool resizeMas
 			//CB #6796: find the master page (*mp) for the current page (*pp)
 			//check if *pp's margins are the same as the *mp's current margins
 			//apply new margins if same
-			const int masterPageNumber = MasterNames[pp->masterPageName()];
-			const ScPage* mp = MasterPages.at(masterPageNumber);
-			if (pp->initialMargins.left() == mp->initialMargins.left() &&
-				pp->initialMargins.top() == mp->initialMargins.top() &&
-				pp->initialMargins.right() == mp->initialMargins.right() &&
-				pp->initialMargins.bottom() == mp->initialMargins.bottom())
+			const int masterPageNumber = appliedMasterPageIndex(pp);
+			if (masterPageNumber >= 0)
 			{
-				pp->initialMargins = m_docPrefsData.docSetupPrefs.margins;
-				pp->marginPreset = m_docPrefsData.docSetupPrefs.marginPreset;
+				const ScPage* mp = MasterPages.at(masterPageNumber);
+				if (pp->initialMargins.left() == mp->initialMargins.left() &&
+					pp->initialMargins.top() == mp->initialMargins.top() &&
+					pp->initialMargins.right() == mp->initialMargins.right() &&
+					pp->initialMargins.bottom() == mp->initialMargins.bottom())
+				{
+					pp->initialMargins = m_docPrefsData.docSetupPrefs.margins;
+					pp->marginPreset = m_docPrefsData.docSetupPrefs.marginPreset;
+				}
 			}
 		}
 	}
Index: scribus/scribusdoc.h
===================================================================
--- scribus/scribusdoc.h	(revision 27864)
+++ scribus/scribusdoc.h	(working copy)
@@ -274,6 +274,11 @@ class SCRIBUS_API ScribusDoc : public QObject, public UndoObject, public Observa
 		int masterPageIndex(const QUuid& masterId, MasterPageSide side) const;
 		bool masterPageHasSide(const QUuid& masterId, MasterPageSide side) const;
 		bool isMasterSpread(const QUuid& masterId) const;
+		int appliedMasterPageIndex(const ScPage* page) const;
+		MasterPageSide matchingMasterPageSide(const QUuid& masterId, int pageNumber) const;
+		bool documentSpreadPages(int pageNumber, int& leftPage, int& rightPage) const;
+		void preserveMasterPageMarginsForLayoutChange(int oldPagePositioning);
+		void reconcileMasterPageSides();
 		bool isMasterItemOnCurrentMaster(const PageItem* item) const;
 		//! @brief Replace a master page by default one
 		void replaceMasterPage(const QString& oldMasterPage);
@@ -847,6 +852,7 @@ class SCRIBUS_API ScribusDoc : public QObject, public UndoObject, public Observa
 		bool applyMasterPage(const QString& pageName, int pageNumber);
 		bool applyMasterPage(const QUuid& masterId, int pageNumber);
 		bool applyMasterPage(const QUuid& masterId, MasterPageSide side, int pageNumber);
+		bool applyMasterSpread(const QUuid& masterId, int pageNumber);
 		/**
 		 * @brief Undo function for applying a master page
 		 */
@@ -1378,6 +1384,7 @@ class SCRIBUS_API ScribusDoc : public QObject, public UndoObject, public Observa
 		void saveFilePermissions(QFileDevice::Permissions p) { m_docFilePermissions = p; }
 
 	protected:
+		bool applyMasterPageInternal(const QUuid& masterId, MasterPageSide side, int pageNumber, bool recordUndo);
 		void addSymbols();
 		void applyPrefsPageSizingAndMargins(bool resizePages, bool resizeMasterPages, bool resizePageMargins, bool resizeMasterPageMargins);
 		bool m_hasGUI {false};
Index: scribus/ui/applytemplatedialog.cpp
===================================================================
--- scribus/ui/applytemplatedialog.cpp	(revision 27864)
+++ scribus/ui/applytemplatedialog.cpp	(working copy)
@@ -79,6 +79,10 @@ ApplyMasterPageDialog::ApplyMasterPageDialog( QWidget* parent ) : QDialog( paren
 	currentPageRadioButton->setChecked( true );
 	applyToPageButtonGroupLayout->addWidget( currentPageRadioButton );
 
+	currentSpreadRadioButton = new QRadioButton(applyToPageButtonGroup);
+	currentSpreadRadioButton->setEnabled(false);
+	applyToPageButtonGroupLayout->addWidget(currentSpreadRadioButton);
+
 	evenPagesRadioButton = new QRadioButton( applyToPageButtonGroup );
 	applyToPageButtonGroupLayout->addWidget( evenPagesRadioButton );
 
@@ -125,9 +129,11 @@ ApplyMasterPageDialog::ApplyMasterPageDialog( QWidget* parent ) : QDialog( paren
 	// signals and slots connections
 	connect( useRangeCheckBox, SIGNAL( toggled(bool) ), this, SLOT( enableRange(bool) ) );
 	connect( currentPageRadioButton, SIGNAL( clicked() ), this, SLOT( singleSelectable() ) );
+	connect( currentSpreadRadioButton, SIGNAL( clicked() ), this, SLOT( singleSelectable() ) );
 	connect( evenPagesRadioButton, SIGNAL( clicked() ), this, SLOT( rangeSelectable() ) );
 	connect( oddPagesRadioButton, SIGNAL( clicked() ), this, SLOT( rangeSelectable() ) );
 	connect( allPagesRadioButton, SIGNAL( clicked() ), this, SLOT( rangeSelectable() ) );
+	connect(masterPageComboBox, &QComboBox::currentIndexChanged, this, &ApplyMasterPageDialog::updateSpreadOption);
 	connect( fromPageSpinBox, SIGNAL( valueChanged(double) ), this, SLOT( checkRangeFrom() ) );
 	connect( toPageSpinBox, SIGNAL( valueChanged(double) ), this, SLOT( checkRangeTo() ) );
 	connect(buttonBox, &QDialogButtonBox::accepted, this, &ApplyMasterPageDialog::accept);
@@ -139,6 +145,7 @@ ApplyMasterPageDialog::ApplyMasterPageDialog( QWidget* parent ) : QDialog( paren
 
 void ApplyMasterPageDialog::setup(ScribusDoc *doc, const QString& Nam)
 {
+	m_doc = doc;
 	QString na = (Nam == CommonStrings::masterPageNormal) ? CommonStrings::trMasterPageNormal : Nam;
 	QString in;
 
@@ -158,6 +165,7 @@ void ApplyMasterPageDialog::setup(ScribusDoc *doc, const QString& Nam)
 	fromPageSpinBox->setValue(doc->currentPage()->pageNr() + 1);
 	toPageSpinBox->setMaximum(docPagesCount);
 	toPageSpinBox->setValue(docPagesCount);
+	updateSpreadOption();
 }
 
 
@@ -168,6 +176,11 @@ QString ApplyMasterPageDialog::getMasterPageName()
 }
 
 
+bool ApplyMasterPageDialog::applyToSpread() const
+{
+	return currentSpreadRadioButton->isEnabled() && currentSpreadRadioButton->isChecked();
+}
+
 int ApplyMasterPageDialog::getPageSelection()
 {
 	if (currentPageRadioButton->isChecked())
@@ -209,6 +222,7 @@ void ApplyMasterPageDialog::rangeSelectable()
 {
 	useRangeCheckBox->setEnabled(true);
 	enableRange(useRangeCheckBox->isChecked());
+	updateSpreadOption();
 }
 
 void ApplyMasterPageDialog::singleSelectable()
@@ -216,6 +230,27 @@ void ApplyMasterPageDialog::singleSelectable()
 	useRangeCheckBox->setEnabled(false);
 	fromPageSpinBox->setEnabled(false);
 	toPageSpinBox->setEnabled(false);
+	updateSpreadOption();
+}
+
+void ApplyMasterPageDialog::updateSpreadOption()
+{
+	bool enableSpreadOption = false;
+	if (m_doc && masterPageComboBox->currentIndex() >= 0)
+	{
+		const QString masterName = masterPageComboBox->currentData().toString();
+		const QUuid masterId = m_doc->masterPageId(masterName);
+		if (m_doc->isMasterSpread(masterId))
+		{
+			int leftPage = -1;
+			int rightPage = -1;
+			enableSpreadOption = m_doc->documentSpreadPages(m_doc->currentPageNumber(), leftPage, rightPage);
+		}
+	}
+
+	currentSpreadRadioButton->setEnabled(enableSpreadOption);
+	if (!enableSpreadOption && currentSpreadRadioButton->isChecked())
+		currentPageRadioButton->setChecked(true);
 }
 
 
@@ -256,7 +291,9 @@ void ApplyMasterPageDialog::languageChange()
 	masterPageLabel->setText( tr( "&Master Page:" ) );
 	applyToPageButtonGroup->setTitle( tr( "Apply to" ) );
 	currentPageRadioButton->setText( tr( "Current &Page" ) );
+	currentSpreadRadioButton->setText( tr( "Current &Spread" ) );
 	currentPageRadioButton->setShortcut( QKeySequence( tr( "Alt+P" ) ) );
+	currentSpreadRadioButton->setShortcut( QKeySequence( tr( "Alt+S" ) ) );
 	evenPagesRadioButton->setText( tr( "&Even Pages" ) );
 	evenPagesRadioButton->setShortcut( QKeySequence( tr( "Alt+E" ) ) );
 	oddPagesRadioButton->setText( tr( "O&dd Pages" ) );
Index: scribus/ui/applytemplatedialog.h
===================================================================
--- scribus/ui/applytemplatedialog.h	(revision 27864)
+++ scribus/ui/applytemplatedialog.h	(working copy)
@@ -47,6 +47,7 @@ public:
 	QComboBox* masterPageComboBox { nullptr };
 	QGroupBox* applyToPageButtonGroup { nullptr };
 	QRadioButton* currentPageRadioButton { nullptr };
+	QRadioButton* currentSpreadRadioButton { nullptr };
 	QRadioButton* evenPagesRadioButton { nullptr };
 	QRadioButton* oddPagesRadioButton { nullptr };
 	QRadioButton* allPagesRadioButton { nullptr };
@@ -61,6 +62,7 @@ public:
 	virtual bool usingRange();
 	virtual int getFromPage();
 	virtual int getToPage();
+	bool applyToSpread() const;
 
 protected:
 	QVBoxLayout* ApplyMasterPageDialogLayout { nullptr };
@@ -77,12 +79,16 @@ protected slots:
 	virtual void languageChange();
 
 private slots:
+	void updateSpreadOption();
 	virtual void checkRangeFrom();
 	virtual void checkRangeTo();
 	virtual void enableRange( bool enabled );
 	virtual void rangeSelectable();
 	virtual void singleSelectable();
 
+private:
+	ScribusDoc* m_doc { nullptr };
+
 };
 
 #endif // APPLYMASTERPAGEDIALOG_H
Index: scribus/ui/pagepalette_pages.cpp
===================================================================
--- scribus/ui/pagepalette_pages.cpp	(revision 27864)
+++ scribus/ui/pagepalette_pages.cpp	(working copy)
@@ -301,6 +301,7 @@ void PagePalette_Pages::handlePageLayout(int layout)
 {
 	if (!currView)
 		return;
+	const int oldPagePositioning = currView->m_doc->pagePositioning();
 	currView->m_doc->setPagePositioning(layout);
 
 	// Block firstPageChanged signal while we sync the UI to the new layout's
@@ -311,6 +312,8 @@ void PagePalette_Pages::handlePageLayout(int layout)
 	pageLayout->setFirstPage(currView->m_doc->pageSets()[layout].FirstPage);
 	connect(pageLayout, SIGNAL(firstPageChanged(int)), this, SLOT(handleFirstPage(int)));
 
+	currView->m_doc->preserveMasterPageMarginsForLayoutChange(oldPagePositioning);
+	currView->m_doc->reconcileMasterPageSides();
 	currView->reformPages();
 	currView->DrawNew();
 	currView->GotoPage(currView->m_doc->currentPageNumber());
@@ -322,6 +325,7 @@ void PagePalette_Pages::handleFirstPage(int fp)
 	if (!currView)
 		return;
 	currView->m_doc->setPageSetFirstPage(currView->m_doc->pagePositioning(), fp);
+	currView->m_doc->reconcileMasterPageSides();
 	currView->reformPages();
 	currView->DrawNew();
 	currView->GotoPage(currView->m_doc->currentPageNumber());
@@ -333,6 +337,7 @@ void PagePalette_Pages::handleBindingDirection(int direction)
 	if (!currView)
 		return;
 	currView->m_doc->setBindingDirection(direction);
+	currView->m_doc->reconcileMasterPageSides();
 	currView->reformPages();
 	currView->DrawNew();
 	currView->GotoPage(currView->m_doc->currentPageNumber());
tsm4-cross-spread-rendering-v1.01.patch (6,100 bytes)   
Index: scribus/canvas.cpp
===================================================================
--- scribus/canvas.cpp	(revision 27864)
+++ scribus/canvas.cpp	(working copy)
@@ -1324,30 +1324,49 @@
 		return;
 	if (!layer.isViewable)
 		return;
-	if (page->masterPageNameEmpty())
-		return;
-	if (page->FromMaster.count() <= 0)
+
+	const QList<PageItem*> masterItems = m_doc->masterItemsForAppliedPage(page);
+	if (masterItems.isEmpty())
 		return;
 
 	FPoint orig = localToCanvas(clip.topLeft());
 	QRectF cullingArea(static_cast<int>(orig.x()), static_cast<int>(orig.y()), 
 	                   qRound(clip.width() / m_viewMode.scale + 0.5), qRound(clip.height() / m_viewMode.scale + 0.5));
 
-	PageItem *currItem;
 	const int masterIndex = m_doc->appliedMasterPageIndex(page);
 	if (masterIndex < 0)
 		return;
-	ScPage* Mp = m_doc->MasterPages.at(masterIndex);
+	const ScPage* masterPage = m_doc->MasterPages.at(masterIndex);
+
+	const bool clipAtSpine = m_doc->isMasterSpread(masterPage->masterPageId());
+	if (clipAtSpine)
+	{
+		QRectF projectionClip = cullingArea.normalized();
+		if (masterPage->masterPageSide() == MasterPageSide::Left)
+			projectionClip.setRight(qMin(projectionClip.right(), page->xOffset() + page->width()));
+		else if (masterPage->masterPageSide() == MasterPageSide::Right)
+			projectionClip.setLeft(qMax(projectionClip.left(), page->xOffset()));
+
+		if (projectionClip.width() <= 0.0 || projectionClip.height() <= 0.0)
+			return;
+
+		cullingArea = projectionClip;
+		painter->save();
+		painter->newPath();
+		painter->moveTo(cullingArea.left(), cullingArea.top());
+		painter->lineTo(cullingArea.right(), cullingArea.top());
+		painter->lineTo(cullingArea.right(), cullingArea.bottom());
+		painter->lineTo(cullingArea.left(), cullingArea.bottom());
+		painter->closePath();
+		painter->setClipPath();
+	}
+
 	if (((layer.blendMode != 0) || (layer.transparency != 1.0)) && (!layer.outlineMode))
 		painter->beginLayer(layer.transparency, layer.blendMode);
-	int pageFromMasterCount = page->FromMaster.count();
-	for (int a = 0; a < pageFromMasterCount; ++a)
+	for (PageItem* currItem : masterItems)
 	{
-		currItem = page->FromMaster.at(a);
 		if (currItem->m_layerID != layer.ID)
 			continue;
-		if ((currItem->OwnPage != -1) && (currItem->OwnPage != Mp->pageNr()))
-			continue;
 		if ((m_viewMode.previewMode) && (!currItem->printEnabled()))
 			continue;
 		if ((m_viewMode.viewAsPreview) && (!currItem->printEnabled()))
@@ -1359,9 +1378,9 @@
 		if (!currItem->ChangedMasterItem)
 		{
 			//Hack to not check for undo changes, indicate drawing only
-			currItem->moveBy(-Mp->xOffset() + page->xOffset(), -Mp->yOffset() + page->yOffset(), true);
-			currItem->BoundingX = oldBX - Mp->xOffset() + page->xOffset();
-			currItem->BoundingY = oldBY - Mp->yOffset() + page->yOffset();
+			currItem->moveBy(-masterPage->xOffset() + page->xOffset(), -masterPage->yOffset() + page->yOffset(), true);
+			currItem->BoundingX = oldBX - masterPage->xOffset() + page->xOffset();
+			currItem->BoundingY = oldBY - masterPage->yOffset() + page->yOffset();
 		}
 		// Save PageItem's OwnPage and set its value to page number
 		// so that page number placed in text frames can work, also modify
@@ -1413,6 +1432,8 @@
 	}
 	if (((layer.blendMode != 0) || (layer.transparency != 1.0)) && (!layer.outlineMode))
 		painter->endLayer();
+	if (clipAtSpine)
+		painter->restore();
 }
 
 
Index: scribus/scribusdoc.cpp
===================================================================
--- scribus/scribusdoc.cpp	(revision 27864)
+++ scribus/scribusdoc.cpp	(working copy)
@@ -3006,6 +3006,30 @@
 	return it == MasterNames.constEnd() ? -1 : it.value();
 }
 
+QList<PageItem*> ScribusDoc::masterItemsForAppliedPage(const ScPage* page) const
+{
+	QList<PageItem*> items;
+	const int masterIndex = appliedMasterPageIndex(page);
+	if (masterIndex < 0)
+		return items;
+
+	const ScPage* masterPage = MasterPages.at(masterIndex);
+	const QUuid masterId = masterPage->masterPageId();
+	if (masterId.isNull() || !isMasterSpread(masterId))
+		return page->FromMaster;
+
+	// Keep FromMaster owner-based for existing output paths. Canvas projections need
+	// the complete logical spread; the target half is selected by spine clipping.
+	for (PageItem* item : std::as_const(MasterItems))
+	{
+		const bool sameLogicalMaster = (!item->masterPageId().isNull() && item->masterPageId() == masterId)
+			|| (item->masterPageId().isNull() && item->OnMasterPage == masterPage->pageName());
+		if (sameLogicalMaster)
+			items.append(item);
+	}
+	return items;
+}
+
 MasterPageSide ScribusDoc::matchingMasterPageSide(const QUuid& masterId, int pageNumber) const
 {
 	const int masterIndex = masterPageIndex(masterId);
Index: scribus/scribusdoc.h
===================================================================
--- scribus/scribusdoc.h	(revision 27864)
+++ scribus/scribusdoc.h	(working copy)
@@ -275,6 +275,7 @@
 		bool masterPageHasSide(const QUuid& masterId, MasterPageSide side) const;
 		bool isMasterSpread(const QUuid& masterId) const;
 		int appliedMasterPageIndex(const ScPage* page) const;
+		QList<PageItem*> masterItemsForAppliedPage(const ScPage* page) const;
 		MasterPageSide matchingMasterPageSide(const QUuid& masterId, int pageNumber) const;
 		bool documentSpreadPages(int pageNumber, int& leftPage, int& rightPage) const;
 		void preserveMasterPageMarginsForLayoutChange(int oldPagePositioning);
Index: scribus/scribusview.cpp
===================================================================
--- scribus/scribusview.cpp	(revision 27864)
+++ scribus/scribusview.cpp	(working copy)
@@ -2665,9 +2665,9 @@
 
 	QList<QPair<PageItem*, int> > changedList;
 	PageItem* currItem;
-	if (!page->FromMaster.isEmpty() && !flags.testFlag(Pixmap_DontReloadImages))
+	if (!flags.testFlag(Pixmap_DontReloadImages))
 	{
-		QList<PageItem*> itemList = page->FromMaster;
+		QList<PageItem*> itemList = m_doc->masterItemsForAppliedPage(page);
 		while (!itemList.isEmpty())
 		{
 			currItem = itemList.takeFirst();
tsm5-output-projection-v1.06.patch (34,208 bytes)   
Index: scribus/pdflib_core.cpp
===================================================================
--- scribus/pdflib_core.cpp	(revision 27864)
+++ scribus/pdflib_core.cpp	(working copy)
@@ -658,7 +658,9 @@
 		QMap<int, int> pageNsMpa;
 		for (uint a = 0; a < pageNs.size(); ++a)
 		{
-			pageNsMpa.insert(doc.MasterNames[doc.DocPages.at(pageNs[a] - 1)->masterPageName()], 0);
+			const int masterIndex = doc.appliedMasterPageIndex(doc.DocPages.at(pageNs[a] - 1));
+			if (masterIndex >= 0)
+				pageNsMpa.insert(masterIndex, 0);
 		}
 		if (usingGUI)
 		{
@@ -2577,22 +2579,18 @@
 	QByteArray tmpOut;
 	ActPageP = pag;
 	PageItem* ite;
-	QList<PageItem*> PItems;
+	const QList<PageItem*> PItems = doc.masterItemsForMasterPage(pag);
 	ScLayer ll;
 	ll.isPrintable = false;
 	ll.ID = 0;
 
 	Content.clear();
 
-	double bLeft, bRight, bBottom, bTop;
-	getBleeds(pag, bLeft, bRight, bBottom, bTop);
-
 	pageData.AObjects.clear();
 
 	for (int la = 0; la < doc.Layers.count(); ++la)
 	{
 		doc.Layers.levelToLayer(ll, la);
-		PItems = doc.MasterItems;
 		if (!ll.isPrintable && !Options.exportsLayers())
 			continue;
 
@@ -2604,21 +2602,10 @@
 			ite = PItems.at(a);
 			if (ite->m_layerID != ll.ID)
 				continue;
-			double x  = pag->xOffset() - bLeft;
-			double y  = pag->yOffset() - bTop;
-			double w  = pag->width() + bLeft + bRight;
-			double h1 = pag->height() + bBottom + bTop;
-			double ilw= ite->visualLineWidth();
-			double x2 = ite->BoundingX - ilw / 2.0;
-			double y2 = ite->BoundingY - ilw / 2.0;
-			double w2 = ite->BoundingW + ilw;
-			double h2 = ite->BoundingH + ilw;
-			if (!QRectF(x2, y2, w2, h2).intersects(QRectF(x, y, w, h1)))
+			if (!masterItemIntersectsPage(pag, ite))
 				continue;
 			if (ite->ChangedMasterItem)
 				continue;
-			if ((!pag->pageNameEmpty()) && (ite->OwnPage != pag->pageNr()) && (ite->OwnPage != -1))
-				continue;
 			PutPage("q\n");
 			if ((ite->doOverprint) && (!Options.UseRGB))
 			{
@@ -3785,38 +3772,52 @@
 
 bool PDFLibCore::PDF_ProcessMasterElements(const ScLayer& layer, const ScPage* pag, uint PNr)
 {
-	PageItem* ite;
 	QByteArray content, output;
-//	QList<PageItem*> PItems;
 
-	if (pag->masterPageNameEmpty())
+	const int masterIndex = doc.appliedMasterPageIndex(pag);
+	if (masterIndex < 0)
 		return true;
-	if (doc.MasterItems.count() <= 0)
+	const ScPage* masterPage = doc.MasterPages.at(masterIndex);
+	const QList<PageItem*> masterItems = doc.masterItemsForAppliedPage(pag);
+	if (masterItems.isEmpty())
 		return true;
-	const ScPage* mPage = doc.MasterPages.at(doc.MasterNames[doc.DocPages.at(PNr)->masterPageName()]);
-	int   mPageIndex  = doc.MasterPages.indexOf((ScPage* const) mPage) + 1;
 
 	if (!Options.MirrorH)
 		PutPage("1 0 0 1 0 0 cm\n");
 	if (!layer.isPrintable && !Options.exportsLayers())
 		return true;
 
+	const bool clipLogicalSpread = doc.isMasterSpread(masterPage->masterPageId());
+	if (clipLogicalSpread)
+	{
+		double bleedLeft = 0.0;
+		double bleedRight = 0.0;
+		double bleedBottom = 0.0;
+		double bleedTop = 0.0;
+		getBleeds(pag, bleedLeft, bleedRight, bleedBottom, bleedTop);
+		PutPage("q\n");
+		PutPage(FToStr(-bleedLeft) + " " + FToStr(-bleedBottom) + " "
+		        + FToStr(pag->width() + bleedLeft + bleedRight) + " "
+		        + FToStr(pag->height() + bleedBottom + bleedTop) + " re W n\n");
+	}
+
 	if (Options.exportsLayers())
 		PutPage("/OC /" + OCGEntries[layer.Name].Name + " BDC\n");
-	for (int i = 0; i < pag->FromMaster.count() && !abortExport; ++i)
+	for (PageItem* item : masterItems)
 	{
-		ite = pag->FromMaster.at(i);
+		if (abortExport)
+			break;
 		if (usingGUI)
 			QApplication::processEvents();
-		if ((ite->m_layerID != layer.ID) || (!ite->printEnabled()))
+		if ((item->m_layerID != layer.ID) || (!item->printEnabled()))
 			continue;
-		if ((!pag->pageNameEmpty()) && (ite->OwnPage != pag->pageNr()) && (ite->OwnPage != -1))
+		if (!masterItemIntersectsPage(masterPage, item))
 			continue;
 
 		QByteArray name = QByteArray("/master_page_obj_%1_%2")
-										.replace("%1", Pdf::toPdf(mPageIndex))
-										.replace("%2", Pdf::toPdf(qHash(ite)));
-		if (!ite->isTextContainer())
+							.replace("%1", Pdf::toPdf(masterIndex + 1))
+							.replace("%2", Pdf::toPdf(qHash(item)));
+		if (!item->isTextContainer())
 		{
 			if (((layer.transparency != 1) || (layer.blendMode != 0)) && Options.supportsTransparency())
 				content += (name + " Do\n");
@@ -3825,21 +3826,22 @@
 			continue;
 		}
 
-		double oldX = ite->xPos();
-		double oldY = ite->yPos();
-		double oldBX = ite->BoundingX;
-		double oldBY = ite->BoundingY;
-		ite->setXPos(ite->xPos() - mPage->xOffset() + pag->xOffset(), true);
-		ite->setYPos(ite->yPos() - mPage->yOffset() + pag->yOffset(), true);
-		if (!PDF_ProcessItem(output, ite, pag, pag->pageNr()))
+		double oldX = item->xPos();
+		double oldY = item->yPos();
+		double oldBX = item->BoundingX;
+		double oldBY = item->BoundingY;
+		item->setXPos(item->xPos() - masterPage->xOffset() + pag->xOffset(), true);
+		item->setYPos(item->yPos() - masterPage->yOffset() + pag->yOffset(), true);
+		const bool processed = PDF_ProcessItem(output, item, pag, pag->pageNr());
+		item->setXYPos(oldX, oldY, true);
+		item->BoundingX = oldBX;
+		item->BoundingY = oldBY;
+		if (!processed)
 			return false;
 		if (((layer.transparency != 1) || (layer.blendMode != 0)) && Options.supportsTransparency())
 			content += output;
 		else
 			PutPage(output);
-		ite->setXYPos(oldX, oldY, true);
-		ite->BoundingX = oldBX;
-		ite->BoundingY = oldBY;
 	}
 	// Couldn't we use Write_TransparencyGroup() here?
 	if (((layer.transparency != 1) || (layer.blendMode != 0)) && Options.supportsTransparency())
@@ -3855,9 +3857,9 @@
 		QByteArray ShName = ResNam + QByteArray::number(ResCount);
 		ResCount++;
 		Transpar[ShName] = writeGState("/CA " + FToStr(layer.transparency) + "\n"
-										+ "/ca " + FToStr(layer.transparency) + "\n"
-										+ "/SMask /None\n/AIS false\n/OPM 1\n"
-										+ "/BM /" + blendMode(layer.blendMode) + "\n");
+								+ "/ca " + FToStr(layer.transparency) + "\n"
+								+ "/SMask /None\n/AIS false\n/OPM 1\n"
+								+ "/BM /" + blendMode(layer.blendMode) + "\n");
 		PdfId formObject = writer.newObject();
 		writer.startObj(formObject);
 		PutDoc("<<\n/Type /XObject\n/Subtype /Form\n/FormType 1\n");
@@ -3885,7 +3887,8 @@
 	}
 	if (Options.exportsLayers())
 		PutPage("EMC\n");
-
+	if (clipLogicalSpread)
+		PutPage("Q\n");
 	return true;
 }
 
@@ -6091,6 +6094,26 @@
 	return tmp;
 }
 
+bool PDFLibCore::masterItemIntersectsPage(const ScPage* page, const PageItem* item) const
+{
+	double bleedLeft = 0.0;
+	double bleedRight = 0.0;
+	double bleedBottom = 0.0;
+	double bleedTop = 0.0;
+	getBleeds(page, bleedLeft, bleedRight, bleedBottom, bleedTop);
+
+	const QRectF pageRect(page->xOffset() - bleedLeft,
+		page->yOffset() - bleedTop,
+		page->width() + bleedLeft + bleedRight,
+		page->height() + bleedBottom + bleedTop);
+	const double lineWidth = item->visualLineWidth();
+	const QRectF itemRect(item->BoundingX - lineWidth / 2.0,
+		item->BoundingY - lineWidth / 2.0,
+		item->BoundingW + lineWidth,
+		item->BoundingH + lineWidth);
+	return itemRect.intersects(pageRect);
+}
+
 void PDFLibCore::getBleeds(const ScPage* page, double &left, double &right) const
 {
 	MarginStruct values;
Index: scribus/pdflib_core.h
===================================================================
--- scribus/pdflib_core.h	(revision 27864)
+++ scribus/pdflib_core.h	(working copy)
@@ -223,6 +223,7 @@
 	QByteArray Write_TransparencyGroup(double trans, int blend, QByteArray &data, const PageItem *controlItem = 0);
 	QByteArray setTextSt(PageItem *ite, uint PNr, const ScPage* pag);
 
+	bool    masterItemIntersectsPage(const ScPage* page, const PageItem* item) const;
 	void    getBleeds(const ScPage* page, double &left, double &right) const;
 	void    getBleeds(const ScPage* page, double &left, double &right, double &bottom, double& top) const;
 
Index: scribus/plugins/export/svgexplugin/svgexplugin.cpp
===================================================================
--- scribus/plugins/export/svgexplugin/svgexplugin.cpp	(revision 27864)
+++ scribus/plugins/export/svgexplugin/svgexplugin.cpp	(working copy)
@@ -257,10 +257,11 @@
 		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);
+			const int masterIndex = m_Doc->appliedMasterPageIndex(page);
+			if (masterIndex >= 0)
+				processPageLayer(m_Doc->MasterPages.at(masterIndex), ll, page);
+			processPageLayer(page, ll, page);
 		}
 	}
 	if(Options.compressFile)
@@ -291,16 +292,15 @@
 	return true;
 }
 
-void SVGExPlug::processPageLayer(ScPage *page, const ScLayer& layer)
+void SVGExPlug::processPageLayer(ScPage *page, const ScLayer& layer, const ScPage* outputPage)
 {
 	QDomElement layerGroup;
-	PageItem *item;
 	QList<PageItem*> items;
 	ScPage* SavedAct = m_Doc->currentPage();
 	if (page->pageNameEmpty())
 		items = m_Doc->DocItems;
 	else
-		items = m_Doc->MasterItems;
+		items = m_Doc->masterItemsForMasterPage(page);
 	if (items.isEmpty())
 		return;
 	if (!layer.isPrintable)
@@ -313,24 +313,33 @@
 	layerGroup.setAttribute("inkscape:groupmode", "layer");
 	if (layer.transparency != 1.0)
 		layerGroup.setAttribute("opacity", FToStr(layer.transparency));
-	for (int j = 0; j < items.count(); ++j)
+
+	// True-spread master items are emitted in target-page coordinates; keep only this physical output page.
+	const bool clipMasterSpread = !page->pageNameEmpty() && m_Doc->isMasterSpread(page->masterPageId());
+	if (clipMasterSpread && outputPage)
+	{
+		const QString clipId = "Clip" + IToStr(m_clipCount++);
+		QDomElement clipPath = m_domDoc.createElement("clipPath");
+		clipPath.setAttribute("id", clipId);
+		clipPath.setAttribute("clipPathUnits", "userSpaceOnUse");
+		QDomElement clipRect = m_domDoc.createElement("rect");
+		clipRect.setAttribute("x", "0");
+		clipRect.setAttribute("y", "0");
+		clipRect.setAttribute("width", FToStr(outputPage->width()));
+		clipRect.setAttribute("height", FToStr(outputPage->height()));
+		clipPath.appendChild(clipRect);
+		m_globalDefs.appendChild(clipPath);
+		layerGroup.setAttribute("clip-path", "url(#" + clipId + ")");
+	}
+
+	const QRectF pageRect(page->xOffset(), page->yOffset(), page->width(), page->height());
+	for (PageItem* item : items)
 	{
-		item = items.at(j);
 		if (item->m_layerID != layer.ID)
 			continue;
 		if (!item->printEnabled())
 			continue;
-		double x = page->xOffset();
-		double y = page->yOffset();
-		double w = page->width();
-		double h = page->height();
-		double x2 = item->BoundingX;
-		double y2 = item->BoundingY;
-		double w2 = item->BoundingW;
-		double h2 = item->BoundingH;
-		if (!(qMax(x, x2) <= qMin(x + w, x2 + w2) && qMax(y, y2) <= qMin(y + h, y2 + h2)))
-			continue;
-		if ((!page->pageNameEmpty()) && (item->OwnPage != page->pageNr()) && (item->OwnPage != -1))
+		if (!pageRect.intersects(item->getVisualBoundingRect()))
 			continue;
 		processItemOnPage(item->xPos()-page->xOffset(), item->yPos()-page->yOffset(), item, &layerGroup);
 	}
Index: scribus/plugins/export/svgexplugin/svgexplugin.h
===================================================================
--- scribus/plugins/export/svgexplugin/svgexplugin.h	(revision 27864)
+++ scribus/plugins/export/svgexplugin/svgexplugin.h	(working copy)
@@ -91,7 +91,7 @@
 	\brief Process a page to export to SVG format
 	\param Seite Page *
 	*/
-	void processPageLayer(ScPage *page, const ScLayer& layer);
+	void processPageLayer(ScPage *page, const ScLayer& layer, const ScPage* outputPage);
 	void processItemOnPage(double xOffset, double yOffset, PageItem *Item, QDomElement *parentElem);
 	void paintBorder(const TableBorder& border, const QPointF& start, const QPointF& end, const QPointF& startOffsetFactors, const QPointF& endOffsetFactors, QDomElement &ob);
 	QString processDropShadow(const PageItem *Item);
Index: scribus/plugins/export/xpsexport/xpsexplugin.cpp
===================================================================
--- scribus/plugins/export/xpsexport/xpsexplugin.cpp	(revision 27864)
+++ scribus/plugins/export/xpsexport/xpsexplugin.cpp	(working copy)
@@ -360,22 +360,22 @@
 		m_Doc->Layers.levelToLayer(ll, i);
 		if (ll.isPrintable)
 		{
-			ScPage *mpage = m_Doc->MasterPages.at(m_Doc->MasterNames[Page->masterPageName()]);
-			writePageLayer(doc_root, rel_root, mpage, ll);
-			writePageLayer(doc_root, rel_root, Page, ll);
+			const int masterIndex = m_Doc->appliedMasterPageIndex(Page);
+			if (masterIndex >= 0)
+				writePageLayer(doc_root, rel_root, m_Doc->MasterPages.at(masterIndex), ll, Page);
+			writePageLayer(doc_root, rel_root, Page, ll, Page);
 		}
 	}
 }
 
-void XPSExPlug::writePageLayer(QDomElement &doc_root, QDomElement &rel_root, ScPage *page, ScLayer& layer)
+void XPSExPlug::writePageLayer(QDomElement &doc_root, QDomElement &rel_root, ScPage *page, ScLayer& layer, const ScPage* outputPage)
 {
-	PageItem *item;
 	QList<PageItem*> items;
 	ScPage* SavedAct = m_Doc->currentPage();
 	if (page->pageNameEmpty())
 		items = m_Doc->DocItems;
 	else
-		items = m_Doc->MasterItems;
+		items = m_Doc->masterItemsForMasterPage(page);
 	if (items.count() == 0)
 		return;
 	if (!layer.isPrintable)
@@ -384,25 +384,26 @@
 	QDomElement layerGroup = p_docu.createElement("Canvas");
 	if (layer.transparency != 1.0)
 		layerGroup.setAttribute("Opacity", layer.transparency);
-	for (int j = 0; j < items.count(); ++j)
+
+	// True-spread master items are emitted in target-page coordinates; keep only this physical output page.
+	const bool clipMasterSpread = !page->pageNameEmpty() && m_Doc->isMasterSpread(page->masterPageId());
+	if (clipMasterSpread && outputPage)
+	{
+		const double clipWidth = outputPage->width() * conversionFactor;
+		const double clipHeight = outputPage->height() * conversionFactor;
+		const QString clipPath = QString("F 1 M0,0 L%1,0 L%1,%2 L0,%2 Z")
+			.arg(FToStr(clipWidth)).arg(FToStr(clipHeight));
+		layerGroup.setAttribute("Clip", clipPath);
+	}
+
+	const QRectF pageRect(page->xOffset(), page->yOffset(), page->width(), page->height());
+	for (PageItem* item : items)
 	{
-		item = items.at(j);
 		if (item->m_layerID != layer.ID)
 			continue;
 		if (!item->printEnabled())
 			continue;
-		double x = page->xOffset();
-		double y = page->yOffset();
-		double w = page->width();
-		double h = page->height();
-		double lw = item->visualLineWidth();
-		double x2 = item->BoundingX - lw / 2.0;
-		double y2 = item->BoundingY - lw / 2.0;
-		double w2 = item->BoundingW + lw;
-		double h2 = item->BoundingH + lw;
-		if (!QRectF(x2, y2, w2, h2).intersects(QRectF(x, y, w, h)))
-			continue;
-		if ((!page->pageNameEmpty()) && (item->OwnPage != static_cast<int>(page->pageNr())) && (item->OwnPage != -1))
+		if (!pageRect.intersects(item->getVisualBoundingRect()))
 			continue;
 		writeItemOnPage(item->xPos() - page->xOffset(), item->yPos() - page->yOffset(), item, layerGroup, rel_root);
 	}
Index: scribus/plugins/export/xpsexport/xpsexplugin.h
===================================================================
--- scribus/plugins/export/xpsexport/xpsexplugin.h	(revision 27864)
+++ scribus/plugins/export/xpsexport/xpsexplugin.h	(working copy)
@@ -84,7 +84,7 @@
 private:
 	void writePages(QDomElement &root);
 	void writePage(QDomElement &doc_root, QDomElement &rel_root, ScPage *Page);
-	void writePageLayer(QDomElement &doc_root, QDomElement &rel_root, ScPage *page, ScLayer& layer);
+	void writePageLayer(QDomElement &doc_root, QDomElement &rel_root, ScPage *page, ScLayer& layer, const ScPage* outputPage);
 	void writeItemOnPage(double xOffset, double yOffset, PageItem *Item, QDomElement &parentElem, QDomElement &rel_root);
 	void handleImageFallBack(PageItem *Item, QDomElement &parentElem, QDomElement &rel_root);
 	void processPolyItem(double xOffset, double yOffset, PageItem *Item, QDomElement &parentElem, QDomElement &rel_root);
Index: scribus/pslib.cpp
===================================================================
--- scribus/pslib.cpp	(revision 27864)
+++ scribus/pslib.cpp	(working copy)
@@ -701,7 +701,9 @@
 			progressDialog->setProgress("EMP", ap);
 			QCoreApplication::processEvents();
 		}
-		if (m_Doc->MasterItems.count() <= 0)
+		ScPage* masterPage = m_Doc->MasterPages.at(ap);
+		const QList<PageItem*> masterItems = m_Doc->masterItemsForMasterPage(masterPage);
+		if (masterItems.isEmpty())
 			continue;
 
 		ScLayer ll;
@@ -712,35 +714,23 @@
 			m_Doc->Layers.levelToLayer(ll, layerLevel);
 			if (!ll.isPrintable)
 				continue;
-			for (int api = 0; api < m_Doc->MasterItems.count() && !abortExport; ++api)
+			for (PageItem* item : masterItems)
 			{
-				PageItem *it = m_Doc->MasterItems.at(api);
+				if (abortExport)
+					break;
 				if (progressDialog)
 					QCoreApplication::processEvents();
-				if ((it->m_layerID != ll.ID) || (!it->printEnabled()))
-					continue;
-				double bLeft, bRight, bBottom, bTop;
-				GetBleeds(m_Doc->MasterPages.at(ap), bLeft, bRight, bBottom, bTop);
-				double x1 = m_Doc->MasterPages.at(ap)->xOffset() - bLeft;
-				double y1 = m_Doc->MasterPages.at(ap)->yOffset() - bTop;
-				double w1 = m_Doc->MasterPages.at(ap)->width() + bLeft + bRight;
-				double h1 = m_Doc->MasterPages.at(ap)->height()+ bBottom + bTop;
-				double lw = it->visualLineWidth();
-				double x2 = it->BoundingX - lw / 2.0;
-				double y2 = it->BoundingY - lw / 2.0;
-				double w2 = qMax(it->BoundingW + lw, 1.0);
-				double h2 = qMax(it->BoundingH + lw, 1.0);
-				if (!QRectF(x2, y2, w2, h2).intersects(QRectF(x1, y1, w1, h1)))
+				if ((item->m_layerID != ll.ID) || (!item->printEnabled()))
 					continue;
-				if ((it->OwnPage != m_Doc->MasterPages.at(ap)->pageNr()) && (it->OwnPage != -1))
+				if (!masterItemIntersectsPage(masterPage, item))
 					continue;
-				if ((m_optimization == OptimizeSize) && it->isImageFrame() && it->imageIsAvailable && (!it->Pfile.isEmpty()) && it->printEnabled() && (!Options.outputSeparations) && Options.useColor)
+				if ((m_optimization == OptimizeSize) && item->isImageFrame() && item->imageIsAvailable && (!item->Pfile.isEmpty()) && item->printEnabled() && (!Options.outputSeparations) && Options.useColor)
 				{
-					errorOccured = !PS_ImageData(it, it->Pfile, it->itemName(), it->ImageProfile, it->UseEmbedded);
+					errorOccured = !PS_ImageData(item, item->Pfile, item->itemName(), item->ImageProfile, item->UseEmbedded);
 					if (errorOccured) break;
 				}
-				PS_TemplateStart(QString("mp_obj_%1_%2").arg(ap).arg(qHash(it)));
-				ProcessItem(m_Doc->MasterPages.at(ap), it, ap+1, true);
+				PS_TemplateStart(QString("mp_obj_%1_%2").arg(ap).arg(qHash(item)));
+				ProcessItem(masterPage, item, ap+1, true);
 				PS_TemplateEnd();
 			}
 			if (errorOccured) break;
@@ -1761,7 +1751,7 @@
 			m_Doc->Layers.levelToLayer(ll, lam);
 			if (!ll.isPrintable)
 				continue;
-			if (!page->masterPageNameEmpty() && !abortExport && !errorOccured)
+			if ((m_Doc->appliedMasterPageIndex(page) >= 0) && !abortExport && !errorOccured)
 			{
 				errorOccured |= !ProcessMasterPageLayer(page, ll, a + 1);
 			}
@@ -2647,97 +2637,128 @@
 	if (!layer.isPrintable)
 		return true;
 
-	ScPage* mPage = m_Doc->MasterPages.at(m_Doc->MasterNames.value(page->masterPageName()));
-	for (int am = 0; am < page->FromMaster.count() && !abortExport; ++am)
+	const int masterIndex = m_Doc->appliedMasterPageIndex(page);
+	if (masterIndex < 0)
+		return true;
+	ScPage* masterPage = m_Doc->MasterPages.at(masterIndex);
+	const QList<PageItem*> masterItems = m_Doc->masterItemsForAppliedPage(page);
+	if (masterItems.isEmpty())
+		return true;
+
+	const bool clipLogicalSpread = m_Doc->isMasterSpread(masterPage->masterPageId());
+	if (clipLogicalSpread)
+	{
+		double bleedLeft = 0.0;
+		double bleedRight = 0.0;
+		double bleedBottom = 0.0;
+		double bleedTop = 0.0;
+		GetBleeds(page, bleedLeft, bleedRight, bleedBottom, bleedTop);
+		const double clipLeft = -bleedLeft;
+		const double clipRight = page->width() + bleedRight;
+		PS_save();
+		PS_moveto(clipLeft, -bleedBottom);
+		PS_lineto(clipRight, -bleedBottom);
+		PS_lineto(clipRight, page->height() + bleedTop);
+		PS_lineto(clipLeft, page->height() + bleedTop);
+		PS_closepath();
+		PS_clip(false);
+	}
+
+	for (PageItem* item : masterItems)
 	{
-		PageItem *ite = page->FromMaster.at(am);
+		if (abortExport)
+			break;
 		if (progressDialog)
 			QCoreApplication::processEvents();
-		if ((ite->m_layerID != layer.ID) || (!ite->printEnabled()))
+		if ((item->m_layerID != layer.ID) || (!item->printEnabled()))
 			continue;
-		if (!ite->isTextContainer())
+		if (!item->isTextContainer())
 		{
-			int mpIndex = m_Doc->MasterNames.value(page->masterPageName());
-			PS_UseTemplate(QString("mp_obj_%1_%2").arg(mpIndex).arg(qHash(ite)));
+			// Template setup culls items that do not touch this physical master half.
+			// Apply the same test here so a projected spread never references a
+			// template that was intentionally not generated for the target half.
+			if (!masterItemIntersectsPage(masterPage, item))
+				continue;
+			PS_UseTemplate(QString("mp_obj_%1_%2").arg(masterIndex).arg(qHash(item)));
 		}
-		else if (!ite->isTextFrame())
+		else if (!item->isTextFrame())
 		{
 			PS_save();
 			// JG : replace what seems mostly duplicate code by corresponding function call (#3936)
-			success &= ProcessItem(mPage, ite, PNr, false, false, true);
+			success &= ProcessItem(masterPage, item, PNr, false, false, true);
 			PS_restore();
 		}
-		else // if (ite->isTextFrame())
+		else // if (item->isTextFrame())
 		{
 			PS_save();
-			if (ite->doOverprint)
+			if (item->doOverprint)
 			{
 				PutStream("true setoverprint\n");
 				PutStream("true setoverprintmode\n");
 			}
-			if (ite->fillColor() != CommonStrings::None)
+			if (item->fillColor() != CommonStrings::None)
 			{
-				SetColor(ite->fillColor(), ite->fillShade(), &h, &s, &v, &k);
+				SetColor(item->fillColor(), item->fillShade(), &h, &s, &v, &k);
 				PS_setcmykcolor_fill(h, s, v, k);
 			}
-			PS_translate(ite->xPos() - mPage->xOffset(), mPage->height() - (ite->yPos() - mPage->yOffset()));
-			if (ite->rotation() != 0)
-				PS_rotate(-ite->rotation());
-			if ((ite->fillColor() != CommonStrings::None) || (ite->GrType != 0))
+			PS_translate(item->xPos() - masterPage->xOffset(), masterPage->height() - (item->yPos() - masterPage->yOffset()));
+			if (item->rotation() != 0)
+				PS_rotate(-item->rotation());
+			if ((item->fillColor() != CommonStrings::None) || (item->GrType != 0))
 			{
-				SetClipPath(ite->PoLine);
+				SetClipPath(item->PoLine);
 				PS_closepath();
-				if (ite->GrType == Gradient_Hatch)
-					PS_HatchFill(ite);
-				else if (ite->GrType != 0)
-					HandleGradientFillStroke(ite, false);
+				if (item->GrType == Gradient_Hatch)
+					PS_HatchFill(item);
+				else if (item->GrType != 0)
+					HandleGradientFillStroke(item, false);
 				else
-					putColor(ite->fillColor(), ite->fillShade(), true);
+					putColor(item->fillColor(), item->fillShade(), true);
 			}
-			if (ite->imageFlippedH())
+			if (item->imageFlippedH())
 			{
-				PS_translate(ite->width(), 0);
+				PS_translate(item->width(), 0);
 				PS_scale(-1, 1);
 			}
-			if (ite->imageFlippedV())
+			if (item->imageFlippedV())
 			{
-				PS_translate(0, -ite->height());
+				PS_translate(0, -item->height());
 				PS_scale(1, -1);
 			}
-			if (ite->itemText.length() != 0)
-				setTextSt(ite, PNr - 1, mPage, true);
-			if ((ite->lineColor() != CommonStrings::None) || (!ite->NamedLStyle.isEmpty()) || (!ite->strokePattern().isEmpty()) || (ite->GrTypeStroke > 0))
-			{
-				PS_setlinewidth(ite->lineWidth());
-				PS_setcapjoin(ite->PLineEnd, ite->PLineJoin);
-				PS_setdash(ite->PLineArt, ite->DashOffset, ite->DashValues);
-				if (ite->NamedLStyle.isEmpty()) // && (ite->lineWidth() != 0.0))
+			if (item->itemText.length() != 0)
+				setTextSt(item, PNr - 1, masterPage, true);
+			if ((item->lineColor() != CommonStrings::None) || (!item->NamedLStyle.isEmpty()) || (!item->strokePattern().isEmpty()) || (item->GrTypeStroke > 0))
+			{
+				PS_setlinewidth(item->lineWidth());
+				PS_setcapjoin(item->PLineEnd, item->PLineJoin);
+				PS_setdash(item->PLineArt, item->DashOffset, item->DashValues);
+				if (item->NamedLStyle.isEmpty()) // && (item->lineWidth() != 0.0))
 				{
-					const ScPattern* strokePattern = m_Doc->checkedPattern(ite->strokePattern());
-					if (strokePattern && (ite->patternStrokePath))
+					const ScPattern* strokePattern = m_Doc->checkedPattern(item->strokePattern());
+					if (strokePattern && (item->patternStrokePath))
 					{
-						QPainterPath path = ite->PoLine.toQPainterPath(false);
-						HandleBrushPattern(ite, path, page, PNr, true);
+						QPainterPath path = item->PoLine.toQPainterPath(false);
+						HandleBrushPattern(item, path, page, PNr, true);
 					}
 					else
 					{
-						SetClipPath(ite->PoLine);
+						SetClipPath(item->PoLine);
 						PS_closepath();
 						if (strokePattern)
-							HandleStrokePattern(ite);
-						else if (ite->GrTypeStroke > 0)
-							HandleGradientFillStroke(ite);
-						else if (ite->lineColor() != CommonStrings::None)
+							HandleStrokePattern(item);
+						else if (item->GrTypeStroke > 0)
+							HandleGradientFillStroke(item);
+						else if (item->lineColor() != CommonStrings::None)
 						{
-							SetColor(ite->lineColor(), ite->lineShade(), &h, &s, &v, &k);
+							SetColor(item->lineColor(), item->lineShade(), &h, &s, &v, &k);
 							PS_setcmykcolor_stroke(h, s, v, k);
-							putColor(ite->lineColor(), ite->lineShade(), false);
+							putColor(item->lineColor(), item->lineShade(), false);
 						}
 					}
 				}
 				else
 				{
-					MultiLine ml = m_Doc->docLineStyles.value(ite->NamedLStyle);
+					MultiLine ml = m_Doc->docLineStyles.value(item->NamedLStyle);
 					for (int it = ml.size() - 1; it > -1; it--)
 					{
 						if (ml[it].Color != CommonStrings::None) //&& (ml[it].Width != 0))
@@ -2747,7 +2768,7 @@
 							PS_setlinewidth(ml[it].Width);
 							PS_setcapjoin(static_cast<Qt::PenCapStyle>(ml[it].LineEnd), static_cast<Qt::PenJoinStyle>(ml[it].LineJoin));
 							PS_setdash(static_cast<Qt::PenStyle>(ml[it].Dash), 0, dum);
-							SetClipPath(ite->PoLine);
+							SetClipPath(item->PoLine);
 							PS_closepath();
 							putColor(ml[it].Color, ml[it].Shade, false);
 						}
@@ -2759,6 +2780,9 @@
 		if (!success)
 			break;
 	}
+
+	if (clipLogicalSpread)
+		PS_restore();
 	return success;
 }
 
@@ -4221,7 +4245,27 @@
 	}
 }
 
-void PSLib::GetBleeds(ScPage* page, double& left, double& right)
+bool PSLib::masterItemIntersectsPage(const ScPage* page, const PageItem* item)
+{
+	double bleedLeft = 0.0;
+	double bleedRight = 0.0;
+	double bleedBottom = 0.0;
+	double bleedTop = 0.0;
+	GetBleeds(page, bleedLeft, bleedRight, bleedBottom, bleedTop);
+
+	const QRectF pageRect(page->xOffset() - bleedLeft,
+		page->yOffset() - bleedTop,
+		page->width() + bleedLeft + bleedRight,
+		page->height() + bleedBottom + bleedTop);
+	const double lineWidth = item->visualLineWidth();
+	const QRectF itemRect(item->BoundingX - lineWidth / 2.0,
+		item->BoundingY - lineWidth / 2.0,
+		qMax(item->BoundingW + lineWidth, 1.0),
+		qMax(item->BoundingH + lineWidth, 1.0));
+	return itemRect.intersects(pageRect);
+}
+
+void PSLib::GetBleeds(const ScPage* page, double& left, double& right)
 {
 	MarginStruct values;
 	m_Doc->getBleeds(page, Options.bleeds, values);
@@ -4229,7 +4273,7 @@
 	right  = values.right();
 }
 
-void PSLib::GetBleeds(ScPage* page, double& left, double& right, double& bottom, double& top)
+void PSLib::GetBleeds(const ScPage* page, double& left, double& right, double& bottom, double& top)
 {
 	MarginStruct values;
 	m_Doc->getBleeds(page, Options.bleeds, values);
Index: scribus/pslib.h
===================================================================
--- scribus/pslib.h	(revision 27864)
+++ scribus/pslib.h	(working copy)
@@ -137,8 +137,8 @@
 		virtual void drawArrow(PageItem *ite, QTransform &arrowTrans, int arrowIndex);
 		virtual void putColor(const QString& color, double shade, bool fill);
 		virtual void putColorNoDraw(const QString& color, double shade);
-		virtual void GetBleeds(ScPage* page, double& left, double& right);
-		virtual void GetBleeds(ScPage* page, double& left, double& right, double& bottom, double& top);
+		virtual void GetBleeds(const ScPage* page, double& left, double& right);
+		virtual void GetBleeds(const ScPage* page, double& left, double& right, double& bottom, double& top);
 		virtual void SetClipPath(const FPointArray &points, bool poly = true);
 		virtual void SetPathAndClip(const FPointArray &points, bool clipRule);
 		virtual void HandleBrushPattern(PageItem *item, QPainterPath &path, ScPage* a, uint PNr, bool master);
@@ -162,6 +162,7 @@
 
 		bool PutImageDataToStream(const QByteArray& image);
 		bool PutInterleavedImageMaskToStream(const QByteArray& image, const QByteArray& mask, bool gray);
+		bool masterItemIntersectsPage(const ScPage* page, const PageItem* item);
 
 		void WriteASCII85Bytes(const QByteArray& array);
 		void WriteASCII85Bytes(const unsigned char* array, int length);
Index: scribus/scpageoutput.cpp
===================================================================
--- scribus/scpageoutput.cpp	(revision 27864)
+++ scribus/scpageoutput.cpp	(working copy)
@@ -101,22 +101,42 @@
 
 void ScPageOutput::drawMasterItems(ScPainterExBase *painter, ScPage *page, const ScLayer& layer, const QRect& clip)
 {
-	if (page->masterPageNameEmpty())
+	if (!layer.isViewable || !layer.isPrintable)
 		return;
-	if (page->FromMaster.count() <= 0)
+
+	const int masterIndex = m_doc->appliedMasterPageIndex(page);
+	if (masterIndex < 0)
 		return;
-	if (!layer.isViewable || !layer.isPrintable)
+	const ScPage* masterPage = m_doc->MasterPages.at(masterIndex);
+	const QList<PageItem*> masterItems = m_doc->masterItemsForAppliedPage(page);
+	if (masterItems.isEmpty())
 		return;
 
-	ScPage* masterPage = m_doc->MasterPages.at(m_doc->MasterNames[page->masterPageName()]);
-	int pageFromMasterCount = page->FromMaster.count();
-	for (int i = 0; i < pageFromMasterCount; ++i)
+	const bool clipAtSpine = m_doc->isMasterSpread(masterPage->masterPageId());
+	if (clipAtSpine)
+	{
+		QRectF projectionClip(QPointF(m_doc->minCanvasCoordinate.x(), m_doc->minCanvasCoordinate.y()),
+		                      QPointF(m_doc->maxCanvasCoordinate.x(), m_doc->maxCanvasCoordinate.y()));
+		projectionClip = projectionClip.normalized();
+		if (masterPage->masterPageSide() == MasterPageSide::Left)
+			projectionClip.setRight(qMin(projectionClip.right(), page->xOffset() + page->width()));
+		else if (masterPage->masterPageSide() == MasterPageSide::Right)
+			projectionClip.setLeft(qMax(projectionClip.left(), page->xOffset()));
+
+		painter->save();
+		painter->newPath();
+		painter->moveTo(projectionClip.left(), projectionClip.top());
+		painter->lineTo(projectionClip.right(), projectionClip.top());
+		painter->lineTo(projectionClip.right(), projectionClip.bottom());
+		painter->lineTo(projectionClip.left(), projectionClip.bottom());
+		painter->lineTo(projectionClip.left(), projectionClip.top());
+		painter->setClipPath();
+	}
+
+	for (PageItem* currItem : masterItems)
 	{
-		PageItem* currItem = page->FromMaster.at(i);
 		if (currItem->m_layerID != layer.ID)
 			continue;
-		if ((currItem->OwnPage != -1) && (currItem->OwnPage != masterPage->pageNr()))
-			continue;
 		if (!currItem->printEnabled())
 			continue;
 		int savedOwnPage = currItem->OwnPage;
@@ -142,6 +162,9 @@
 			currItem->BoundingY = OldBY;
 		}
 	}
+
+	if (clipAtSpine)
+		painter->restore();
 }
 
 void ScPageOutput::drawPageItems(ScPainterExBase *painter, ScPage *page, const ScLayer& layer, const QRect& clip)
Index: scribus/scribusdoc.cpp
===================================================================
--- scribus/scribusdoc.cpp	(revision 27864)
+++ scribus/scribusdoc.cpp	(working copy)
@@ -3018,13 +3018,29 @@
 	if (masterId.isNull() || !isMasterSpread(masterId))
 		return page->FromMaster;
 
-	// Keep FromMaster owner-based for existing output paths. Canvas projections need
-	// the complete logical spread; the target half is selected by spine clipping.
+	return masterItemsForMasterPage(masterPage);
+}
+
+QList<PageItem*> ScribusDoc::masterItemsForMasterPage(const ScPage* masterPage) const
+{
+	QList<PageItem*> items;
+	if (!masterPage)
+		return items;
+
+	const QUuid masterId = masterPage->masterPageId();
+	const bool logicalSpread = !masterId.isNull() && isMasterSpread(masterId);
 	for (PageItem* item : std::as_const(MasterItems))
 	{
-		const bool sameLogicalMaster = (!item->masterPageId().isNull() && item->masterPageId() == masterId)
-			|| (item->masterPageId().isNull() && item->OnMasterPage == masterPage->pageName());
-		if (sameLogicalMaster)
+		if (logicalSpread)
+		{
+			const bool sameLogicalMaster = (!item->masterPageId().isNull() && item->masterPageId() == masterId)
+				|| (item->masterPageId().isNull() && item->OnMasterPage == masterPage->pageName());
+			if (sameLogicalMaster)
+				items.append(item);
+			continue;
+		}
+
+		if (item->OwnPage == masterPage->pageNr() || item->OwnPage == -1)
 			items.append(item);
 	}
 	return items;
Index: scribus/scribusdoc.h
===================================================================
--- scribus/scribusdoc.h	(revision 27864)
+++ scribus/scribusdoc.h	(working copy)
@@ -276,6 +276,8 @@
 		bool isMasterSpread(const QUuid& masterId) const;
 		int appliedMasterPageIndex(const ScPage* page) const;
 		QList<PageItem*> masterItemsForAppliedPage(const ScPage* page) const;
+		//! Returns physical-master items for a normal master, or all items in the logical spread for a true-spread half.
+		QList<PageItem*> masterItemsForMasterPage(const ScPage* masterPage) const;
 		MasterPageSide matchingMasterPageSide(const QUuid& masterId, int pageNumber) const;
 		bool documentSpreadPages(int pageNumber, int& leftPage, int& rightPage) const;
 		void preserveMasterPageMarginsForLayoutChange(int oldPagePositioning);
tsm6-default-master-lifecycle-normal-spread-v1.04.patch (29,413 bytes)   
Index: scribus/commonstrings.cpp
===================================================================
--- scribus/commonstrings.cpp	(revision 27864)
+++ scribus/commonstrings.cpp	(working copy)
@@ -108,6 +108,8 @@
 QString CommonStrings:: trMasterPageNormalMiddle;
 QString CommonStrings:: masterPageNormalRight;
 QString CommonStrings:: trMasterPageNormalRight;
+QString CommonStrings:: masterPageNormalSpread;
+QString CommonStrings:: trMasterPageNormalSpread;
 
 QString CommonStrings::trPenStyle_SolidLine;
 QString CommonStrings::trPenStyle_DashedLine;
@@ -381,6 +383,8 @@
 	CommonStrings::trMasterPageNormalMiddle = tr( "Normal Middle", "Default middle master page" );
 	CommonStrings::masterPageNormalRight    = "Normal Right";
 	CommonStrings::trMasterPageNormalRight  = tr( "Normal Right", "Default right master page" );
+	CommonStrings::masterPageNormalSpread   = "Normal Spread";
+	CommonStrings::trMasterPageNormalSpread = tr( "Normal Spread", "Default facing master spread" );
 
 	CommonStrings::trPenStyle_SolidLine      = tr("Solid Line");
 	CommonStrings::trPenStyle_DashedLine     = tr("Dashed Line");
Index: scribus/commonstrings.h
===================================================================
--- scribus/commonstrings.h	(revision 27864)
+++ scribus/commonstrings.h	(working copy)
@@ -174,6 +174,8 @@
 	static QString trMasterPageNormalMiddle;
 	static QString masterPageNormalRight;
 	static QString trMasterPageNormalRight;
+	static QString masterPageNormalSpread;
+	static QString trMasterPageNormalSpread;
 
 	//Pen Styles
 	static QString trPenStyle_SolidLine;
Index: scribus/scribus.cpp
===================================================================
--- scribus/scribus.cpp	(revision 27864)
+++ scribus/scribus.cpp	(working copy)
@@ -5287,7 +5287,7 @@
 		doc->addPageToSection(wo + 1, where, 1);
 	}
 
-	doc->reconcileMasterPageSides();
+	doc->reconcileMasterPages();
 	view->reformPages(false);
 	doc->updateEndnotesFrames();
 	doc->changed();
@@ -5422,7 +5422,7 @@
 	doc->addPageToAnnotLinks(wot, where, numPages);
 	doc->addPageToSection(wo, where, numPages);
 	if (!m_deferMasterPageSideReconcile)
-		doc->reconcileMasterPageSides();
+		doc->reconcileMasterPages();
 	doc->reformPages();
 	doc->updateEndnotesFrames();
 	updateGUIAfterPagesChanged();
@@ -5478,24 +5478,16 @@
 		int pageLocation = 0;
 		copyDialog.values(masterPageName, copyFromMaster, pageLocation);
 		bool badMasterPageName = doc->MasterNames.contains(masterPageName);
-		badMasterPageName |= (masterPageName == CommonStrings::masterPageNormal);
-		badMasterPageName |= (masterPageName == CommonStrings::trMasterPageNormal);
-		badMasterPageName |= (masterPageName == CommonStrings::trMasterPageNormalLeft);
-		badMasterPageName |= (masterPageName == CommonStrings::trMasterPageNormalMiddle);
-		badMasterPageName |= (masterPageName == CommonStrings::trMasterPageNormalRight);
-		badMasterPageName |=  masterPageName.isEmpty();
+		badMasterPageName |= doc->isDefaultMasterPageName(masterPageName);
+		badMasterPageName |= masterPageName.isEmpty();
 		while (badMasterPageName)
 		{
 			if (!copyDialog.exec())
 				return;
 			copyDialog.values(masterPageName, copyFromMaster, pageLocation);
 			badMasterPageName = doc->MasterNames.contains(masterPageName);
-			badMasterPageName |= (masterPageName == CommonStrings::masterPageNormal);
-			badMasterPageName |= (masterPageName == CommonStrings::trMasterPageNormal);
-			badMasterPageName |= (masterPageName == CommonStrings::trMasterPageNormalLeft);
-			badMasterPageName |= (masterPageName == CommonStrings::trMasterPageNormalMiddle);
-			badMasterPageName |= (masterPageName == CommonStrings::trMasterPageNormalRight);
-			badMasterPageName |=  masterPageName.isEmpty();
+			badMasterPageName |= doc->isDefaultMasterPageName(masterPageName);
+			badMasterPageName |= masterPageName.isEmpty();
 		}
 		int currentPageNumber = doc->currentPage()->pageNr();
 		bool ok = doc->copyPageToMasterPage(currentPageNumber, pageLocation, pageLocationCount, masterPageName, copyFromMaster);
@@ -6110,7 +6102,7 @@
 
 void ScribusMainWindow::deleteMasterPage(const QString& masterPageName)
 {
-	if (!HaveDoc)
+	if (!HaveDoc || doc->isDefaultMasterPage(masterPageName))
 		return;
 
 	const QUuid masterId = doc->masterPageId(masterPageName);
@@ -6278,7 +6270,7 @@
 	}
 	if (!doc->masterPageMode() && !m_deferMasterPageSideReconcile)
 	{
-		doc->reconcileMasterPageSides();
+		doc->reconcileMasterPages();
 		doc->reformPages();
 	}
 	pageSelector->setMaximum(doc->Pages->count());
@@ -6880,6 +6872,8 @@
 	view->reformPages();
 	view->GotoPage(doc->currentPage()->pageNr());
 	view->DrawNew();
+	if (oldDocPrefs.docSetupPrefs.pagePositioning != newDocPrefs.docSetupPrefs.pagePositioning)
+		pagePalette->updateMasterPageList();
 	pagePalette->rebuildPages();
 	emit UpdateRequest(reqCmsOptionsUpdate);
 	doc->changed();
@@ -8119,7 +8113,7 @@
 	if (!savedMasterPageMode && batchEnd)
 	{
 		m_deferMasterPageSideReconcile = false;
-		doc->reconcileMasterPageSides();
+		doc->reconcileMasterPages();
 		doc->reformPages();
 	}
 	doc->rebuildMasterNames();
Index: scribus/scribusdoc.cpp
===================================================================
--- scribus/scribusdoc.cpp	(revision 27864)
+++ scribus/scribusdoc.cpp	(working copy)
@@ -118,6 +118,102 @@
 
 
 
+namespace
+{
+enum class DefaultMasterRole
+{
+	None,
+	Single,
+	Left,
+	Right,
+	Middle,
+	Spread
+};
+
+DefaultMasterRole defaultMasterRoleForName(const QString& name)
+{
+	if (name == CommonStrings::masterPageNormal || name == CommonStrings::trMasterPageNormal)
+		return DefaultMasterRole::Single;
+	if (name == CommonStrings::masterPageNormalLeft || name == CommonStrings::trMasterPageNormalLeft)
+		return DefaultMasterRole::Left;
+	if (name == CommonStrings::masterPageNormalRight || name == CommonStrings::trMasterPageNormalRight)
+		return DefaultMasterRole::Right;
+	if (name == CommonStrings::masterPageNormalMiddle || name == CommonStrings::trMasterPageNormalMiddle)
+		return DefaultMasterRole::Middle;
+	if (name == CommonStrings::masterPageNormalSpread || name == CommonStrings::trMasterPageNormalSpread)
+		return DefaultMasterRole::Spread;
+	return DefaultMasterRole::None;
+}
+
+QString defaultMasterName(DefaultMasterRole role)
+{
+	switch (role)
+	{
+		case DefaultMasterRole::Single:
+			return CommonStrings::trMasterPageNormal;
+		case DefaultMasterRole::Left:
+			return CommonStrings::trMasterPageNormalLeft;
+		case DefaultMasterRole::Right:
+			return CommonStrings::trMasterPageNormalRight;
+		case DefaultMasterRole::Middle:
+			return CommonStrings::trMasterPageNormalMiddle;
+		case DefaultMasterRole::Spread:
+			return CommonStrings::trMasterPageNormalSpread;
+		case DefaultMasterRole::None:
+			break;
+	}
+	return QString();
+}
+
+QStringList defaultMasterNames(DefaultMasterRole role)
+{
+	QStringList names;
+	const QString translatedName = defaultMasterName(role);
+	if (!translatedName.isEmpty())
+		names.append(translatedName);
+
+	QString canonicalName;
+	switch (role)
+	{
+		case DefaultMasterRole::Single:
+			canonicalName = CommonStrings::masterPageNormal;
+			break;
+		case DefaultMasterRole::Left:
+			canonicalName = CommonStrings::masterPageNormalLeft;
+			break;
+		case DefaultMasterRole::Right:
+			canonicalName = CommonStrings::masterPageNormalRight;
+			break;
+		case DefaultMasterRole::Middle:
+			canonicalName = CommonStrings::masterPageNormalMiddle;
+			break;
+		case DefaultMasterRole::Spread:
+			canonicalName = CommonStrings::masterPageNormalSpread;
+			break;
+		case DefaultMasterRole::None:
+			break;
+	}
+	if (!canonicalName.isEmpty() && !names.contains(canonicalName))
+		names.append(canonicalName);
+	return names;
+}
+
+QUuid defaultMasterId(const ScribusDoc* doc, DefaultMasterRole role)
+{
+	const bool spreadRole = role == DefaultMasterRole::Spread;
+	for (const QString& name : defaultMasterNames(role))
+	{
+		const QUuid id = doc->masterPageId(name);
+		if (id.isNull())
+			continue;
+		if (doc->isMasterSpread(id) == spreadRole)
+			return id;
+	}
+	return QUuid();
+}
+}
+
+
 /**
  This class forwards change events for pages and pageitems to 
  the region occupied by this page or pageitem.
@@ -2834,7 +2930,8 @@
 	if (oldPageName != newPageName && MasterNames.contains(newPageName))
 		return false;
 
-	Q_ASSERT(oldPageName != CommonStrings::masterPageNormal && oldPageName != CommonStrings::trMasterPageNormal);
+	if (isDefaultMasterPage(oldPageName))
+		return false;
 
 	for (int index : indices)
 		MasterPages.at(index)->setPageName(newPageName);
@@ -2868,6 +2965,8 @@
 {
 	assert(masterPageMode());
 	assert( Pages->count() > 1 && Pages->count() > pageNumber );
+	if (pageNumber < 0 || pageNumber >= Pages->count() || isDefaultMasterPage(Pages->at(pageNumber)->pageName()))
+		return;
 	setCurrentPage(Pages->at(0));
 	ScPage* page = Pages->takeAt(pageNumber);
 	delete page;
@@ -2978,6 +3077,38 @@
 	return masterPageHasSide(masterId, MasterPageSide::Left) && masterPageHasSide(masterId, MasterPageSide::Right);
 }
 
+bool ScribusDoc::isDefaultMasterPageName(const QString& pageName) const
+{
+	return defaultMasterRoleForName(pageName) != DefaultMasterRole::None;
+}
+
+QStringList ScribusDoc::defaultMasterPageNames() const
+{
+	QStringList names;
+	for (DefaultMasterRole role : { DefaultMasterRole::Single, DefaultMasterRole::Left, DefaultMasterRole::Right, DefaultMasterRole::Middle, DefaultMasterRole::Spread })
+		names.append(defaultMasterNames(role));
+	names.removeDuplicates();
+	return names;
+}
+
+QString ScribusDoc::translatedDefaultMasterPageName(const QString& pageName) const
+{
+	const DefaultMasterRole role = defaultMasterRoleForName(pageName);
+	return role == DefaultMasterRole::None ? pageName : defaultMasterName(role);
+}
+
+bool ScribusDoc::isDefaultMasterPage(const QString& pageName) const
+{
+	const DefaultMasterRole role = defaultMasterRoleForName(pageName);
+	if (role == DefaultMasterRole::None)
+		return false;
+
+	const QUuid id = masterPageId(pageName);
+	if (id.isNull())
+		return false;
+	return isMasterSpread(id) == (role == DefaultMasterRole::Spread);
+}
+
 int ScribusDoc::appliedMasterPageIndex(const ScPage* page) const
 {
 	if (!page)
@@ -3110,6 +3241,156 @@
 	return leftPage >= 0 && rightPage >= 0;
 }
 
+void ScribusDoc::ensureDefaultMasterPages()
+{
+	const int columns = pageSets().at(m_docPrefsData.docSetupPrefs.pagePositioning).Columns;
+	UndoBlocker undoBlocker;
+
+	auto moveConflictingReservedMaster = [this](DefaultMasterRole role)
+	{
+		for (const QString& reservedName : defaultMasterNames(role))
+		{
+			const QUuid existingId = masterPageId(reservedName);
+			if (existingId.isNull())
+				continue;
+			const bool expectedSpread = role == DefaultMasterRole::Spread;
+			if (isMasterSpread(existingId) == expectedSpread)
+				continue;
+
+			const QString replacementName = getUniqueName(reservedName, MasterNames.keys());
+			renameMasterPage(existingId, replacementName);
+		}
+	};
+
+	auto ensureOnePageDefault = [this, &moveConflictingReservedMaster](DefaultMasterRole role) -> ScPage*
+	{
+		QUuid id = defaultMasterId(this, role);
+		if (!id.isNull())
+		{
+			const int index = masterPageIndex(id, MasterPageSide::Single);
+			return index >= 0 ? MasterPages.at(index) : nullptr;
+		}
+
+		moveConflictingReservedMaster(role);
+		ScPage* page = addMasterPage(MasterPages.count(), defaultMasterName(role));
+		switch (role)
+		{
+			case DefaultMasterRole::Left:
+				page->LeftPg = 1;
+				page->Margins.setLeft(page->initialMargins.right());
+				page->Margins.setRight(page->initialMargins.left());
+				break;
+			case DefaultMasterRole::Right:
+				page->LeftPg = 0;
+				page->Margins.setRight(page->initialMargins.right());
+				page->Margins.setLeft(page->initialMargins.left());
+				break;
+			case DefaultMasterRole::Middle:
+				page->LeftPg = 2;
+				page->Margins.setLeft(page->initialMargins.left());
+				page->Margins.setRight(page->initialMargins.left());
+				break;
+			case DefaultMasterRole::Single:
+			case DefaultMasterRole::Spread:
+			case DefaultMasterRole::None:
+				break;
+		}
+		return page;
+	};
+
+	if (columns == 1)
+	{
+		ensureOnePageDefault(DefaultMasterRole::Single);
+		return;
+	}
+
+	ScPage* leftDefault = ensureOnePageDefault(DefaultMasterRole::Left);
+	ScPage* rightDefault = ensureOnePageDefault(DefaultMasterRole::Right);
+	if (columns == 3 || columns == 4)
+	{
+		ensureOnePageDefault(DefaultMasterRole::Middle);
+		return;
+	}
+	if (columns != 2 || !defaultMasterId(this, DefaultMasterRole::Spread).isNull())
+		return;
+
+	moveConflictingReservedMaster(DefaultMasterRole::Spread);
+	const auto spread = addMasterSpread(MasterPages.count(), defaultMasterName(DefaultMasterRole::Spread));
+	spread.first->LeftPg = 1;
+	spread.second->LeftPg = 0;
+	if (leftDefault)
+	{
+		spread.first->copySizingProperties(leftDefault, leftDefault->Margins);
+		leftDefault->guides.copy(&spread.first->guides);
+	}
+	if (rightDefault)
+	{
+		spread.second->copySizingProperties(rightDefault, rightDefault->Margins);
+		rightDefault->guides.copy(&spread.second->guides);
+	}
+
+	// Default spreads can be created while the document is not in master-page
+	// mode, so reformPages() cannot position the pair at that point. Give the
+	// newly created spread valid geometry immediately.
+	const double spreadX = m_docPrefsData.displayPrefs.scratch.left();
+	const double spreadY = m_docPrefsData.displayPrefs.scratch.top();
+	spread.first->setXOffset(spreadX);
+	spread.first->setYOffset(spreadY);
+	spread.second->setXOffset(spreadX + spread.first->width());
+	spread.second->setYOffset(spreadY);
+}
+
+QUuid ScribusDoc::defaultMasterPageIdForLocation(PageLocation pageLocation) const
+{
+	const int columns = pageSets().at(m_docPrefsData.docSetupPrefs.pagePositioning).Columns;
+	if (columns == 1)
+		return defaultMasterId(this, DefaultMasterRole::Single);
+	if (pageLocation == LeftPage)
+		return defaultMasterId(this, DefaultMasterRole::Left);
+	if (pageLocation == RightPage)
+		return defaultMasterId(this, DefaultMasterRole::Right);
+	if (columns == 3 || columns == 4)
+		return defaultMasterId(this, DefaultMasterRole::Middle);
+	return QUuid();
+}
+
+void ScribusDoc::reconcileDefaultMasterPages()
+{
+	if (masterPageMode())
+		return;
+
+	for (int pageNumber = 0; pageNumber < DocPages.count(); ++pageNumber)
+	{
+		ScPage* page = DocPages.at(pageNumber);
+		QUuid currentId = page->appliedMasterPageId();
+		QString currentName;
+		if (!currentId.isNull())
+			currentName = masterPageName(currentId);
+		if (currentName.isEmpty())
+			currentName = page->masterPageName();
+
+		const DefaultMasterRole currentRole = defaultMasterRoleForName(currentName);
+		if (currentRole == DefaultMasterRole::None || currentRole == DefaultMasterRole::Spread)
+			continue;
+		if (!isDefaultMasterPage(currentName))
+			continue;
+
+		const QUuid targetId = defaultMasterPageIdForLocation(locationOfPage(pageNumber));
+		if (targetId.isNull())
+			continue;
+		if (page->appliedMasterPageId() == targetId && page->appliedMasterPageSide() == MasterPageSide::Single)
+			continue;
+
+		applyMasterPageInternal(targetId, MasterPageSide::Single, pageNumber, false);
+	}
+}
+
+void ScribusDoc::reconcileMasterPages()
+{
+	reconcileDefaultMasterPages();
+	reconcileMasterPageSides();
+}
+
 void ScribusDoc::preserveMasterPageMarginsForLayoutChange(int oldPagePositioning)
 {
 	if (oldPagePositioning < 0 || oldPagePositioning >= pageSets().count())
@@ -3185,46 +3466,38 @@
 
 void ScribusDoc::replaceMasterPage(const QString& oldMasterPage)
 {
+	const QUuid oldMasterId = masterPageId(oldMasterPage);
 	for (int pageIndex = 0; pageIndex < DocPages.count(); ++pageIndex)
 	{
 		ScPage* docPage = DocPages.at(pageIndex);
-		if (docPage->masterPageName() != oldMasterPage)
+		QUuid appliedId = docPage->appliedMasterPageId();
+		if (appliedId.isNull())
+			appliedId = masterPageId(docPage->masterPageName());
+		if ((!oldMasterId.isNull() && appliedId != oldMasterId) ||
+			(oldMasterId.isNull() && docPage->masterPageName() != oldMasterPage))
 			continue;
 
-		QString replacementName;
-		const PageLocation pageLoc = locationOfPage(pageIndex);
-		if (pageLoc == LeftPage && MasterNames.contains(CommonStrings::trMasterPageNormalLeft)
-			&& CommonStrings::trMasterPageNormalLeft != oldMasterPage)
-			replacementName = CommonStrings::trMasterPageNormalLeft;
-		else if (pageLoc == RightPage && MasterNames.contains(CommonStrings::trMasterPageNormalRight)
-			&& CommonStrings::trMasterPageNormalRight != oldMasterPage)
-			replacementName = CommonStrings::trMasterPageNormalRight;
-		else if (pageLoc == MiddlePage && MasterNames.contains(CommonStrings::trMasterPageNormalMiddle)
-			&& CommonStrings::trMasterPageNormalMiddle != oldMasterPage)
-			replacementName = CommonStrings::trMasterPageNormalMiddle;
-		else if (MasterNames.contains(CommonStrings::trMasterPageNormal)
-			&& CommonStrings::trMasterPageNormal != oldMasterPage)
-			replacementName = CommonStrings::trMasterPageNormal;
-
-		if (replacementName.isEmpty())
+		QUuid replacementId = defaultMasterPageIdForLocation(locationOfPage(pageIndex));
+		if (replacementId.isNull() || replacementId == oldMasterId)
 		{
 			for (auto it = MasterNames.cbegin(); it != MasterNames.cend(); ++it)
 			{
-				if (it.key() != oldMasterPage)
+				const QUuid candidateId = masterPageId(it.key());
+				if (!candidateId.isNull() && candidateId != oldMasterId)
 				{
-					replacementName = it.key();
+					replacementId = candidateId;
 					break;
 				}
 			}
 		}
 
-		const QUuid replacementId = masterPageId(replacementName);
 		if (!replacementId.isNull())
 			applyMasterPage(replacementId, pageIndex);
 	}
 }
 
 
+
 void ScribusDoc::deletePage(int pageNumber)
 {
 	assert( Pages->count() > 1 && Pages->count() > pageNumber );
@@ -3247,7 +3520,7 @@
 		m_undoManager->action(this, ss);
 	}
 	Pages->swapItemsAt(a, b);
-	reconcileMasterPageSides();
+	reconcileMasterPages();
 	reformPages();
 	changed();
 }
@@ -3301,7 +3574,7 @@
 		ss->set("PAGE_MOVE_NEWPOS", position);
 		m_undoManager->action(this, ss);
 	}
-	reconcileMasterPageSides();
+	reconcileMasterPages();
 	reformPages();
 	if (m_View && m_ScMW)
 	{
@@ -7989,7 +8262,7 @@
 	addPageToAnnotLinks(annotLinksInsert, whereToInsert, copyCount);
 	addPageToSection(existingPage, whereToInsert, copyCount);
 
-	reconcileMasterPageSides();
+	reconcileMasterPages();
 	if (lastDest != nullptr)
 		setCurrentPage(lastDest);
 	else
@@ -15141,37 +15414,7 @@
 
 void ScribusDoc::createDefaultMasterPages()
 {
-	int setcol = pageSets()[m_docPrefsData.docSetupPrefs.pagePositioning].Columns;
-	if (setcol == 1)
-	{
-		addMasterPage(0, CommonStrings::trMasterPageNormal);
-	}
-	else if (setcol == 2)
-	{
-		ScPage *lp = addMasterPage(0, CommonStrings::trMasterPageNormalLeft);
-		lp->LeftPg = 1;
-		lp->Margins.setLeft(lp->initialMargins.right());
-		lp->Margins.setRight(lp->initialMargins.left());
-		lp = addMasterPage(1, CommonStrings::trMasterPageNormalRight);
-		lp->LeftPg = 0;
-		lp->Margins.setRight(lp->initialMargins.right());
-		lp->Margins.setLeft(lp->initialMargins.left());
-	}
-	else if ((setcol == 3) || (setcol == 4))
-	{
-		ScPage *lp = addMasterPage(0, CommonStrings::trMasterPageNormalLeft);
-		lp->LeftPg = 1;
-		lp->Margins.setLeft(lp->initialMargins.right());
-		lp->Margins.setRight(lp->initialMargins.left());
-		lp = addMasterPage(1, CommonStrings::trMasterPageNormalRight);
-		lp->LeftPg = 0;
-		lp->Margins.setRight(lp->initialMargins.right());
-		lp->Margins.setLeft(lp->initialMargins.left());
-		lp = addMasterPage(2, CommonStrings::trMasterPageNormalMiddle);
-		lp->LeftPg = 2;
-		lp->Margins.setLeft(lp->initialMargins.left());
-		lp->Margins.setRight(lp->initialMargins.left());
-	}
+	ensureDefaultMasterPages();
 }
 
 
@@ -17747,8 +17990,11 @@
 	m_docPrefsData = prefsData;
 	double topDisplacement = prefsData.displayPrefs.scratch.top() - oldPrefsData.displayPrefs.scratch.top();
 	double leftDisplacement = prefsData.displayPrefs.scratch.left() - oldPrefsData.displayPrefs.scratch.left();
+	preserveMasterPageMarginsForLayoutChange(oldPrefsData.docSetupPrefs.pagePositioning);
 	applyPrefsPageSizingAndMargins(resizePages, resizeMasterPages, resizePageMargins, resizeMasterPageMargins);
-	reconcileMasterPageSides();
+	if (prefsData.docSetupPrefs.pagePositioning != oldPrefsData.docSetupPrefs.pagePositioning)
+		ensureDefaultMasterPages();
+	reconcileMasterPages();
 
 	int docItemsCount = MasterItems.count();
 	for (int i = 0; i < docItemsCount; ++i)
Index: scribus/scribusdoc.h
===================================================================
--- scribus/scribusdoc.h	(revision 27864)
+++ scribus/scribusdoc.h	(working copy)
@@ -280,7 +280,12 @@
 		QList<PageItem*> masterItemsForMasterPage(const ScPage* masterPage) const;
 		MasterPageSide matchingMasterPageSide(const QUuid& masterId, int pageNumber) const;
 		bool documentSpreadPages(int pageNumber, int& leftPage, int& rightPage) const;
+		bool isDefaultMasterPageName(const QString& pageName) const;
+		QStringList defaultMasterPageNames() const;
+		QString translatedDefaultMasterPageName(const QString& pageName) const;
+		bool isDefaultMasterPage(const QString& pageName) const;
 		void preserveMasterPageMarginsForLayoutChange(int oldPagePositioning);
+		void reconcileMasterPages();
 		void reconcileMasterPageSides();
 		bool isMasterItemOnCurrentMaster(const PageItem* item) const;
 		//! @brief Replace a master page by default one
@@ -288,7 +293,7 @@
 		//! @brief Rename a master page
 		bool renameMasterPage(const QString& oldPageName, const QString& newPageName);
 		bool renameMasterPage(const QUuid& masterId, const QString& newPageName);
-		//! @brief Create the default master pages based on the layout selected by the user, ie, Normal, Normal Left, etc.
+		//! @brief Ensure the built-in master pages required by the current layout, including Normal Spread for facing pages.
 		void createDefaultMasterPages();
 		//! @brief Create the requested pages in a new document, run after createDefaultMasterPages()
 		void createNewDocPages(int pageCount);
@@ -1388,6 +1393,9 @@
 
 	protected:
 		bool applyMasterPageInternal(const QUuid& masterId, MasterPageSide side, int pageNumber, bool recordUndo);
+		void ensureDefaultMasterPages();
+		void reconcileDefaultMasterPages();
+		QUuid defaultMasterPageIdForLocation(PageLocation pageLocation) const;
 		void addSymbols();
 		void applyPrefsPageSizingAndMargins(bool resizePages, bool resizeMasterPages, bool resizePageMargins, bool resizeMasterPageMargins);
 		bool m_hasGUI {false};
Index: scribus/ui/pagepalette_masterpages.cpp
===================================================================
--- scribus/ui/pagepalette_masterpages.cpp	(revision 27864)
+++ scribus/ui/pagepalette_masterpages.cpp	(working copy)
@@ -158,9 +158,9 @@
 	// allow to delete multiple pages in one step
 	foreach (QListWidgetItem * delItem, masterPageListBox->selectedItems())
 	{
-		m_currentPage = delItem->text();
+		m_currentPage = delItem->data(Qt::UserRole).toString();
 
-		if (isReservedName(m_currentPage))
+		if (m_doc->isDefaultMasterPage(m_currentPage))
 			continue;
 
 		QString extraWarn;
@@ -425,7 +425,8 @@
 	QString lastName;
 	for (int selectionIndex = 0; selectionIndex < selectedNames.count(); ++selectionIndex)
 	{
-		QString name = getUniqueName(selectedNames.at(selectionIndex), m_doc->MasterNames);
+		QString name = getNonReservedName(selectedNames.at(selectionIndex));
+		name = getUniqueName(name, m_doc->MasterNames);
 		const QList<int> sourceIndices = indexGroups.value(selectionIndex);
 		if (sourceIndices.isEmpty())
 			continue;
@@ -468,8 +469,7 @@
 
 	bool deleteEnabled = true;
 	deleteEnabled &= (m_doc->MasterNames.count() > 1);
-	deleteEnabled &= (m_currentPage != CommonStrings::masterPageNormal);
-	deleteEnabled &= (m_currentPage != CommonStrings::trMasterPageNormal);
+	deleteEnabled &= !m_doc->isDefaultMasterPage(m_currentPage);
 	deleteButton->setEnabled(deleteEnabled);
 
 	if (m_doc->appMode == modeEditClip)
@@ -508,8 +508,7 @@
 
 	bool deleteEnabled = true;
 	deleteEnabled &= (m_doc->MasterNames.count() > 1);
-	deleteEnabled &= (m_currentPage != CommonStrings::masterPageNormal);
-	deleteEnabled &= (m_currentPage != CommonStrings::trMasterPageNormal);
+	deleteEnabled &= !m_doc->isDefaultMasterPage(m_currentPage);
 	deleteButton->setEnabled(deleteEnabled);
 
 	if (m_doc->appMode == modeEditClip)
@@ -533,17 +532,14 @@
 	masterPageListBox->clear();
 	for (auto it = m_doc->MasterNames.cbegin(); it != m_doc->MasterNames.cend(); ++it)
 	{
-		QString mpName = it.key();
-
-		if (it.key() == CommonStrings::masterPageNormal)
-			mpName = CommonStrings::trMasterPageNormal;
+		QString mpName = getTranslatedReservedName(it.key());
 		QListWidgetItem* mpItem = new QListWidgetItem(mpName);
 		const QUuid masterId = m_doc->masterPageId(it.key());
 		const bool spread = m_doc->isMasterSpread(masterId);
 		mpItem->setIcon(IconManager::instance().loadIcon(spread ? "page-type-facing" : "page-type-single"));
 		mpItem->setToolTip(spread ? tr("Facing master spread") : tr("One-page master"));
 
-		if (!isReservedName(mpName))
+		if (!m_doc->isDefaultMasterPage(it.key()))
 			mpItem->setFlags(mpItem->flags() |= Qt::ItemIsEditable);
 		else
 			mpItem->setFlags(mpItem->flags() &= ~Qt::ItemIsEditable);
@@ -551,12 +547,8 @@
 		mpItem->setData(Qt::UserRole, it.key());
 		masterPageListBox->addItem(mpItem);
 	}
-	deleteButton->setEnabled(m_doc->MasterNames.count() != 1);
-	if (masterPageName == CommonStrings::masterPageNormal)
-	{
-		masterPageName = CommonStrings::trMasterPageNormal;
-		deleteButton->setEnabled(false);
-	}
+	deleteButton->setEnabled(m_doc->MasterNames.count() != 1 && !m_doc->isDefaultMasterPage(masterPageName));
+	masterPageName = getTranslatedReservedName(masterPageName);
 	QList<QListWidgetItem *> itL = masterPageListBox->findItems(masterPageName, Qt::MatchExactly);
 	if (itL.count() != 0)
 		itL.at(0)->setSelected(true);
@@ -628,40 +620,17 @@
 // FIXME: move to a new masterpages.h
 bool PagePalette_MasterPages::isReservedName(const QString& name) const
 {
-	bool retval = name == CommonStrings::masterPageNormal ||
-		name == CommonStrings::trMasterPageNormal ||
-		name == CommonStrings::masterPageNormalLeft ||
-		name == CommonStrings::trMasterPageNormalLeft ||
-		name == CommonStrings::masterPageNormalMiddle ||
-		name == CommonStrings::trMasterPageNormalMiddle ||
-		name == CommonStrings::masterPageNormalRight ||
-		name == CommonStrings::trMasterPageNormalRight;
-	return retval;
+	return m_doc->isDefaultMasterPageName(name);
 }
 
 // FIXME: move to a new masterpages.h
 QString PagePalette_MasterPages::getNonReservedName(const QString& name) const
 {
-	const QStringList reservedNames = {
-		CommonStrings::masterPageNormal,
-		CommonStrings::trMasterPageNormal,
-		CommonStrings::trMasterPageNormalLeft,
-		CommonStrings::trMasterPageNormalMiddle,
-		CommonStrings::trMasterPageNormalRight
-	};
-	return getUniqueName(name, reservedNames);
+	return getUniqueName(name, m_doc->defaultMasterPageNames());
 }
 
 // FIXME: move to a new masterpages.h
 QString PagePalette_MasterPages::getTranslatedReservedName(const QString& name) const
 {
-	if (name == CommonStrings::masterPageNormal)
-		return CommonStrings::trMasterPageNormal;
-	if (name == CommonStrings::masterPageNormalLeft)
-		return CommonStrings::trMasterPageNormalLeft;
-	if (name == CommonStrings::masterPageNormalMiddle)
-		return CommonStrings::trMasterPageNormalMiddle;
-	if (name == CommonStrings::masterPageNormalRight)
-		return CommonStrings::trMasterPageNormalRight;
-	return name;
+	return m_doc->translatedDefaultMasterPageName(name);
 }
Index: scribus/ui/pagepalette_pages.cpp
===================================================================
--- scribus/ui/pagepalette_pages.cpp	(revision 27864)
+++ scribus/ui/pagepalette_pages.cpp	(working copy)
@@ -86,7 +86,7 @@
 
 void PagePalette_Pages::deleteMasterPage(const QString &tmp)
 {
-	if (tmp == CommonStrings::trMasterPageNormal)
+	if (currView->m_doc->isDefaultMasterPage(tmp))
 		return;
 	QString extraWarn;
 	for (int i = 0; i < currView->m_doc->DocPages.count(); ++i)
@@ -313,10 +313,12 @@
 	connect(pageLayout, SIGNAL(firstPageChanged(int)), this, SLOT(handleFirstPage(int)));
 
 	currView->m_doc->preserveMasterPageMarginsForLayoutChange(oldPagePositioning);
-	currView->m_doc->reconcileMasterPageSides();
+	currView->m_doc->createDefaultMasterPages();
+	currView->m_doc->reconcileMasterPages();
 	currView->reformPages();
 	currView->DrawNew();
 	currView->GotoPage(currView->m_doc->currentPageNumber());
+	rebuildMasters();
 	rebuildPages();
 }
 
@@ -325,7 +327,7 @@
 	if (!currView)
 		return;
 	currView->m_doc->setPageSetFirstPage(currView->m_doc->pagePositioning(), fp);
-	currView->m_doc->reconcileMasterPageSides();
+	currView->m_doc->reconcileMasterPages();
 	currView->reformPages();
 	currView->DrawNew();
 	currView->GotoPage(currView->m_doc->currentPageNumber());
@@ -337,7 +339,7 @@
 	if (!currView)
 		return;
 	currView->m_doc->setBindingDirection(direction);
-	currView->m_doc->reconcileMasterPageSides();
+	currView->m_doc->reconcileMasterPages();
 	currView->reformPages();
 	currView->DrawNew();
 	currView->GotoPage(currView->m_doc->currentPageNumber());
PatchYes

Activities

buttar

2026-09-27 05:50

reporter   ~0054498

The screenshots
spread-master1-PROPOSED.png (737,399 bytes)   
spread-master1-PROPOSED.png (737,399 bytes)   

buttar

2026-09-27 13:15

reporter   ~0054499

apply-to-spread-PROPOSED.png (147,627 bytes)   
apply-to-spread-PROPOSED.png (147,627 bytes)   
spread-master2-PROPOSED.png (175,913 bytes)   
spread-master2-PROPOSED.png (175,913 bytes)   

Issue History

Date Modified Username Field Change
2026-09-27 05:45 buttar New Issue
2026-09-27 05:45 buttar Tag Attached: #please_test
2026-09-27 05:45 buttar File Added: tsm1-master-model-persistence-v1.01.patch
2026-09-27 05:45 buttar File Added: tsm2-spread-editor-v1.16.patch
2026-09-27 05:45 buttar File Added: tsm3-application-reflow-v1.05.patch
2026-09-27 05:45 buttar File Added: tsm4-cross-spread-rendering-v1.01.patch
2026-09-27 05:45 buttar File Added: tsm5-output-projection-v1.06.patch
2026-09-27 05:45 buttar File Added: tsm6-default-master-lifecycle-normal-spread-v1.04.patch
2026-09-27 05:50 buttar Note Added: 0054498
2026-09-27 05:50 buttar File Added: spread-master1-PROPOSED.png
2026-09-27 13:15 buttar Note Added: 0054499
2026-09-27 13:15 buttar File Added: apply-to-spread-PROPOSED.png
2026-09-27 13:15 buttar File Added: spread-master2-PROPOSED.png