Index: resources/iconsets/1_7_0/1_7_0.xml
===================================================================
--- resources/iconsets/1_7_0/1_7_0.xml	(Revision 26366)
+++ resources/iconsets/1_7_0/1_7_0.xml	(Arbeitskopie)
@@ -138,8 +138,10 @@
 		<icon id="u_group.png" file="16/object-group.svg" /><!-- Outliner -->
 		<icon id="u_undo16.png" file="16/action-undo.svg" /><!-- Barcode -->
 		<icon id="ungroup.png" file="16/object-ungroup.svg" />
+		<icon id="add" file="16/action-add.svg" />
 		<icon id="collapse" file="16/action-chevron-right.svg" />
 		<icon id="context-menu" file="16/action-menu.svg" />
+		<icon id="delete" file="16/action-delete.svg" />
 		<icon id="delete-selected" file="16/action-delete-selected.svg" />
 		<icon id="erase-shadow-off" file="16/action-shadow-erase-off.svg" />
 		<icon id="erase-shadow-on" file="16/action-shadow-erase-on.svg" />
@@ -150,11 +152,9 @@
 		<icon id="reset-style-changes" file="16/action-reload.svg" /> <!-- 16/action-revert-changes.svg -->
 		<icon id="scale-height" file="16/action-scale-height.svg" />
 		<icon id="scale-width" file="16/action-scale-width.svg" />
+		<icon id="settings" file="16/properties-external-tools.svg" />
 		<icon id="swap" file="16/action-swap.svg" />
 		<icon id="toggle-object-coordination" file="16/object-xycoordination.svg" />
-		<icon id="context-menu" file="16/action-menu.svg" />
-		<icon id="scale-width" file="16/action-scale-width.svg" />
-		<icon id="scale-height" file="16/action-scale-height.svg" />
 		<icon id="whitespace-review-mode" file="16/action-whitespace-review.svg" />
 			
 		<!-- Alerts -->
Index: scribus/CMakeLists_Sources.txt
===================================================================
--- scribus/CMakeLists_Sources.txt	(Revision 26366)
+++ scribus/CMakeLists_Sources.txt	(Arbeitskopie)
@@ -114,6 +114,7 @@
 	nfttemplate.cpp
 	nodeeditcontext.cpp
 	notesstyles.cpp
+	opticalmarginlookup.cpp
 	pageitem.cpp
 	pageitem_arc.cpp
 	pageitem_group.cpp
@@ -381,6 +382,7 @@
 	ui/nodeeditpalette.cpp
 	ui/notesstyleseditor.cpp
 	ui/numformatcombo.cpp
+	ui/opticalmargineditor.cpp
 	ui/opticalmarginswidget.cpp
 	ui/outlinepalette.cpp
 	ui/outputpreview_pdf.cpp
Index: scribus/pageitem_textframe.cpp
===================================================================
--- scribus/pageitem_textframe.cpp	(Revision 26366)
+++ scribus/pageitem_textframe.cpp	(Arbeitskopie)
@@ -41,6 +41,7 @@
 #include "marks.h"
 #include "notesstyles.h"
 #include "numeration.h"
+#include "opticalmarginlookup.h"
 #include "pageitem.h"
 #include "pageitem_group.h"
 #include "pageitem_noteframe.h"
@@ -901,44 +912,58 @@
 
 
 	//defined but not used
