Index: scribus/pageitem_textframe.cpp
===================================================================
--- scribus/pageitem_textframe.cpp	(Revision 26361)
+++ scribus/pageitem_textframe.cpp	(Arbeitskopie)
@@ -68,6 +68,7 @@
 #include "units.h"
 #include "util.h"
 #include "util_math.h"
+#include "util_text.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::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::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/util_text.cpp
===================================================================
--- scribus/util_text.cpp	(Revision 26361)
+++ scribus/util_text.cpp	(Arbeitskopie)
@@ -94,3 +94,79 @@
 	return QString(xml.c_str());
 }
 
+
+OpticalMarginLookup* OpticalMarginLookup::m_instance = nullptr;
+
+OpticalMarginLookup::OpticalMarginLookup(QObject *parent) : QObject(parent)
+{
+	// https://en.wikipedia.org/wiki/Optical_margin_alignment
+	// https://symbl.cc/en/unicode-table/
+	Margins.clear();
+
+	// Char, left offset, right offset
+	Margins.insert(QChar(0x0022), QPair<double, double>(0.5, 0.5)); // Quotation Mark "
+	Margins.insert(QChar(0x0027), QPair<double, double>(0.75, 0.75)); // Apostrophe '
+	Margins.insert(QChar(0x0028), QPair<double, double>(0.5, 0.5)); // Left Parenthesis (
+	Margins.insert(QChar(0x0029), QPair<double, double>(0.5, 0.5)); // Right Parenthesis )
+	Margins.insert(QChar(0x002a), QPair<double, double>(0.75, 0.75)); // Asterisk *
+	Margins.insert(QChar(0x002c), QPair<double, double>(0.0, 0.75)); // Comma ,
+	Margins.insert(QChar(0x002e), QPair<double, double>(0.0, 0.75)); // Full Stop .
+	Margins.insert(QChar(0x002d), QPair<double, double>(0.0, 0.75)); // Hyphen-Minus -
+	Margins.insert(QChar(0x003a), QPair<double, double>(0.0, 0.5)); // Colon :
+	Margins.insert(QChar(0x003b), QPair<double, double>(0.0, 0.5)); // Semicolon ;
+	Margins.insert(QChar(0x005b), QPair<double, double>(0.5, 0.5)); // Left Square Bracket [
+	Margins.insert(QChar(0x005d), QPair<double, double>(0.5, 0.5)); // Right Square Bracket ]
+	// Margins.insert(QChar(0x0041), QPair<double, double>(0.1, 0.1)); // A
+	// Margins.insert(QChar(0x0043), QPair<double, double>(0.1, 0.0)); // C
+	// Margins.insert(QChar(0x0044), QPair<double, double>(0.0, 0.1)); // D
+	// Margins.insert(QChar(0x0047), QPair<double, double>(0.1, 0.0)); // G
+	// Margins.insert(QChar(0x004F), QPair<double, double>(0.1, 0.1)); // O
+	// Margins.insert(QChar(0x0054), QPair<double, double>(0.1, 0.1)); // T
+	// Margins.insert(QChar(0x0056), QPair<double, double>(0.1, 0.1)); // V
+	// Margins.insert(QChar(0x0057), QPair<double, double>(0.1, 0.1)); // W
+	// Margins.insert(QChar(0x0059), QPair<double, double>(0.1, 0.1)); // Y
+	Margins.insert(QChar(0x007e), QPair<double, double>(0.75, 0.75)); // Tilde ~
+	Margins.insert(QChar(0x00B4), QPair<double, double>(0.75, 0.75)); // Acute Accent
+	Margins.insert(QChar(0x0060), QPair<double, double>(0.75, 0.75)); // Gravis
+	Margins.insert(QChar(0x00ab), QPair<double, double>(0.5, 0.5)); // Left-Pointing Double Angle Quotation Mark <<
+	Margins.insert(QChar(0x00bb), QPair<double, double>(0.5, 0.5)); // Right-Pointing Double Angle Quotation Mark >>
+	Margins.insert(QChar(0x2010), QPair<double, double>(0.0, 0.75)); // Hyphen
+	Margins.insert(QChar(0x2013), QPair<double, double>(0.0, 0.5)); // En Dash
+	Margins.insert(QChar(0x2014), QPair<double, double>(0.25, 0.25)); // Em Dash
+	Margins.insert(QChar(0x2018), QPair<double, double>(0.75, 0.75)); // Left Single Quotation Mark
+	Margins.insert(QChar(0x2019), QPair<double, double>(0.75, 0.75)); // Right Single Quotation Mark
+	Margins.insert(QChar(0x201a), QPair<double, double>(0.75, 0.75)); // Single Low-9 Quotation Mark
+	Margins.insert(QChar(0x201b), QPair<double, double>(0.75, 0.75)); // Single High-Reversed-9 Quotation Mark
+	Margins.insert(QChar(0x201c), QPair<double, double>(0.5, 0.5)); // Left Double Quotation Mark
+	Margins.insert(QChar(0x201d), QPair<double, double>(0.5, 0.5)); // Right Double Quotation Mark
+	Margins.insert(QChar(0x201e), QPair<double, double>(0.5, 0.5)); // Double Low-9 Quotation Mark
+	Margins.insert(QChar(0x201f), QPair<double, double>(0.5, 0.5)); // Double High-Reversed-9 Quotation Mark
+	Margins.insert(QChar(0x2032), QPair<double, double>(0.0, 0.75)); // Prime
+	Margins.insert(QChar(0x2033), QPair<double, double>(0.0, 0.5)); // Double Prime
+	Margins.insert(QChar(0x2035), QPair<double, double>(0.75, 0.0)); // Reversed Prime
+	Margins.insert(QChar(0x2036), QPair<double, double>(0.5, 0.0)); // Reversed Double Prime
+	Margins.insert(QChar(0x2039), QPair<double, double>(0.75, 0.75)); // Single Left-Pointing Angle Quotation Mark <
+	Margins.insert(QChar(0x203a), QPair<double, double>(0.75, 0.75)); // Single Right-Pointing Angle Quotation Mark >
+	Margins.insert(QChar(0x3002), QPair<double, double>(0.0, 0.5)); // Ideographic Full Stop
+	Margins.insert(QChar(0xff0c), QPair<double, double>(0.5, 0.5)); // Fullwidth Comma
+	Margins.insert(QChar(0xff1a), QPair<double, double>(0.5, 0.5)); // Fullwidth Colon
+	Margins.insert(QChar(0xff1b), QPair<double, double>(0.5, 0.5)); // Fullwidth Semicolon
+
+}
+
+OpticalMarginLookup& OpticalMarginLookup::instance()
+{
+	static OpticalMarginLookup m_instance;
+	return m_instance;
+}
+
+QPair<double, double> OpticalMarginLookup::offset(const QChar &c)
+{
+	QPair<double, double> res(0.0, 0.0);
+
+	if (OpticalMarginLookup::instance().Margins.contains(c))
+		res = OpticalMarginLookup::instance().Margins.value(c);
+
+	return res;
+}
+
Index: scribus/util_text.h
===================================================================
--- scribus/util_text.h	(Revision 26361)
+++ scribus/util_text.h	(Arbeitskopie)
@@ -30,4 +30,23 @@
 //returns string with saxed story
 QString SCRIBUS_API saxedText(StoryText* story);
 
+class OpticalMarginLookup : QObject
+{
+
+public:
+	OpticalMarginLookup(OpticalMarginLookup const&) = delete;
+	void operator=(OpticalMarginLookup const&) = delete;
+	static OpticalMarginLookup& instance();
+	static QPair<double, double> offset(const QChar &c);
+
+	QMap<QChar, QPair<double, double>> Margins;
+
+private:
+	OpticalMarginLookup(QObject *parent = nullptr);
+	~OpticalMarginLookup() = default;
+
+	static OpticalMarginLookup* m_instance;
+};
+
+
 #endif