-	///// calculate how much the first char should stick out to the left
-	//double opticalLeftMargin(const StoryText& itemText)
-	//{
-	//	int b = line.firstChar;
-	//	while (b < line.lastChar && (itemText.flags(b) & ScLayout_SuppressSpace))
-	//		   ++b;
-	//
-	//	double chs = itemText.charStyle(b).fontSize() * (itemText.charStyle(b).scaleH() / 1000.0);
-	//	QChar chr = itemText.text(b);
-	//	double leftCorr = itemText.charStyle(b).font().realCharWidth(chr, chs / 10.0);
-	//	if (QString("'´`").indexOf(chr) >= 0
-	//		|| chr == QChar(0x2018) // quote 6
-	//		|| chr == QChar(0x2019) // quote 9
-	//		|| chr == QChar(0x201a) // lower quote 9
-	//		|| chr == QChar(0x201b) // upper reversed 9 6
-	//		|| chr == QChar(0x2039) // single guillemet <
-	//		|| chr == QChar(0x203a) // single guillemet >
-	//		)
-	//		leftCorr *= -0.7;
-	//	else if (QString("\"").indexOf(chr) >= 0
-	//			 || chr == QChar(0x00ab) // guillemet <<
-	//			 || chr == QChar(0x00bb) // guillemet >>
-	//			 || chr == QChar(0x201c) // quote 66
-	//			 || chr == QChar(0x201d) // quote 99
-	//			 || chr == QChar(0x201e) // lower quote 99
-	//			 || chr == QChar(0x201f) // upper reversed 99
-	//			 )
-	//		leftCorr *= -0.5;
-	//	else {
-	//		leftCorr = itemText.charStyle(b).font().charWidth(QChar(' '), chs / 10.0, chr);
-	//		leftCorr -= itemText.charStyle(b).font().charWidth(QChar(' '), chs / 10.0);
-	////				double leftCorr2 = itemText.charStyle(a).font().charWidth(QChar('K'), chs / 10.0, chr);
-	////				leftCorr2 -= itemText.charStyle(a).font().charWidth(QChar('K'), chs / 10.0);
-	////				leftCorr = qMin(leftCorr, leftCorr2);
-	//	}
-	//	return leftCorr;
-	//}
+	/// calculate how much the first char should stick out to the left
+	double opticalLeftMargin(const StoryText& itemText)
+	{
+		int b = 0;
+		while (b < lineData.lastCluster && (itemText.flags(b) & ScLayout_SuppressSpace))
+			   ++b;
 
+		if (b < lineData.lastCluster)
+		{
+			const CharStyle& style = glyphs[b].style();
+			const ScFace& font = style.font();
+			double chs = style.fontSize() * (style.scaleH() / 1000.0);
+			QChar chr = itemText.text(glyphs[b].firstChar());
+			double leftCorr = font.glyphBBox(font.char2CMap(chr.unicode()), chs / 10.0).width;
+
+			leftCorr *= OpticalMarginLookup::instance().offset(chr).first;
+
+// 			if (QString("'´`").indexOf(chr) >= 0
+// 					|| chr == QChar(0x2018) // quote 6
+// 					|| chr == QChar(0x2019) // quote 9
+// 					|| chr == QChar(0x201a) // lower quote 9
+// 					|| chr == QChar(0x201b) // upper reversed 9 6
+// 					|| chr == QChar(0x2039) // single guillemet <
+// 					|| chr == QChar(0x203a) // single guillemet >
+// 					)
+// 				leftCorr *= 0.7;
+// 			else if (QString("\"").indexOf(chr) >= 0
+// 					 || chr == QChar(0x00ab) // guillemet <<
+// 					 || chr == QChar(0x00bb) // guillemet >>
+// 					 || chr == QChar(0x201c) // quote 66
+// 					 || chr == QChar(0x201d) // quote 99
+// 					 || chr == QChar(0x201e) // lower quote 99
+// 					 || chr == QChar(0x201f) // upper reversed 99
+// 					 )
+// 				leftCorr *= 0.5;
+// 			else {
+// #if 0
+// 				leftCorr = itemText.charStyle(b).font().charWidth(QChar(' '), chs / 10.0, chr);
+// 				leftCorr -= itemText.charStyle(b).font().charWidth(QChar(' '), chs / 10.0);
+// 				//				double leftCorr2 = itemText.charStyle(a).font().charWidth(QChar('K'), chs / 10.0, chr);
+// 				//				leftCorr2 -= itemText.charStyle(a).font().charWidth(QChar('K'), chs / 10.0);
+// 				//				leftCorr = qMin(leftCorr, leftCorr2);
+// #else
+// 				leftCorr = 0;
+// #endif
+// 			}
+
+			return leftCorr;
+		}
+		return 0.0;
+	}
+
 	/// calculate how much the last char should stick out to the right
 	double opticalRightMargin(const StoryText& itemText)
 	{
@@ -956,41 +981,46 @@
 			QChar chr = itemText.text(glyphs[b].lastChar());
 			double rightCorr;
 			if (glyphs[b].hasFlag(ScLayout_SoftHyphenVisible))
-				rightCorr = font.hyphenWidth(style, chs / 10.0);
+				rightCorr = font.hyphenWidth(style, chs / 10.0) * 0.5;
 			else
+			{
 				rightCorr = font.glyphBBox(font.char2CMap(chr.unicode()), chs / 10.0).width;
-			if (glyphs[b].hasFlag(ScLayout_SoftHyphenVisible)
-				|| QString("-,.`´'~").indexOf(chr) >= 0
-				|| chr == QChar(0x2010)
-				|| chr == QChar(0x2018)
-				|| chr == QChar(0x2019)
-				|| chr == QChar(0x201a)
-				|| chr == QChar(0x201b)
-				|| chr == QChar(0x2039)
-				|| chr == QChar(0x203a)
-				|| chr == QChar(0x2032) // PRIME
-				)
-				rightCorr *= 0.7;
-			else if (QString(";:\"").indexOf(chr) >= 0
-					 || chr == QChar(0x00ab)
-					 || chr == QChar(0x00bb)
-					 || chr == QChar(0x201c)
-					 || chr == QChar(0x201d)
-					 || chr == QChar(0x201e)
-					 || chr == QChar(0x201f)
-					 || chr == QChar(0x2013) // EN DASH
-					 || chr == QChar(0x2033) // double prime
-					 )
-				rightCorr *= 0.5;
-			else {
-#if 0
-				// FIXME HOST: is the kerning with "." a really reliable way to check this?
-				rightCorr = chStyle.font().realCharWidth(chr, chs / 10.0);
-				rightCorr -= chStyle.font().charWidth(chr, chs / 10.0, QChar('.'));
-#else
-				rightCorr = 0;
-#endif
+				rightCorr *= OpticalMarginLookup::instance().offset(chr).second;
 			}
+
+
+// 			if (glyphs[b].hasFlag(ScLayout_SoftHyphenVisible)
+// 				|| QString("-,.`´'~").indexOf(chr) >= 0
+// 				|| chr == QChar(0x2010) // Hyphen
+// 				|| chr == QChar(0x2018) // quote 6
+// 				|| chr == QChar(0x2019) // quote 9
+// 				|| chr == QChar(0x201a)
+// 				|| chr == QChar(0x201b)
+// 				|| chr == QChar(0x2039)
+// 				|| chr == QChar(0x203a)
+// 				|| chr == QChar(0x2032) // PRIME
+// 				)
+// 				rightCorr *= 0.7;
+// 			else if (QString(";:\"").indexOf(chr) >= 0
+// 					 || chr == QChar(0x00ab)
+// 					 || chr == QChar(0x00bb)
+// 					 || chr == QChar(0x201c)
+// 					 || chr == QChar(0x201d)
+// 					 || chr == QChar(0x201e)
+// 					 || chr == QChar(0x201f)
+// 					 || chr == QChar(0x2013) // EN DASH
+// 					 || chr == QChar(0x2033) // double prime
+// 					 )
+// 				rightCorr *= 0.5;
+// 			else {
+// #if 0
+// 				// FIXME HOST: is the kerning with "." a really reliable way to check this?
+// 				rightCorr = chStyle.font().realCharWidth(chr, chs / 10.0);
+// 				rightCorr -= chStyle.font().charWidth(chr, chs / 10.0, QChar('.'));
+// #else
+// 				rightCorr = 0;
+// #endif
+// 			}
 			return rightCorr;
 		}
 		return 0.0;
@@ -2438,6 +2468,11 @@
 					{
 						if (opticalMargins & ParagraphStyle::OM_RightHangingPunct)
 							current.lineData.width += current.opticalRightMargin(itemText);
+						if (opticalMargins & ParagraphStyle::OM_LeftHangingPunct)
+						{
+							current.lineData.width += current.opticalLeftMargin(itemText);
+							current.lineData.x -= current.opticalLeftMargin(itemText);
+						}
 
 						OFs = 0;
 						if (style.alignment() == ParagraphStyle::RightAligned)
@@ -2467,6 +2502,9 @@
 						{
 							if (opticalMargins & ParagraphStyle::OM_RightHangingPunct)
 								current.lineData.naturalWidth += current.opticalRightMargin(itemText);
+							if (opticalMargins & ParagraphStyle::OM_LeftHangingPunct)
+								current.lineData.naturalWidth += current.opticalLeftMargin(itemText);
+
 							double optiWidth = current.colRight - style.rightMargin() - style.lineSpacing() / 2.0 - current.lineData.x;
 							if (current.lineData.naturalWidth > optiWidth)
 								current.lineData.width = qMax(current.lineData.width - current.maxShrink, optiWidth);
@@ -2553,6 +2591,11 @@
 						// Justification
 						if (opticalMargins & ParagraphStyle::OM_RightHangingPunct)
 							current.lineData.width += current.opticalRightMargin(itemText);
+						if (opticalMargins & ParagraphStyle::OM_LeftHangingPunct)
+						{
+							current.lineData.width += current.opticalLeftMargin(itemText);
+							current.lineData.x -= current.opticalLeftMargin(itemText);
+						}
 						// #12565: Right alignment of hyphens
 						// The additional character width has already been taken into account
 						// above via the line break position, so it's not necessary to increase
@@ -2579,6 +2622,9 @@
 						{
 							if (opticalMargins & ParagraphStyle::OM_RightHangingPunct)
 								current.lineData.naturalWidth += current.opticalRightMargin(itemText);
+							if (opticalMargins & ParagraphStyle::OM_LeftHangingPunct)
+								current.lineData.naturalWidth += current.opticalLeftMargin(itemText);
+
 							current.indentLine(style, OFs);
 						}
 						current.xPos = current.lineData.x + current.lineData.width;
@@ -2841,6 +2887,11 @@
 
 			if (opticalMargins & ParagraphStyle::OM_RightHangingPunct)
 				current.lineData.width += current.opticalRightMargin(itemText);
+			if (opticalMargins & ParagraphStyle::OM_LeftHangingPunct)
+			{
+				current.lineData.width += current.opticalLeftMargin(itemText);
+				current.lineData.x -= current.opticalLeftMargin(itemText);
+			}
 
 			OFs = 0;
 			if (style.alignment() == ParagraphStyle::RightAligned)
@@ -2869,6 +2920,9 @@
 			{
 				if (opticalMargins & ParagraphStyle::OM_RightHangingPunct)
 					current.lineData.naturalWidth += current.opticalRightMargin(itemText);
+				if (opticalMargins & ParagraphStyle::OM_LeftHangingPunct)
+					current.lineData.naturalWidth -= current.opticalLeftMargin(itemText);
+
 				current.indentLine(style, OFs);
 			}
 			if (current.glyphs[0].hasFlag(ScLayout_DropCap))
Index: scribus/plugins/fileloader/scribus170format/scribus170format.cpp
===================================================================
--- scribus/plugins/fileloader/scribus170format/scribus170format.cpp	(Revision 26366)
+++ scribus/plugins/fileloader/scribus170format/scribus170format.cpp	(Arbeitskopie)
@@ -1974,6 +1974,11 @@
 			if (!success) break;
 			m_Doc->setUsesMarksAndNotes(true);
 		}
+		else if (tagName == QLatin1String("OpticalMarginGroups"))
+		{
+			success = readOpticalMarginGroups(m_Doc, reader);
+			if (!success) break;
+		}
 		else
 		{
 			reader.skipCurrentElement();
@@ -3974,6 +3979,29 @@
 	return !reader.hasError();
 }
 
+bool Scribus170Format::readOpticalMarginGroups(ScribusDoc *doc, ScXmlStreamReader &reader) const
+{
+	QString tagName(reader.nameAsString());
+	OpticalMarginGroups groups;
+
+	while (!reader.atEnd() && !reader.hasError())
+	{
+		reader.readNext();
+		if (reader.isEndElement() && reader.name() == tagName)
+			break;
+		if (reader.isStartElement() && reader.name() == QLatin1String("OpticalMarginGroup"))
+		{
+			ScXmlStreamAttributes attrs = reader.scAttributes();
+			groups.insert(QPair<double, double>(attrs.valueAsDouble("Left"), attrs.valueAsDouble("Right")), unicodeToString(attrs.valueAsString("Characters")));
+
+		}
+	}
+
+	doc->typographicPrefs().opticalMarginGroups = groups;
+	m_Doc->setOpticalMarginGroups(groups);
+	return !reader.hasError();
+}
+
 bool Scribus170Format::readSections(ScribusDoc* doc, ScXmlStreamReader& reader) const
 {
 	QString tagName(reader.nameAsString());
Index: scribus/plugins/fileloader/scribus170format/scribus170format.h
===================================================================
--- scribus/plugins/fileloader/scribus170format/scribus170format.h	(Revision 26366)
+++ scribus/plugins/fileloader/scribus170format/scribus170format.h	(Arbeitskopie)
@@ -162,6 +162,7 @@
 		bool readNotesStyles(ScribusDoc* doc, ScXmlStreamReader& reader) const;
 		bool readNotesFrames(ScXmlStreamReader &reader);
 		bool readMarks(ScribusDoc* doc, ScXmlStreamReader& reader);
+		bool readOpticalMarginGroups(ScribusDoc* doc, ScXmlStreamReader& reader) const;
 
 		//lists of items and marks with names only, which need update to pointers
 		QMap<Mark*, int> markeredItemsMap;
@@ -213,6 +214,7 @@
 		void writeIndexes(ScXmlStreamWriter& docu);
 		void writeTOC(ScXmlStreamWriter& docu);
 		void writeMarks(ScXmlStreamWriter & docu);
+		void writeOpticalMarginGroups(ScXmlStreamWriter & docu);
 		void writeNotesStyles(ScXmlStreamWriter & docu);
 		void writeNotesStyles(ScXmlStreamWriter & docu, const QStringList& styleSelection);
 		void writeNotesFrames(ScXmlStreamWriter & docu);
Index: scribus/plugins/fileloader/scribus170format/scribus170format_save.cpp
===================================================================
--- scribus/plugins/fileloader/scribus170format/scribus170format_save.cpp	(Revision 26366)
+++ scribus/plugins/fileloader/scribus170format/scribus170format_save.cpp	(Arbeitskopie)
@@ -45,8 +45,8 @@
 #include "ui/missing.h"
 #include "units.h"
 #include "util.h"
-
 #include "util_color.h"
+#include "util_text.h"
 
 QString Scribus170Format::saveElements(double xp, double yp, double wp, double hp, Selection* selection, QByteArray &prevData)
 {
@@ -539,6 +539,7 @@
 	writeIndexes(docu);
 	writeTOC(docu);
 	writeMarks(docu);
+	writeOpticalMarginGroups(docu);
 	writeNotesStyles(docu);
 	writeNotesFrames(docu);
 	writeNotes(docu);
@@ -1532,6 +1533,21 @@
 	docu.writeEndElement();
 }
 
+void Scribus170Format::writeOpticalMarginGroups(ScXmlStreamWriter & docu)
+{
+	docu.writeStartElement("OpticalMarginGroups");
+
+	for (auto [offset, str] : m_Doc->typographicPrefs().opticalMarginGroups.asKeyValueRange())
+	{
+		docu.writeEmptyElement("OpticalMarginGroup");
+		docu.writeAttribute("Left", offset.first);
+		docu.writeAttribute("Right", offset.second);
+		docu.writeAttribute("Characters", stringToUnicode(str));
+	}
+
+	docu.writeEndElement();
+}
+
 void Scribus170Format::writeNotesStyles(ScXmlStreamWriter & docu)
 {
 	if (m_Doc->m_docNotesStylesList.isEmpty())
Index: scribus/prefsmanager.cpp
===================================================================
--- scribus/prefsmanager.cpp	(Revision 26366)
+++ scribus/prefsmanager.cpp	(Arbeitskopie)
@@ -60,6 +60,7 @@
 #include "util_color.h"
 #include "util_file.h"
 #include "util_ghostscript.h"
+#include "util_text.h"
 
 #include "ui/missing.h"
 #include "ui/modetoolbar.h"
@@ -223,6 +224,7 @@
 	appPrefs.typoPrefs.valueUnderlineWidth = -1;
 	appPrefs.typoPrefs.valueStrikeThruPos = -1;
 	appPrefs.typoPrefs.valueStrikeThruWidth = -1;
+	appPrefs.typoPrefs.opticalMarginGroups = OpticalMarginLookup::instance().defaultOpticalMarginGroups();
 	appPrefs.guidesPrefs.valueBaselineGrid = 14.4;
 	appPrefs.guidesPrefs.offsetBaselineGrid = 0.0;
 	appPrefs.displayPrefs.showToolTips = true;
@@ -1920,6 +1922,19 @@
 	experimentalElem.setAttribute("NotesEnabled", appPrefs.experimentalFeaturePrefs.notesEnabled);
 	elem.appendChild(experimentalElem);
 
+	QDomElement opticalMarginGroupsAttr = docu.createElement("OpticalMarginGroups");
+	for (auto [offset, str] : appPrefs.typoPrefs.opticalMarginGroups.asKeyValueRange())
+	{
+		//QByteArray chars = str.toUtf8();
+		QDomElement item = docu.createElement("OpticalMarginGroup");
+		item.setAttribute("Left", offset.first);
+		item.setAttribute("Right", offset.second);
+		//item.setAttribute("Characters", chars.toHex());
+		item.setAttribute("Characters", stringToUnicode(str));
+		opticalMarginGroupsAttr.appendChild(item);
+	}
+	elem.appendChild(opticalMarginGroupsAttr);
+
 	// write file
 	bool result = false;
 	QFile f(filePath);
@@ -2752,7 +2767,28 @@
 		{
 			appPrefs.experimentalFeaturePrefs.notesEnabled = static_cast<bool>(dc.attribute("NotesEnabled", "0").toInt());
 		}
+		// optical margin groups
+		if (dc.tagName() == "OpticalMarginGroups")
+		{
+			OpticalMarginGroups groups;
 
+			for (QDomNode group = domNode.firstChild(); !group.isNull(); group = group.nextSibling())
+			{
+				QDomElement groupAttr = group.toElement();
+				if (groupAttr.tagName() != "OpticalMarginGroup")
+					continue;
+
+					// const QByteArray ba = QByteArray::fromHex(groupAttr.attribute("Characters").toUtf8());
+					// QString chars(ba.constData());
+					QString chars = unicodeToString(groupAttr.attribute("Characters"));
+					double left = ScCLocale::toDoubleC(groupAttr.attribute("Left"), 0.5);
+					double right = ScCLocale::toDoubleC(groupAttr.attribute("Right"), 0.5);
+					groups.insert(QPair<double, double>(left, right), chars);
+			}
+
+			appPrefs.typoPrefs.opticalMarginGroups = groups;
+		}
+
 		domNode = domNode.nextSibling();
 	}
 	// Some sanity checks
Index: scribus/prefsstructs.h
===================================================================
--- scribus/prefsstructs.h	(Revision 26366)
+++ scribus/prefsstructs.h	(Arbeitskopie)
@@ -20,6 +20,7 @@
 #include <QHash>
 
 #include "documentinformation.h"
+#include "opticalmarginlookup.h"
 #include "pagestructs.h"
 #include "pdfoptions.h"
 #include "scfonts.h"
@@ -80,6 +81,7 @@
 	int valueUnderlineWidth; //! Underline width
 	int valueStrikeThruPos; //! Strike-through displacement
 	int valueStrikeThruWidth; //! Strike-through line width
+	OpticalMarginGroups opticalMarginGroups; //! OpticalMargin Group List
 
 	inline bool operator==(const TypoPrefs &other) const
 	{
Index: scribus/scribusdoc.h
===================================================================
--- scribus/scribusdoc.h	(Revision 26366)
+++ scribus/scribusdoc.h	(Arbeitskopie)
@@ -49,6 +49,7 @@
 #include "nodeeditcontext.h"
 #include "notesstyles.h"
 #include "observable.h"
+#include "opticalmarginlookup.h"
 #include "pageitem.h"
 #include "pageitem_group.h"
 #include "pageitem_latexframe.h"
@@ -226,6 +227,8 @@
 	int marginPreset() const { return m_docPrefsData.docSetupPrefs.marginPreset; }
 	void setMarginPreset(int mp) { m_docPrefsData.docSetupPrefs.marginPreset = mp; }
 
+	void setOpticalMarginGroups(OpticalMarginGroups groups) { OpticalMarginLookup::instance().setOpticalMarginGrous(groups); }
+
 	TypoPrefs& typographicPrefs() { return m_docPrefsData.typoPrefs; }
 	GuidesPrefs& guidesPrefs() { return m_docPrefsData.guidesPrefs; }
 	ItemToolPrefs& itemToolPrefs() { return m_docPrefsData.itemToolPrefs; }
Index: scribus/ui/prefs_typography.cpp
===================================================================
--- scribus/ui/prefs_typography.cpp	(Revision 26366)
+++ scribus/ui/prefs_typography.cpp	(Arbeitskopie)
@@ -53,6 +53,7 @@
 	strikeoutLineWidthSpinBox->setValue(prefsData->typoPrefs.valueStrikeThruWidth / 10.0);
 	smallcapsScalingSpinBox->setValue(prefsData->typoPrefs.valueSmallCaps);
 	automaticLineSpacingSpinBox->setValue(prefsData->typoPrefs.autoLineSpacing);
+	opticalMarginsEditor->setOpticalMarginGroups(prefsData->typoPrefs.opticalMarginGroups);
 }
 
 void Prefs_Typography::saveGuiToPrefs(struct ApplicationPrefs *prefsData) const
@@ -67,5 +68,6 @@
 	prefsData->typoPrefs.valueStrikeThruWidth = strikeoutLineWidthSpinBox->value() * 10.0;
 	prefsData->typoPrefs.valueSmallCaps = smallcapsScalingSpinBox->value();
 	prefsData->typoPrefs.autoLineSpacing = automaticLineSpacingSpinBox->value();
+	prefsData->typoPrefs.opticalMarginGroups = opticalMarginsEditor->opticalMarginGroups();
 }
 
Index: scribus/util_text.cpp
===================================================================
--- scribus/util_text.cpp	(Revision 26366)
+++ scribus/util_text.cpp	(Arbeitskopie)
@@ -94,3 +94,29 @@
 	return QString(xml.c_str());
 }
 
+
+QString stringToUnicode(const QString &text)
+{
+	QString out;
+
+	for (auto c : text)
+		out += QString("U+%1,").arg(c.unicode(), 4, 16, QChar('0')).toUpper();
+
+	return out.mid(0, out.length() -1);
+
+}
+
+QString unicodeToString(const QString &text)
+{
+	QString out;
+	for (const auto &str : text.split(",", Qt::SkipEmptyParts))
+	{
+		QString tmp(str);
+		tmp.remove("U+");
+
+		bool ok;
+		out += QChar(tmp.toInt(&ok, 16));
+	}
+
+	return out;
+}
Index: scribus/util_text.h
===================================================================
--- scribus/util_text.h	(Revision 26366)
+++ scribus/util_text.h	(Arbeitskopie)
@@ -30,4 +30,7 @@
 //returns string with saxed story
 QString SCRIBUS_API saxedText(StoryText* story);
 
+QString SCRIBUS_API stringToUnicode(const QString &text);
+QString SCRIBUS_API unicodeToString(const QString &text);
+
 #endif
