View Issue Details

IDProjectCategoryView StatusLast Update
0010539ScribusTypographypublic2024-12-11 22:02
Reportermeho_r Assigned Tonitramr  
PrioritynormalSeveritymajorReproducibilityalways
Status closedResolutionfixed 
PlatformLinuxOSDebianOS VersionWheezy
Product Version1.4.0 
Target Version1.7.0Fixed in Version1.7.0.svn 
Summary0010539: Optical margins, protrusion into the left margin doesn't work
DescriptionActually, there are two issues:

1. When text or text frame is selected and "Both Sides" or "Left Only" optical margin is selected, nothing happens for quotation marks, hyphens, en-dashes and em-dashes on the left side of the text (no matter of text alignment).

2. There is inconsistency regarding character protrusion in general. E.g.:
a) Quotation marks: protruded on the right side, not on the left;
b) Hyphens: protruded on the right side, not on the left;
c) En-dashes: protruded on the right side, not on the left;
d) Em-dashes: do not get protruded neither on the right, nor the left side.

I mentioned only these characters which are most obvious when protrusion takes place, but there are other more subtle cases, like capital letter "A" at the beginning of a non-indented paragraph, exclamation mark at the end of a line, etc., which do not work either.
Steps To Reproduce1. Create a text frame.
2. Place (a) a double left quotation mark at the beginning of the first paragraph; (b) a hyphen at the beginning of the second paragraph; (c) an en-dash at the beginning of the third paragraph, and (d) an em-dash at the beginning of the forth paragraph.
3. Set text alignment to "Justified" or "Forced Justified".
4. Set optical margin to "Both Sides" or "Left Only".
Additional InformationWith Scribus 1.4.0.rc5 on Windows, this issue occurs only for hyphens, en-dashes and em-dashes, which do not get protruded into the left margin, but quotation marks do.
TagsNo tags attached.
PatchYes

Relationships

related to 0016113 closednitramr "Optical margins" is invalid for Chinese punctuation. 
has duplicate 0014832 closedjghali Optical Margins (hanging punctuation) misses quotation marks on the left 
related to 0012265 closednitramr Optical margins setting too verbose and unintuitive 

Activities

bubu

2012-10-07 19:14

reporter   ~0029020

Last edited: 2012-10-07 19:48

I can confirm this bug:
- Ubuntu Scribus 1.4.1 64bit
- Ubuntu Scribus 1.5.0svn 64bit
- Windows Scribus 1.4.2svn latest snapshot on SF (12-10-07)

Pancho

2014-04-23 13:56

reporter   ~0031767

Confirm for 1.4.3.
IMHO more than a minor bug.

nasfarley88

2014-08-20 10:40

reporter   ~0033298

Can confirm for 1.4.4

Kunda

2014-08-20 12:47

updater   ~0033301

upgrading to 'major' severity.

jeff

2016-06-29 01:14

reporter   ~0041743

I would so love to see this work, not only for punctuation but also some letters such as A, T, V, W, Y, C, O. The bottom of https://en.wikipedia.org/wiki/Optical_margin_alignment has a nice table mentioning approximate percentages of justification offsets depending on the type of character.

I'm particularly interested in this to apply to headers; currently there is no way to do have "properly" aligned headers (bigger text) vs body text, other than having separate text frames for everything, which makes the flow mostly impossible to manage.

Scriber

2017-06-28 08:41

reporter   ~0044103

Can confirm for 1.5.3

MZaske

2018-12-01 10:52

reporter   ~0045670

I confirm this for

Scribus Version 1.5.5.svn
07 November 2018
Build ID: C--T-*-C1.16.0-Windows-64bit
Using Ghostscript version 9.22

aka scribus-1.5.5.svn-snapshot-181125-x64.exe

MZaske

2018-12-01 11:14

reporter   ~0045671

I would also like to beg, for this problem to be given some priority. My team is producing an entire batch of childrens' books. Each page has a dominant illustration and typically only 2 - 5 lines of text in huge sizes of 20pt+. So the left margins look very frazzled whereever there is a possessive:


Related feature request, if one of the developers is looking into this:
This is a hint about certain minority languages: There is more possible "punctuation" or "small stuff" which can occur at the beginning of a line (in LTR) than you might imagine. In our case, the possessive on nouns is represented by a word-initial typographical apostrophe (U+2019) >> ’ <<. So a line starting with a "left-weak" glyph like "j" preceeded by an apostrophe looks ragged: ’ jɛŋjɛlɛ a da ada ma ni. (...[previous line] our local danses where happening.)

So what would really help please, would be

- either the option for users to define their custom set of glyphs/characters which get considered for margin-correction

- or to please add the typographical apostrophe U+2019 and the keyboard apostrophe U+0027 to the characters which get treatment on the left-side too

- or some hint in the documentation where in source this "set of gplyphs" is defined or listed, so that brave users can try to hack it.

Thank you.

ale

2018-12-03 15:57

manager   ~0045676

martin, i don't think that there is any promise that the team can do... but if you try to define as well as possible how the feature should work and provide a detailed specification, it's likely that the chances that somebody (or even a member of the team) will program it.

there are a few people in the scribus community who work a lot with the tool and has good ideas about how scribus should work... and it would be nice to see you team up and produce proposal for changes and new features.
ideally, somebody in the group knows how to write specifications.

here are two links about writing software specifications:

- https://codeburst.io/on-writing-tech-specs-6404c9791159
- https://eng.lyft.com/awesome-tech-specs-86eea8e45bb9

imo, we could start a discussion in the mailing list or in the forums to write an how-to for writing specifications for scribus...
the goals would be to avoid both huge proposals that don't lead to any change nor wishes that are too much shaped on the user's workflow to be directly programmed.

Scriber

2019-01-17 16:11

reporter   ~0045803

Are there any news on this issue?

Since the function works properly on the right side of a column, I would appreciate if the developers could make it work on the left side of a column in the same way.

Nathan

2024-01-04 00:50

reporter   ~0050792

Checking in on this as well; I am disappointed to see that the issue is still present in 1.6 (great release an all other counts however!).
HangingPunctuation.png (13,833 bytes)   
HangingPunctuation.png (13,833 bytes)   

nitramr

2024-10-24 19:19

developer   ~0051483

I finished the implementation of left optical margins. There was already some unused and a little outdated code for left margins.

As an improvement, I implemented a look-up table for character offsets. In the future, someone can make this table editable in the UI, so that users can set their own values.
opticalmargin_2024-10-24_01.patch (15,413 bytes)   
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

nitramr

2024-10-26 21:45

developer   ~0051486

>> someone can make this table editable in the UI, so that users can set their own values.

Maybe it's me. :D

I created a UI editor to edit the optical margin values. The lookup table is stored locally in the SLA file. Changes in the editor affect the text in real time.
Currently, you cannot predefine the values ​​via the document properties dialog.

The implementation still needs some optimization. But it is enough for initial feedback.

To avoid conflicts with the xml structure, all characters are saved as encoded hex numbers.
Example: [0,0.75](2c2d2ee28090e280b2)

What do you think?
files.zip (5,788 bytes)
opticalmargin_2024-10-26_01.patch (18,979 bytes)   
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)
@@ -2499,6 +2499,7 @@
 		//->Prefs m_Doc->papColor = QColor(attrs.valueAsString("PAGEC"));
 
 	m_Doc->setMarginColored(attrs.valueAsBool("RANDF", false));
+	m_Doc->setOpticalMarginGroups(attrs.valueAsString("opticalMarginGroups"));
 
 	readCMSSettings(doc, attrs);
 	readDocumentInfo(doc, attrs);
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)
@@ -519,6 +519,7 @@
 	docu.writeAttribute("calligraphicPenAngle", m_Doc->itemToolPrefs().calligraphicPenAngle);
 	docu.writeAttribute("calligraphicPenWidth", m_Doc->itemToolPrefs().calligraphicPenWidth);
 	docu.writeAttribute("calligraphicPenStyle", m_Doc->itemToolPrefs().calligraphicPenStyle);
+	docu.writeAttribute("opticalMarginGroups", m_Doc->opticalMarginGroups());
 
 	writeCheckerProfiles(docu);
 	writeJavascripts(docu);
Index: scribus/scribusdoc.cpp
===================================================================
--- scribus/scribusdoc.cpp	(Revision 26366)
+++ scribus/scribusdoc.cpp	(Arbeitskopie)
@@ -200,6 +200,7 @@
 ScribusDoc::ScribusDoc() : UndoObject( tr("Document")), Observable<ScribusDoc>(nullptr),
 	m_appPrefsData(PrefsManager::instance().appPrefs),
 	m_docPrefsData(PrefsManager::instance().appPrefs),
+	m_opticalMarginLookup(OpticalMarginLookup::instance()),
 	m_undoManager(UndoManager::instance()),
 	m_automaticTextFrames(false),
 	m_guardedObject(this),
@@ -236,6 +237,7 @@
 ScribusDoc::ScribusDoc(const QString& docName, int unitindex, const PageSize& pagesize, const MarginStruct& margins, const DocPagesSetup& pagesSetup) : UndoObject( tr("Document")),
 	m_appPrefsData(PrefsManager::instance().appPrefs),
 	m_docPrefsData(PrefsManager::instance().appPrefs),
+	m_opticalMarginLookup(OpticalMarginLookup::instance()),
 	m_undoManager(UndoManager::instance()),
 	m_docUnitRatio(unitGetRatioFromIndex(m_appPrefsData.docSetupPrefs.docUnitIndex)),
 	m_automaticTextFrames(pagesSetup.autoTextFrames),
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"
@@ -242,6 +243,8 @@
 	const ApplicationPrefs& prefsData() const { return m_docPrefsData; }
 	void setNewPrefs(const ApplicationPrefs& prefsData, const ApplicationPrefs& oldPrefsData, bool resizePages, bool resizeMasterPages, bool resizePageMargins, bool resizeMasterPageMargins);
 
+	void setOpticalMarginGroups(const QString &groups) { m_opticalMarginLookup.fromString(groups);}
+	const QString opticalMarginGroups() { return m_opticalMarginLookup.toString();}
 	// Add, delete and move pages
 	
 	ScPage* addPage(int pageNumber, const QString& masterPageName = QString(), bool addAutoFrame = false);
@@ -1301,6 +1304,7 @@
 	QFileDevice::Permissions m_docFilePermissions {QFileDevice::ReadOwner|QFileDevice::WriteOwner};
 	ApplicationPrefs& m_appPrefsData;
 	ApplicationPrefs m_docPrefsData;
+	OpticalMarginLookup& m_opticalMarginLookup;
 	UndoManager * const m_undoManager;
 	bool m_loading {false};
 	bool m_modified {false};
Index: scribus/ui/opticalmarginswidget.cpp
===================================================================
--- scribus/ui/opticalmarginswidget.cpp	(Revision 26366)
+++ scribus/ui/opticalmarginswidget.cpp	(Arbeitskopie)
@@ -8,9 +8,11 @@
 #include <QHBoxLayout>
 
 #include "opticalmarginswidget.h"
+#include "opticalmarginlookup.h"
 #include "iconmanager.h"
 #include "scribus.h"
 #include "scribusapp.h"
+#include "ui/opticalmargineditor.h"
 
 OpticalMarginsWidget::OpticalMarginsWidget(QWidget* parent) : FormWidget(parent)
 {
@@ -21,7 +23,10 @@
 	rightButton->setCheckable(true);
 	parentButton = new QToolButton();
 	parentButton->hide(); // only used for style manager
+	settingsButton = new QToolButton();
 
+	editor = new OpticalMarginEditor();
+
 	QHBoxLayout* mainLayout = new QHBoxLayout();
 	mainLayout->setSpacing(4);
 	mainLayout->setContentsMargins(0, 0, 0, 0);
@@ -29,6 +34,7 @@
 	mainLayout->addWidget(leftButton);
 	mainLayout->addWidget(rightButton);
 	mainLayout->addSpacerItem(new QSpacerItem(4, 0, QSizePolicy::Fixed));
+	mainLayout->addWidget(settingsButton);
 	mainLayout->addWidget(parentButton);
 	mainLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
 
@@ -40,6 +46,8 @@
 	connect(ScQApp, SIGNAL(iconSetChanged()), this, SLOT(iconSetChange()));
 	connect(rightButton, SIGNAL(clicked()), this, SIGNAL(opticalMarginChanged()));
 	connect(leftButton, SIGNAL(clicked()), this, SIGNAL(opticalMarginChanged()));
+	connect(settingsButton, SIGNAL(clicked()), this, SLOT(showOpticalMarginEditor()));
+	connect(editor, SIGNAL(marginGroupChanged()), this, SIGNAL(opticalMarginChanged()));
 }
 
 int OpticalMarginsWidget::opticalMargin()
@@ -91,6 +99,7 @@
 	IconManager &im = IconManager::instance();
 	leftButton->setIcon(im.loadIcon("paragraph-optical-margin-left"));
 	rightButton->setIcon(im.loadIcon("paragraph-optical-margin-right"));
+	settingsButton->setIcon(im.loadIcon("settings"));
 }
 
 void OpticalMarginsWidget::changeEvent(QEvent *e)
@@ -109,4 +118,11 @@
 
 	leftButton->setToolTip( tr("Optical character offset on left side"));
 	rightButton->setToolTip( tr("Optical character offset on right side"));
+	settingsButton->setToolTip( tr("Edit offset of optical margins"));
 }
+
+void OpticalMarginsWidget::showOpticalMarginEditor()
+{
+	editor->setOpticalMarginGroup(OpticalMarginLookup::instance().opticalMarginGroups());
+	editor->exec();
+}
Index: scribus/ui/opticalmarginswidget.h
===================================================================
--- scribus/ui/opticalmarginswidget.h	(Revision 26366)
+++ scribus/ui/opticalmarginswidget.h	(Arbeitskopie)
@@ -13,6 +13,7 @@
 class ParagraphStyle;
 class ScribusDoc;
 class ScribusMainWindow;
+class OpticalMarginEditor;
 
 class OpticalMarginsWidget : public FormWidget
 {
@@ -25,6 +26,7 @@
 	QToolButton *leftButton = {nullptr};
 	QToolButton *rightButton = {nullptr};
 	QToolButton *parentButton = {nullptr};
+	QToolButton *settingsButton = {nullptr};
 
 	int opticalMargin();
 	void setOpticalMargin(int omt);
@@ -33,10 +35,13 @@
 
 	void changeEvent(QEvent *e) override;
 
+	OpticalMarginEditor * editor;
+
 public slots:
 
 	void iconSetChange();
 	void languageChange();
+	void showOpticalMarginEditor();
 
 signals:
 	void opticalMarginChanged();
optical_margin_lookup.png (46,819 bytes)   
optical_margin_lookup.png (46,819 bytes)   

nitramr

2024-10-27 18:05

developer   ~0051492

I moved the editor to the preferences dialog -> typography -> optical margins.
Now, users can specify values globally or locally for each document.

In addition, I changed the way how the data is stored in sla file and prefs.

New structure is more readable:

<OpticalMarginGroups>
            <OpticalMarginGroup Left="0" Right="0.5" Characters="U+003A,U+003B,U+2013,U+2033,U+3002"/>
...
files-2.zip (7,597 bytes)
opticalmargins_2024-10-27_01.patch (22,327 bytes)   
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
opticalmargins.png (83,094 bytes)   
opticalmargins.png (83,094 bytes)   

ale

2024-10-27 19:39

manager   ~0051496

I'm not an optical margins expert at all... but...

Looking at examples I've found with a quick search in the internet, there is more than the ( and - to "hang out" to the left.
Letters like A, W, and T seem to be popular candidates.
Does anybody know if there is a list of letters and their "standard" values?
Do they depend on the font and the language?

From what I've read, Adobe seems to have the option to set a custom offset in pt. While your choice to use % seems better to me (I could not test how well it works), it might be a good idea to keep the door open for also using pts as the unit.

On top of this, while you were continuing your work on the code, I was writing about the pro and contra of storing the offsets in the document or in the preferences.
You now have added them also to the preferences, but I think that my thoughts could still be useful.

So here they are:
 
I'm not 100% sure about storing the values in the .sla.
Once the setting has made it into .sla, it has to be supported forever.
(With my little experience in this domain,) I'm not sure that this is something somebody wants to define for a specific Scribus document.

Probably, the best way to store them, is to have a set of profiles in the preferences.

The advantage of having the offsets in the .sla is that one can share the document and the recipient will get all the necessary information.
On the other side, this will force the users to import the offsets (we will have export and import function? Right?) into each new document.

Both ways have pros and contra.
Hard to tell if there is a right way to do it.

(Btw, I tried (not too hard) to check if the values can already be found in the font file, but did not find any sign of that being a thing.)

<hr>

All this having being said:
Of course, what you did is already wonderful! And I'm fine with a rather minimal implementation.
I'm just trying to check if it's going in a way that can allows to be extended, with as little work to keep the backwards compatibility.

nitramr

2024-10-27 20:02

developer   ~0051497

I totally agree with your pros and cons.

One reason I saved the data in the SLA file is so that the document looks the same everywhere, similar to other definitions like colors.

>> Letters like A, W, and T seem to be popular candidates.
I add them too, but the code is commented out for now.

>> Does anybody know if there is a list of letters and their "standard" values?
Very good question. I could not find a useful list. There are just few examples on Wikipedia.

>> Do they depend on the font and the language?
I guess there are differences between serif and sans serif fonts for letters like "A".
Maybe the offset rules should be on paragraph style level?

nitramr

2024-10-27 20:26

developer   ~0051498

Wow, Affinity Publisher has it on character level.
https://affinity.help/publisher2/en-US.lproj/index.html?page=pages/Text/opticalAlignment.html&title=Optical%20alignment

utnik

2024-10-27 21:31

updater   ~0051499

optical margins are a complex business. it's a bit easier than with kerning pairs, as every character can be handled separately. but it definitely depends on the font. for perfection it should be defined in a 'margin table' in the font file. but unfortunately there is no such table (afaik).
i don't know how quark and adobe deal with it. but for perfection every character needs to be checked. (even round glyphs like an 'o' should hang a little bit as they do vertically acros the baseline…)
i think it is worth some good thinking before implementing some 'final version'…

nitramr

2024-11-06 10:55

developer   ~0051530

There is an update.

Optical margin rules can now be defined at paragraph level. This way it is possible to define different rules for different fonts.

I also added two different presets (Latin and Asian). They simply have different preset character rules. Also, the unit for each rule can be customized by the user (relative or fixed). There is one limitation: you cannot define two different units for the same character.

As a result of this last change, I removed the optical margin rules from the preferences file and the document preferences. A new paragraph uses the hard-coded default set.
And it is possible to override and change the optical margin rules via text properties panel too.

I will provide a patch after I'm done with some more cleanups and optimizations.

utnik

2024-11-06 11:13

updater   ~0051531

looks promising!

cbradney

2024-11-06 20:27

administrator   ~0051533

I'm not sure we'd want to not have this in prefs and doc settings. The values in properties palette needs to inherit from somewhere.

JLuc

2024-11-06 21:24

developer   ~0051534

Last edited: 2024-11-06 21:24

That looks complex a job.

When right optical margins are OK, why not do the same for left margins ?

I agree with « so that the document looks the same everywhere ». This is one of the main reason i think of when i recommend using Scribus instead of say LibreOffice.

ale

2024-11-07 14:20

manager   ~0051536

i'm not sure, that it's a good idea to make it a paragraph formatting.

one needs to to enable / disable the optical margins on a paragraph basis.
but i don't see why the configuration would not be valid for all uses of the same font (and size if the % does not work well enough) inside of the document.
(to be honest, i'm not even sure that it should be enabled / disabled for single paragraphs... well, in the extreme case of some code snippet where a line would start with a mono space "-", i would not want that to be in the margin... but that's a real use case? i mean: how likely that the author would enable the optical margins elsewhere?)

i would say that the list of offsets per character belongs in the preferences (and not even in the document settings) and one should be able to:

- import and export the (each) sets of offsets.
- have multiple sets that can be enabled or disabled.
- the order of the sets might matter and allow some sort of inheritance.
- the set can be valid only for specific fonts and/or text sizes and/or languages

my preferred way to share the settings with a .sla file would be to extend the "Collect for output" tool.
this would mean that the settings are or can be in separate files (by default in ~/.config/scribus) and scribus would automatically read them, if they are next to the .sla or in a specific sub directory (./scribus/optical-margins.xml).

also, i'm fine with any basic implementation that makes it work ok. i prefer to have it for the next stable and, for now, i would put most of the thoughts on:

- how to put it in the sla / preferences and avoid the need for further changes in there (adding new features is of course always ok! modifying them can be a pain...)
- finding out how the offset can be best defined to fit different fonts, sizes, and cultural / language habits.

without the need to implement the whole thing in one go.
just build sold foundations.

nitramr

2024-11-12 18:59

developer   ~0051567

Hello everyone,

I have researched the topic a little more deeply and looked at other programs.


# What rules and definitions are there?

Optical margins are mainly font-related. The special features of languages ​​are then defined primarily by the characters within a font.
On the Internet, you can find various definitions of how far characters are moved into the margin. In general, it can be said that it is a matter of taste and there are no fixed rule.
However, there are 2 areas of application in which the offset can be different:
1. Hanging punctuation (mostly used for quotes, lists or headings)
2. Optical margin alignment (mostly used for block text)

So we can note 2 things:
1. The optical margin offset must be adjustable for each character
2. The optical margin offset is different for one and the same character depending on the application and font

# How do others do it?

> InDesign
In InDesign, you can only set a fixed value in pt for all characters. This value is applied to the entire frame and all connected frames. However, you can set in the paragraph formats whether this margin alignment should be ignored. If you want to ignore the optical margin for just one character, you have to put a tab stop in front of the character. But this is a hack.

> Affinity Publisher
In Affinity Publisher, optical margin alignment is defined individually for each character (only one global definition for all characters). There are 2 ways to apply these rules:
1. Font: Use the rules within a font (currently, I don't know what they are using)
2. Manual: The rules can then be applied to individual characters, paragraphs or frames.


# How it could work in Scribus

We could adapt Affinity publisher's approach for Scribus.
Currently, you can switch the optical margin on and off in paragraph style. Instead of having just one global rule like in Affinity Publisher, we could use different presets, e.g. optimized for serif fonts or sans serif fonts. In the SLA file, we only need to save the preset ID in paragraph style. If necessary, we copy the presets used into the SLA file.
I imagine it to be similar to the colors. You can only edit the presets globally, and only references are used in the styles.

Currently in 1.6.2, the rules are defined directly in the code. In the first step, we could outsource these rules to an external file and define just one global rule, which we will deliver with 1.7.0. Later, we can make these rules editable and allow more than one.

# Sources:
https://en.wikipedia.org/wiki/Optical_margin_alignment
https://en.wikipedia.org/wiki/Hanging_punctuation
https://helpx.adobe.com/indesign/using/formatting-paragraphs.html
https://affinity.help/publisher2/en-US.lproj/index.html?page=pages/Text/opticalAlignment.html&title=Optical%20alignment
https://www.youtube.com/watch?v=-QWIsBLLCKg

cbradney

2024-11-12 19:51

administrator   ~0051568

The best answer relates to providing better (which can be more complicated) results. How we then remove the complication that may be present, or make it easy to interact with then becomes the challenge.
Our general approach, where applicable, has been overall settings exist in application settings, these are copied to document settings when a new doc is created. These are inherited by new style or non-styled text or frames.
We need to assume that the document needs to take all settings with it (apart from some view related settings) in case it moves between computers, and in between Scribus versions.
Don't some of these margin settings come from more complete font files too? If so, those should be imported from the font when set on the style or paragraph.
I suggest for 1.7.0 (if you're really quick, hint hint), they go in prefs, in doc setup, and that replaces the fixed in code values now. Either stop there and pause until 1.7.1.svn is started (potentially hiding the developed UI), or they could be inherited to further steps downstream.

utnik

2024-11-12 22:42

updater   ~0051570

"…Don't some of these margin settings come from more complete font files too?"

i would like to say yes. it would be the right place. but unfortunately i couldn't find any 'margin settings' in a font file so far.

nitramr

2024-11-18 22:29

developer   ~0051593

Hi, a new attempt.

# The new patch includes the following implementation:
1. There is an optical margin standard rule set that is defined in the code
2. This rule set can be edited in the Prefs dialog and Doc Properties dialog and is saved in the Prefs file and SLA file.
3. Individual rules can be deleted and new ones added
4. A changed rule set can be reset to the default value in the code.
5. The rules are saved as a definition in the SLA file and only referenced in the paragraph styles by ID. (Currently there is only one set. The implementation allows infinite)

# What is implemented but disabled in the UI?
1. You can add additional rule sets
2. When deleting rule sets you can select an alternative rule set that is then replaced in all paragraph styles and local changes.
3. In the text properties panel you can apply a different rule set to a paragraph than the one defined in the paragraph style

# What doesn't work? When importing paragraph styles, only the paragraph style is imported and not the optical margin rule sets. Since the paragraph style only has a reference ID, the values ​​from there are not adopted. The values ​​of the target document are then used.

This is also a reason why I deactivated the advanced functions. The import function is quite complex. Especially if the set with the same ID has different definitions.

The import problem could be solved if all values ​​were saved directly in the paragraph style and not just referenced. But that just bloats the SLA file unnecessarily.
files-3.zip (11,360 bytes)
opticalmargin_2024-11-18_01.patch (52,815 bytes)   
Index: resources/iconsets/1_7_0/1_7_0.xml
===================================================================
--- resources/iconsets/1_7_0/1_7_0.xml	(Revision 26436)
+++ 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" />
@@ -146,15 +148,14 @@
 		<icon id="expand" file="16/action-chevron-down.svg" />
 		<icon id="inherit-opacity-off" file="16/action-inherit-opacity-off.svg" />
 		<icon id="inherit-opacity-on" file="16/action-inherit-opacity-on.svg" />
+		<icon id="remove" file="16/action-remove.svg" />
 		<icon id="reset" file="16/action-reload.svg" />
 		<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 26436)
+++ 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 26436)
+++ 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"
@@ -647,11 +648,22 @@
 		lineData.ascent = lineData.descent = 0;
 		if (glyphs.isEmpty())
 			return;
-		const CharStyle& cStyle(glyphs.at(0).style());
-		double scaleV = cStyle.scaleV() / 1000.0;
-		double offset = (cStyle.fontSize() / 10) * (cStyle.baselineOffset() / 1000.0);
-		lineData.ascent = cStyle.font().ascent(cStyle.fontSize() / 10.00) * scaleV + offset;
-		lineData.descent = cStyle.font().descent(cStyle.fontSize() / 10.00) * scaleV - offset;
+
+		// #17286 seek for largest glphy in line
+		for (int i = 0; i < glyphs.count(); i++)
+		{
+			const CharStyle& cStyle(glyphs.at(i).style());
+			double scaleV = cStyle.scaleV() / 1000.0;
+			double offset = (cStyle.fontSize() / 10) * (cStyle.baselineOffset() / 1000.0);
+			double tmpAscent = cStyle.font().ascent(cStyle.fontSize() / 10.00) * scaleV + offset;
+
+			if (lineData.ascent < tmpAscent)
+			{
+				lineData.ascent = tmpAscent;
+				lineData.descent = cStyle.font().descent(cStyle.fontSize() / 10.00) * scaleV;
+			}
+
+		}
 	}
 
 // yPos should not be changed when all line is already calculated - at new y position there can be overflow!!!
@@ -901,44 +913,34 @@
 
 
 	//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 ParagraphStyle& pStyle = itemText.paragraphStyle(lineData.lastCluster);
+			const ScFace& font = style.font();
+			double chs = style.fontSize() * (style.scaleH() / 1000.0);
+			QChar chr = itemText.text(glyphs[b].firstChar());
+			double leftCorr = 0;
+
+			OpticalMarginSet set = doc->opticalMarginSets().value(pStyle.opticalMarginSetId());
+			OpticalMarginRule rule = OpticalMarginLookup::instance().offset(chr, set);
+			if ( rule.Unit == SC_PERCENT)
+				leftCorr = font.glyphBBox(font.char2CMap(chr.unicode()), chs / 10.0).width * rule.Left;
+			else
+				leftCorr = rule.Left;
+
+			return leftCorr;
+		}
+		return 0.0;
+	}
+
 	/// calculate how much the last char should stick out to the right
 	double opticalRightMargin(const StoryText& itemText)
 	{
@@ -951,46 +953,20 @@
 		if (b >= 0)
 		{
 			const CharStyle& style = glyphs[b].style();
+			const ParagraphStyle& pStyle = itemText.paragraphStyle(lineData.lastCluster);
 			const ScFace& font = style.font();
 			double chs = style.fontSize() * (style.scaleH() / 1000.0);
-			QChar chr = itemText.text(glyphs[b].lastChar());
-			double rightCorr;
-			if (glyphs[b].hasFlag(ScLayout_SoftHyphenVisible))
-				rightCorr = font.hyphenWidth(style, chs / 10.0);
+			bool softHypen = glyphs[b].hasFlag(ScLayout_SoftHyphenVisible);
+			QChar chr = softHypen ? QChar(style.hyphenChar()) : itemText.text(glyphs[b].lastChar());
+			double rightCorr = 0;
+
+			OpticalMarginSet set = doc->opticalMarginSets().value(pStyle.opticalMarginSetId());
+			OpticalMarginRule rule = OpticalMarginLookup::instance().offset(chr, set);
+			if ( rule.Unit == SC_PERCENT)
+				rightCorr = softHypen ? font.hyphenWidth(style, chs / 10.0) * rule.Right : font.glyphBBox(font.char2CMap(chr.unicode()), chs / 10.0).width * rule.Right;
 			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 = rule.Right;
+
 			return rightCorr;
 		}
 		return 0.0;
@@ -2438,6 +2414,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 +2448,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 +2537,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 +2568,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 +2833,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 +2866,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 26436)
+++ scribus/plugins/fileloader/scribus170format/scribus170format.cpp	(Arbeitskopie)
@@ -1974,6 +1974,11 @@
 			if (!success) break;
 			m_Doc->setUsesMarksAndNotes(true);
 		}
+		else if (tagName == QLatin1String("OpticalMarginSets"))
+		{
+			success = readOpticalMarginSets(m_Doc, reader);
+			if (!success) break;
+		}
 		else
 		{
 			reader.skipCurrentElement();
@@ -3166,6 +3171,10 @@
 	if (attrs.hasAttribute(OpticalMargins))
 		newStyle.setOpticalMargins(attrs.valueAsInt(OpticalMargins));
 
+	static const QString OpticalMarginSetId("OpticalMarginSetId");
+	if (attrs.hasAttribute(OpticalMarginSetId))
+		newStyle.setOpticalMarginSetId(attrs.valueAsString(OpticalMarginSetId));
+
 	static const QString HyphenConsecutiveLines("HyphenConsecutiveLines");
 	if (attrs.hasAttribute(HyphenConsecutiveLines))
 		newStyle.setHyphenConsecutiveLines(attrs.valueAsInt(HyphenConsecutiveLines));
@@ -3974,6 +3983,52 @@
 	return !reader.hasError();
 }
 
+bool Scribus170Format::readOpticalMarginSets(ScribusDoc *doc, ScXmlStreamReader &reader) const
+{
+	QString tagName(reader.nameAsString());
+	OpticalMarginSets sets;
+	OpticalMarginSet set;
+	QMap<QString, QString> names;
+	OpticalMarginRules rules;
+
+	while (!reader.atEnd() && !reader.hasError())
+	{
+		reader.readNext();
+		if (reader.isEndElement() && reader.name() == tagName)
+			break;
+
+		if (reader.isStartElement() && reader.name() == QLatin1String("Set"))
+		{
+			ScXmlStreamAttributes attrs = reader.scAttributes();
+			set.id = attrs.valueAsString("Id");
+			set.type = attrs.valueAsString("Type");
+			set.name = attrs.valueAsString("Name");
+		}
+
+		if (reader.isStartElement() && reader.name() == QLatin1String("Rule"))
+		{
+			ScXmlStreamAttributes attrs = reader.scAttributes();
+			OpticalMarginRule rule(unicodeToString(attrs.valueAsString("Characters")), attrs.valueAsDouble("Left"), attrs.valueAsDouble("Right"), attrs.valueAsInt("Unit"));
+			rules.append(rule);
+		}
+
+		if (reader.isEndElement() && reader.name() == QLatin1String("Rules"))
+		{
+			set.rules = OpticalMarginLookup::instance().splitRulesToChars(rules);
+			rules.clear();
+		}
+
+		if (reader.isEndElement() && reader.name() == QLatin1String("Set"))
+			sets.insert(set.id, set);
+
+	}
+
+	doc->typographicPrefs().opticalMarginSets = sets;
+	m_Doc->setOpticalMarginSets(sets);
+
+	return !reader.hasError();
+}
+
 bool Scribus170Format::readSections(ScribusDoc* doc, ScXmlStreamReader& reader) const
 {
 	QString tagName(reader.nameAsString());
@@ -5553,6 +5608,8 @@
 		pstyle.setMaxGlyphExtension(attrs.valueAsDouble("MaxGlyphExtend"));
 	if (attrs.hasAttribute("OpticalMargins"))
 		pstyle.setOpticalMargins(attrs.valueAsInt("OpticalMargins"));
+	if (attrs.hasAttribute("OpticalMarginSetId"))
+		pstyle.setOpticalMarginSetId(attrs.valueAsString("OpticalMarginSetId"));
 	if (attrs.hasAttribute("HyphenationMode"))
 		pstyle.setHyphenationMode(attrs.valueAsInt("HyphenationMode"));
 	if (attrs.hasAttribute("leftMargin"))
Index: scribus/plugins/fileloader/scribus170format/scribus170format.h
===================================================================
--- scribus/plugins/fileloader/scribus170format/scribus170format.h	(Revision 26436)
+++ 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 readOpticalMarginSets(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 writeOpticalMarginSets(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 26436)
+++ 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)
 {
@@ -540,6 +540,7 @@
 	writeTOC(docu);
 	writeMarks(docu);
 	writeNotesStyles(docu);
+	writeOpticalMarginSets(docu);
 	writeNotesFrames(docu);
 	writeNotes(docu);
 	writePageSets(docu);
@@ -882,6 +883,8 @@
 		docu.writeAttribute("NumerationHigher", static_cast<int>(style.numHigher()));
 	if ( ! style.isInhOpticalMargins())
 		docu.writeAttribute("OpticalMargins", style.opticalMargins());
+	if ( ! style.isInhOpticalMarginSetId())
+		docu.writeAttribute("OpticalMarginSetId", style.opticalMarginSetId());
 	if ( ! style.isInhHyphenConsecutiveLines())
 		docu.writeAttribute("HyphenConsecutiveLines", style.hyphenConsecutiveLines());
 	if ( ! style.isInhHyphenationMode())
@@ -1531,6 +1534,40 @@
 	docu.writeEndElement();
 }
 
+void Scribus170Format::writeOpticalMarginSets(ScXmlStreamWriter & docu)
+{
+	docu.writeStartElement("OpticalMarginSets");
+
+	OpticalMarginSets sets = m_Doc->typographicPrefs().opticalMarginSets;
+
+	for (const auto &set : sets)
+	{
+		docu.writeStartElement("Set");
+		docu.writeAttribute("Id", set.id);
+		docu.writeAttribute("Type", set.type);
+		docu.writeAttribute("Name", set.name);
+
+		docu.writeStartElement("Rules");
+
+		OpticalMarginRules rules = OpticalMarginLookup::instance().mergeCharsToRules(set.rules);
+
+		for (const auto &rule : rules)
+		{
+			docu.writeEmptyElement("Rule");
+			docu.writeAttribute("Left", rule.Left);
+			docu.writeAttribute("Right", rule.Right);
+			docu.writeAttribute("Unit", rule.Unit);
+			docu.writeAttribute("Characters", stringToUnicode(rule.Chars));
+		}
+
+		docu.writeEndElement(); // Rules
+		docu.writeEndElement(); // Set
+
+	}
+
+	docu.writeEndElement(); // OpticalMarginSets
+}
+
 void Scribus170Format::writeNotesStyles(ScXmlStreamWriter & docu)
 {
 	if (m_Doc->m_docNotesStylesList.isEmpty())
Index: scribus/prefsmanager.cpp
===================================================================
--- scribus/prefsmanager.cpp	(Revision 26436)
+++ 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"
@@ -224,6 +225,7 @@
 	appPrefs.typoPrefs.valueStrikeThruPos = -1;
 	appPrefs.typoPrefs.valueStrikeThruWidth = -1;
 	appPrefs.guidesPrefs.valueBaselineGrid = 14.4;
+	appPrefs.typoPrefs.opticalMarginSets = OpticalMarginLookup::instance().defaultSets();
 	appPrefs.guidesPrefs.offsetBaselineGrid = 0.0;
 	appPrefs.displayPrefs.showToolTips = true;
 	appPrefs.displayPrefs.showMouseCoordinates = true;
@@ -1920,6 +1922,33 @@
 	experimentalElem.setAttribute("NotesEnabled", appPrefs.experimentalFeaturePrefs.notesEnabled);
 	elem.appendChild(experimentalElem);
 
+	QDomElement opticalMarginSets = docu.createElement("OpticalMarginSets");
+	for (const auto &set : appPrefs.typoPrefs.opticalMarginSets)
+	{
+		QDomElement setItem = docu.createElement("Set");
+		setItem.setAttribute("Id", set.id);
+		setItem.setAttribute("Type", set.type);
+		setItem.setAttribute("Name", set.name);
+
+		QDomElement rulesItem = docu.createElement("Rules");
+		setItem.appendChild(rulesItem);
+
+		OpticalMarginRules rules = OpticalMarginLookup::instance().mergeCharsToRules(set.rules);
+
+		for (auto rule : rules)
+		{
+			QDomElement ruleItem = docu.createElement("Rule");
+			ruleItem.setAttribute("Left", rule.Left);
+			ruleItem.setAttribute("Right", rule.Right);
+			ruleItem.setAttribute("Unit", rule.Unit);
+			ruleItem.setAttribute("Characters", stringToUnicode(rule.Chars));
+			rulesItem.appendChild(ruleItem);
+		}
+
+		opticalMarginSets.appendChild(setItem);
+	}
+	elem.appendChild(opticalMarginSets);
+
 	// write file
 	bool result = false;
 	QFile f(filePath);
@@ -2768,6 +2797,66 @@
 			appPrefs.experimentalFeaturePrefs.notesEnabled = static_cast<bool>(dc.attribute("NotesEnabled", "0").toInt());
 		}
 
+		// optical margin groups
+		if (dc.tagName() == "OpticalMarginSets")
+		{
+			OpticalMarginSets sets;
+			OpticalMarginSet set;
+
+			QDomNodeList setNodes = dc.elementsByTagName("Set");
+
+			for(int i = 0; i < setNodes.count(); i++)
+			{
+				QDomNode setNode = setNodes.at(i);
+				if(setNode.isElement())
+				{
+					QDomElement setElement = setNode.toElement();
+					set.id = setElement.attribute("Id");
+					set.type = setElement.attribute("Type");
+					set.name = setElement.attribute("Name");
+
+					QDomNodeList childrenList = setElement.childNodes();
+
+					for(int i = 0; i < childrenList.count(); i++)
+					{
+						QDomNode child = childrenList.at(i);
+						if(child.isElement())
+						{
+							QDomElement childElement = child.toElement();
+
+							if (childElement.tagName() == "Rules")
+							{
+								OpticalMarginRules rules;
+
+								QDomNodeList ruleList = childElement.elementsByTagName("Rule");
+								for(int i = 0; i < ruleList.count(); i++)
+								{
+									QDomNode rule = ruleList.at(i);
+									if(rule.isElement())
+									{
+										QDomElement ruleElement = rule.toElement();
+										OpticalMarginRule rule(unicodeToString(ruleElement.attribute("Characters")),
+																 ruleElement.attribute("Left").toDouble(),
+																 ruleElement.attribute("Right").toDouble(),
+																 ruleElement.attribute("Unit").toInt()
+																 );
+
+										rules.append(rule);
+									}
+								}
+
+								set.rules = OpticalMarginLookup::instance().splitRulesToChars(rules);
+							}
+						}
+					}
+
+					sets.insert(set.id, set);
+				}
+			}
+
+			appPrefs.typoPrefs.opticalMarginSets = sets;
+		}
+
 		domNode = domNode.nextSibling();
 	}
 	// Some sanity checks
Index: scribus/prefsstructs.h
===================================================================
--- scribus/prefsstructs.h	(Revision 26436)
+++ 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
+	OpticalMarginSets opticalMarginSets; //! OpticalMargin Group List
 
 	inline bool operator==(const TypoPrefs &other) const
 	{
Index: scribus/scribusdoc.cpp
===================================================================
--- scribus/scribusdoc.cpp	(Revision 26436)
+++ scribus/scribusdoc.cpp	(Arbeitskopie)
@@ -9046,10 +9046,11 @@
 	itemSelection_ApplyCharStyle(newStyle, customSelection, "EFFECTS");
 }
 
-void ScribusDoc::itemSelection_SetOpticalMargins(int i, Selection* customSelection)
+void ScribusDoc::itemSelection_SetOpticalMargins(int i, const QString id, Selection* customSelection)
 {
 	ParagraphStyle newStyle;
-	newStyle.setOpticalMargins(i);
+	newStyle.setOpticalMarginSetId(id);
+	newStyle.setOpticalMargins(i);	
 	itemSelection_ApplyParagraphStyle(newStyle, customSelection);
 }
 
Index: scribus/scribusdoc.h
===================================================================
--- scribus/scribusdoc.h	(Revision 26436)
+++ 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,9 @@
 	int marginPreset() const { return m_docPrefsData.docSetupPrefs.marginPreset; }
 	void setMarginPreset(int mp) { m_docPrefsData.docSetupPrefs.marginPreset = mp; }
 
+	OpticalMarginSets opticalMarginSets() { return m_docPrefsData.typoPrefs.opticalMarginSets; }
+	void setOpticalMarginSets(OpticalMarginSets sets) { m_docPrefsData.typoPrefs.opticalMarginSets = sets; }
+
 	TypoPrefs& typographicPrefs() { return m_docPrefsData.typoPrefs; }
 	GuidesPrefs& guidesPrefs() { return m_docPrefsData.guidesPrefs; }
 	ItemToolPrefs& itemToolPrefs() { return m_docPrefsData.itemToolPrefs; }
@@ -1156,7 +1160,7 @@
 	void itemSelection_SetUnderline(int pos, int wid, Selection* customSelection = nullptr);
 	void itemSelection_SetStrikethru(int pos, int wid, Selection* customSelection = nullptr);
 	void itemSelection_SetEffects(int s, Selection* customSelection = nullptr);
-	void itemSelection_SetOpticalMargins(int i, Selection* customSelection = nullptr);
+	void itemSelection_SetOpticalMargins(int i, const QString id, Selection* customSelection = nullptr);
 	void itemSelection_SetColorProfile(const QString& profileName, Selection* customSelection = nullptr);
 	void itemSelection_SetRenderIntent(int intentIndex, Selection* customSelection = nullptr);
 	void itemSelection_SetCompressionMethod(int cmIndex, Selection* customSelection = nullptr);
Index: scribus/styles/paragraphstyle.attrdefs.cxx
===================================================================
--- scribus/styles/paragraphstyle.attrdefs.cxx	(Revision 26436)
+++ scribus/styles/paragraphstyle.attrdefs.cxx	(Arbeitskopie)
@@ -57,3 +57,4 @@
 ATTRDEF(QString, backgroundColor, BackgroundColor, "None")
 ATTRDEF(double, backgroundShade, BackgroundShade, 100)
 ATTRDEF(int, hyphenConsecutiveLines, HyphenConsecutiveLines, 2)
+ATTRDEF(QString, opticalMarginSetId, OpticalMarginSetId, OpticalMarginLookup::instance().defaultSetId())
Index: scribus/styles/paragraphstyle.h
===================================================================
--- scribus/styles/paragraphstyle.h	(Revision 26436)
+++ scribus/styles/paragraphstyle.h	(Arbeitskopie)
@@ -18,6 +18,7 @@
 
 #include "style.h"
 #include "charstyle.h"
+#include "opticalmarginlookup.h"
 #include "styles/stylecontextproxy.h"
 
 
Index: scribus/ui/opticalmarginswidget.cpp
===================================================================
--- scribus/ui/opticalmarginswidget.cpp	(Revision 26436)
+++ scribus/ui/opticalmarginswidget.cpp	(Arbeitskopie)
@@ -22,6 +22,8 @@
 	parentButton = new QToolButton();
 	parentButton->hide(); // only used for style manager
 
+	setsCombo = new QComboBox();
+
 	QHBoxLayout* mainLayout = new QHBoxLayout();
 	mainLayout->setSpacing(4);
 	mainLayout->setContentsMargins(0, 0, 0, 0);
@@ -29,6 +31,7 @@
 	mainLayout->addWidget(leftButton);
 	mainLayout->addWidget(rightButton);
 	mainLayout->addSpacerItem(new QSpacerItem(4, 0, QSizePolicy::Fixed));
+	mainLayout->addWidget(setsCombo);
 	mainLayout->addWidget(parentButton);
 	mainLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
 
@@ -40,6 +43,7 @@
 	connect(ScQApp, SIGNAL(iconSetChanged()), this, SLOT(iconSetChange()));
 	connect(rightButton, SIGNAL(clicked()), this, SIGNAL(opticalMarginChanged()));
 	connect(leftButton, SIGNAL(clicked()), this, SIGNAL(opticalMarginChanged()));
+	connect(setsCombo, SIGNAL(currentIndexChanged(int)), this, SIGNAL(opticalMarginChanged()));
 }
 
 int OpticalMarginsWidget::opticalMargin()
@@ -86,6 +90,27 @@
 	blockSignals(blocked);
 }
 
+void OpticalMarginsWidget::setOpticalMarginSets(OpticalMarginSets sets)
+{
+	m_sets = sets;
+	setOpticalMarginSetId(opticalMarginSetId());
+}
+
+void OpticalMarginsWidget::setOpticalMarginSetId(const QString id)
+{	
+	if (m_sets.size() <= 0)
+		return;
+
+	m_setId = m_sets.contains(id) ? id : m_sets.firstKey();
+
+	updateSetSelector();
+}
+
+const QString OpticalMarginsWidget::opticalMarginSetId()
+{
+	return setsCombo->currentData().toString();
+}
+
 void OpticalMarginsWidget::iconSetChange()
 {
 	IconManager &im = IconManager::instance();
@@ -103,9 +128,25 @@
 	QWidget::changeEvent(e);
 }
 
+void OpticalMarginsWidget::updateSetSelector()
+{
+	if (m_sets.size() <= 0)
+		return;
+
+	QSignalBlocker blockSets(setsCombo);
+	setsCombo->setVisible( m_sets.size() > 1 ? true : false );
+	setsCombo->clear();
+
+	for (auto [id, set] : m_sets.asKeyValueRange())
+		setsCombo->addItem(set.name, id);
+
+	int index = setsCombo->findData(m_setId);
+	setsCombo->setCurrentIndex(index);
+}
+
 void OpticalMarginsWidget::languageChange()
 {
-	setText(tr("Optical &Margins"));
+	setText( tr("Optical &Margins") );
 
 	leftButton->setToolTip( tr("Optical character offset on left side"));
 	rightButton->setToolTip( tr("Optical character offset on right side"));
Index: scribus/ui/opticalmarginswidget.h
===================================================================
--- scribus/ui/opticalmarginswidget.h	(Revision 26436)
+++ scribus/ui/opticalmarginswidget.h	(Arbeitskopie)
@@ -7,7 +7,9 @@
 #ifndef OPTICALMARGINSWIDGET_H
 #define OPTICALMARGINSWIDGET_H
 
+#include <QComboBox>
 #include <QToolButton>
+#include "opticalmarginlookup.h"
 #include "ui/widgets/form_widget.h"
 
 class ParagraphStyle;
@@ -25,13 +27,23 @@
 	QToolButton *leftButton = {nullptr};
 	QToolButton *rightButton = {nullptr};
 	QToolButton *parentButton = {nullptr};
+	QComboBox *setsCombo = {nullptr};
 
 	int opticalMargin();
 	void setOpticalMargin(int omt);
 
+	void setOpticalMarginSets(OpticalMarginSets sets);
+	OpticalMarginSets opticalMarginSets() { return m_sets; };
+
+	void setOpticalMarginSetId(const QString id);
+	const QString opticalMarginSetId();
+
 protected:
 
 	void changeEvent(QEvent *e) override;
+	void updateSetSelector();
+	OpticalMarginSets m_sets;
+	QString m_setId;
 
 public slots:
 
Index: scribus/ui/prefs_typography.cpp
===================================================================
--- scribus/ui/prefs_typography.cpp	(Revision 26436)
+++ scribus/ui/prefs_typography.cpp	(Arbeitskopie)
@@ -8,9 +8,13 @@
 #include "prefs_typography.h"
 #include "prefsstructs.h"
 #include "scribusdoc.h"
+#include "styles/styleset.h"
+#include "ui/smtextstyles.h"
+#include "scribus.h"
 
-Prefs_Typography::Prefs_Typography(QWidget* parent, ScribusDoc* /*doc*/)
-	: Prefs_Pane(parent)
+Prefs_Typography::Prefs_Typography(QWidget* parent, ScribusDoc* doc)
+	: Prefs_Pane(parent),
+	  m_Doc(doc)
 {
 	setupUi(this);
 	languageChange();
@@ -17,6 +21,8 @@
 
 	m_caption = tr("Typography");
 	m_icon = "16/draw-text.png";
+
+	connect(opticalMarginsEditor, &OpticalMarginEditor::setDeleted, this, &Prefs_Typography::updateParagraphStyles);
 }
 
 Prefs_Typography::~Prefs_Typography() = default;
@@ -41,6 +47,51 @@
 	automaticLineSpacingSpinBox->setToolTip( tr( "Percentage increase over the font size for the line spacing" ) );
 }
 
+void Prefs_Typography::updateParagraphStyles(QString removedID, QString newID)
+{
+	if (!m_Doc)
+		return;
+
+	StyleSet<ParagraphStyle> m_tmpStyles;
+	m_tmpStyles.redefine(m_Doc->paragraphStyles(), true);
+
+	// Update all paragraph styles
+	for (int i = 0; i < m_tmpStyles.count(); ++i)
+	{
+		ParagraphStyle &parStyle = m_tmpStyles[i];
+		if (parStyle.opticalMarginSetId() == removedID)
+			parStyle.setOpticalMarginSetId(newID);
+	}
+
+	m_Doc->redefineStyles(m_tmpStyles, false);
+	m_Doc->scMW()->requestUpdate(reqTextStylesUpdate);
+
+	// Check for local paragraph changes
+	for (auto item : *m_Doc->Items)
+	{
+		if (!item->isTextFrame())
+			continue;
+
+		int par = 0;
+		PageItem_TextFrame *tf = item->asTextFrame();
+
+		while (par < tf->itemText.length())
+		{
+			int nextPar = tf->itemText.nextParagraph(par);
+			ParagraphStyle ps = tf->itemText.paragraphStyle(nextPar);
+
+			if (ps.opticalMarginSetId() == removedID)
+			{
+				ps.setOpticalMarginSetId(newID);
+				tf->itemText.setStyle(nextPar, ps);
+			}
+			par = nextPar;
+		}
+
+	}
+
+}
+
 void Prefs_Typography::restoreDefaults(struct ApplicationPrefs *prefsData)
 {
 	subscriptDisplacementSpinBox->setValue(prefsData->typoPrefs.valueSubScript);
@@ -53,6 +104,7 @@
 	strikeoutLineWidthSpinBox->setValue(prefsData->typoPrefs.valueStrikeThruWidth / 10.0);
 	smallcapsScalingSpinBox->setValue(prefsData->typoPrefs.valueSmallCaps);
 	automaticLineSpacingSpinBox->setValue(prefsData->typoPrefs.autoLineSpacing);
+	opticalMarginsEditor->setOpticalMarginSets(prefsData->typoPrefs.opticalMarginSets);
 }
 
 void Prefs_Typography::saveGuiToPrefs(struct ApplicationPrefs *prefsData) const
@@ -67,5 +119,6 @@
 	prefsData->typoPrefs.valueStrikeThruWidth = strikeoutLineWidthSpinBox->value() * 10.0;
 	prefsData->typoPrefs.valueSmallCaps = smallcapsScalingSpinBox->value();
 	prefsData->typoPrefs.autoLineSpacing = automaticLineSpacingSpinBox->value();
+	prefsData->typoPrefs.opticalMarginSets = opticalMarginsEditor->opticalMarginSets();
 }
 
Index: scribus/ui/prefs_typography.h
===================================================================
--- scribus/ui/prefs_typography.h	(Revision 26436)
+++ scribus/ui/prefs_typography.h	(Arbeitskopie)
@@ -25,8 +25,15 @@
 		void restoreDefaults(struct ApplicationPrefs *prefsData) override;
 		void saveGuiToPrefs(struct ApplicationPrefs *prefsData) const override;
 
+	protected:
+		ScribusDoc* m_Doc {nullptr};
+
 	public slots:
 		void languageChange();
+
+	private slots:
+		void updateParagraphStyles(QString removedID, QString newID);
+
 };
 
 #endif // PREFS_TYPOGRAPHY_H
Index: scribus/ui/propertywidget_alignment.cpp
===================================================================
--- scribus/ui/propertywidget_alignment.cpp	(Revision 26436)
+++ scribus/ui/propertywidget_alignment.cpp	(Arbeitskopie)
@@ -44,7 +44,7 @@
 
 	m_haveItem = tmp;
 
-	showOpticalMargins(newCurrent.opticalMargins());
+	showOpticalMargins(newCurrent.opticalMargins(), newCurrent.opticalMarginSetId());
 }
 
 void PropertyWidget_Alignment::setMainWindow(ScribusMainWindow* mw)
@@ -150,7 +150,6 @@
 	connect(tabsButton    , SIGNAL(clicked())           , this, SLOT(handleTabs()), Qt::UniqueConnection);
 	connect(flopWidget->flopGroup, SIGNAL(idClicked(int)), this, SLOT(handleFirstLinePolicy(int)));
 	connect(opticalMarginsWidget, SIGNAL(opticalMarginChanged()), this, SLOT(handleOpticalMargins()) );
-//	connect(opticalMarginsWidget->resetButton, SIGNAL(clicked()), this, SLOT(resetOpticalMargins()) );
 	connect(textAlignment , SIGNAL(State(int))   , this, SLOT(handleAlignment(int)));
 	connect(textVerticalAlignment, SIGNAL(State(int)), this, SLOT(handleVAlignment(int)));
 	connect(textDirection , SIGNAL(State(int))   , this, SLOT(handleDirection(int)));
@@ -161,7 +160,6 @@
 	disconnect(tabsButton    , SIGNAL(clicked())           , this, SLOT(handleTabs()));
 	disconnect(flopWidget->flopGroup, SIGNAL(idClicked(int)), this, SLOT(handleFirstLinePolicy(int)));
 	disconnect(opticalMarginsWidget, SIGNAL(opticalMarginChanged()), this, SLOT(handleOpticalMargins()) );
-//	disconnect(opticalMarginsWidget->resetButton, SIGNAL(clicked()), this, SLOT(resetOpticalMargins()) );
 	disconnect(textAlignment , SIGNAL(State(int))   , this, SLOT(handleAlignment(int)));
 	disconnect(textVerticalAlignment, SIGNAL(State(int)), this, SLOT(handleVAlignment(int)));
 	disconnect(textDirection , SIGNAL(State(int))   , this, SLOT(handleDirection(int)));
@@ -254,7 +252,7 @@
 		flopWidget->flopBaselineGrid->setChecked(true);
 }
 
-void PropertyWidget_Alignment::showOpticalMargins(int e)
+void PropertyWidget_Alignment::showOpticalMargins(int e, const QString &setId)
 {
 	if (!m_ScMW || m_ScMW->scriptIsRunning())
 		return;
@@ -261,6 +259,8 @@
 	bool tmp = m_haveItem;
 	m_haveItem = false;
 	opticalMarginsWidget->setOpticalMargin(e);
+	opticalMarginsWidget->setOpticalMarginSets(m_doc->opticalMarginSets());
+	opticalMarginsWidget->setOpticalMarginSetId(setId);
 	m_haveItem = tmp;
 }
 
@@ -389,8 +389,6 @@
 	if (!m_doc || !m_item || !m_ScMW || m_ScMW->scriptIsRunning())
 		return;
 
-	int omt(opticalMarginsWidget->opticalMargin());
-
 	PageItem *item = m_item;
 	if (m_doc->appMode == modeEditTable)
 		item = m_item->asTable()->activeCell().textFrame();
@@ -398,25 +396,10 @@
 	{
 		Selection tempSelection(this, false);
 		tempSelection.addItem(item, true);
-		m_doc->itemSelection_SetOpticalMargins(omt, &tempSelection);
+		m_doc->itemSelection_SetOpticalMargins(opticalMarginsWidget->opticalMargin(), opticalMarginsWidget->opticalMarginSetId(), &tempSelection);
 	}
 }
 
-// void PropertyWidget_Alignment::resetOpticalMargins()
-// {
-// 	// if (!m_doc || !m_item || !m_ScMW || m_ScMW->scriptIsRunning())
-// 	// 	return;
-// 	// PageItem *item = m_item;
-// 	// if (m_doc->appMode == modeEditTable)
-// 	// 	item = m_item->asTable()->activeCell().textFrame();
-// 	// if (item != nullptr)
-// 	// {
-// 	// 	Selection tempSelection(this, false);
-// 	// 	tempSelection.addItem(item, true);
-// 	// 	m_doc->itemSelection_resetOpticalMargins(&tempSelection);
-// 	// }
-// }
-
 void PropertyWidget_Alignment::changeEvent(QEvent *e)
 {
 	if (e->type() == QEvent::LanguageChange)
Index: scribus/ui/propertywidget_alignment.h
===================================================================
--- scribus/ui/propertywidget_alignment.h	(Revision 26436)
+++ scribus/ui/propertywidget_alignment.h	(Arbeitskopie)
@@ -53,7 +53,7 @@
 	void toggleLabelVisibility(bool v);
 
 	void showFirstLinePolicy(FirstLineOffsetPolicy f);
-	void showOpticalMargins(int e);
+	void showOpticalMargins(int e, const QString &setId);
 	void showAlignment(int e);
 	void showDirection(int e);
 
@@ -62,8 +62,6 @@
 	void handleTabs();
 	void handleFirstLinePolicy(int);
 	void handleOpticalMargins();
-//	void resetOpticalMargins();
-
 	void handleAlignment(int a);
 	void handleVAlignment(int a);
 	void handleDirection(int d);
Index: scribus/ui/smopticalmarginswidget.cpp
===================================================================
--- scribus/ui/smopticalmarginswidget.cpp	(Revision 26436)
+++ scribus/ui/smopticalmarginswidget.cpp	(Arbeitskopie)
@@ -54,12 +54,50 @@
 	connect(parentButton, SIGNAL(pressed()), this, SLOT(pbPressed()));
 }
 
+void SMOpticalMarginsWidget::setOpticalMarginSetId(const QString o)
+{
+	disconnect(parentButton, SIGNAL(pressed()), this, SLOT(pbPressed()));
+	disconnect(this, SIGNAL(opticalMarginChanged()), this, SLOT(styleChanged()));
+
+	m_hasParent = false;
+	parentButton->hide();
+	m_pSetId = "";
+
+	OpticalMarginsWidget::setOpticalMarginSetId(o);
+
+	connect(this, SIGNAL(opticalMarginChanged()), this, SLOT(styleChanged()));
+	connect(parentButton, SIGNAL(pressed()), this, SLOT(pbPressed()));
+}
+
+void SMOpticalMarginsWidget::setOpticalMarginSetId(const QString o, const QString ps, bool isParentValue)
+{
+	disconnect(parentButton, SIGNAL(pressed()), this, SLOT(pbPressed()));
+	disconnect(this, SIGNAL(opticalMarginChanged()), this, SLOT(styleChanged()));
+
+	if (!ps.isEmpty())
+	{
+		m_hasParent = true;
+		parentButton->setVisible(!isParentValue);
+		m_pSetId = ps;
+	}
+	else
+		m_hasParent = false;
+
+	OpticalMarginsWidget::setOpticalMarginSetId(o);
+
+	connect(this, SIGNAL(opticalMarginChanged()), this, SLOT(styleChanged()));
+	connect(parentButton, SIGNAL(pressed()), this, SLOT(pbPressed()));
+}
+
 bool SMOpticalMarginsWidget::useParentValue()
 {
 	bool ret = m_useParentStyle;
 	m_useParentStyle = false;
 	if (ret)
+	{
 		setOpticalMargins(m_pStyle, m_pStyle, true);
+		setOpticalMarginSetId(m_pSetId, m_pSetId, true);
+	}
 
 	return ret;
 }
Index: scribus/ui/smopticalmarginswidget.h
===================================================================
--- scribus/ui/smopticalmarginswidget.h	(Revision 26436)
+++ scribus/ui/smopticalmarginswidget.h	(Arbeitskopie)
@@ -20,6 +20,9 @@
 	void setOpticalMargins(int o);
 	void setOpticalMargins(int o, int ps, bool isParentValue);
 
+	void setOpticalMarginSetId(const QString o);
+	void setOpticalMarginSetId(const QString o, const QString ps, bool isParentValue);
+
 	bool useParentValue();
 
 private:
@@ -26,6 +29,7 @@
 	bool m_hasParent = false;
 	bool m_useParentStyle = false;
 	int m_pStyle = -1;
+	QString m_pSetId;
 
 private slots:
 	void styleChanged();
Index: scribus/ui/smpstylewidget.cpp
===================================================================
--- scribus/ui/smpstylewidget.cpp	(Revision 26436)
+++ scribus/ui/smpstylewidget.cpp	(Arbeitskopie)
@@ -224,6 +224,8 @@
 	//fillNumerationsCombo();
 	//fillNumRestartCombo();
 
+	optMarginWidget->setOpticalMarginSets(m_Doc->opticalMarginSets());
+
 	if (m_hasParent)
 	{
 		lineSpacingMode->setCurrentItem(pstyle->lineSpacingMode(), pstyle->isInhLineSpacingMode());
@@ -231,7 +233,8 @@
 		
 //		optMarginCombo->setCurrentItemByData( pstyle->opticalMargins(),  pstyle->isInhOpticalMargins() );
 //		optMarginCombo->setParentItem(optMarginCombo->getItemIndexForData( parent->opticalMargins()));
-		optMarginWidget->setOpticalMargins(pstyle->opticalMargins(), parent->opticalMargins(), pstyle->isInhOpticalMargins());
+		optMarginWidget->setOpticalMargins(pstyle->opticalMargins(), parent->opticalMargins(), pstyle->isInhOpticalMargins());		
+		optMarginWidget->setOpticalMarginSetId(pstyle->opticalMarginSetId(), parent->opticalMarginSetId(), pstyle->isInhOpticalMarginSetId());
 		
 		minSpaceSpin->setValue(pstyle->minWordTracking() * 100.0,  pstyle->isInhMinWordTracking());
 		minSpaceSpin->setParentValue(parent->minWordTracking());
@@ -350,7 +353,8 @@
 		spaceAbove->setValue(pstyle->gapBefore());
 		spaceBelow->setValue(pstyle->gapAfter());
 //		optMarginCombo->setCurrentItemByData( pstyle->opticalMargins() );
-		optMarginWidget->setOpticalMargins(pstyle->opticalMargins());
+		optMarginWidget->setOpticalMargins(pstyle->opticalMargins());
+		optMarginWidget->setOpticalMarginSetId(pstyle->opticalMarginSetId());
 		minSpaceSpin->setValue(pstyle->minWordTracking() * 100.0);
 		minGlyphExtSpin->setValue(pstyle->minGlyphExtension() * 100.0);
 		maxGlyphExtSpin->setValue(pstyle->maxGlyphExtension() * 100.0);
@@ -658,7 +662,9 @@
 //		}
 //	}
 //	optMarginCombo->setCurrentItemByData(o);
-	optMarginWidget->setOpticalMargins(pstyles[0]->opticalMargins());
+	optMarginWidget->setOpticalMargins(pstyles[0]->opticalMargins());
+	optMarginWidget->setOpticalMarginSets(m_Doc->opticalMarginSets());
+	optMarginWidget->setOpticalMarginSetId(pstyles[0]->opticalMarginSetId());
 }
 
 void SMPStyleWidget::showMinSpace(const QList< ParagraphStyle * > & pstyles)
Index: scribus/ui/smtextstyles.cpp
===================================================================
--- scribus/ui/smtextstyles.cpp	(Revision 26436)
+++ scribus/ui/smtextstyles.cpp	(Arbeitskopie)
@@ -508,14 +508,11 @@
 
 	connect(m_pwidget, SIGNAL(useParentParaEffects()), this, SLOT(slotParentParaEffects()));
 	connect(m_pwidget->peCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPargraphEffects(int)));
-//	connect(m_pwidget->dropCapsBox, SIGNAL(toggled(bool)), this, SLOT(slotDropCap(bool)));
 	connect(m_pwidget->dropCapLines, SIGNAL(valueChanged(int)), this, SLOT(slotDropCapLines(int)));
 	connect(m_pwidget->parEffectOffset, SIGNAL(valueChanged(double)), this, SLOT(slotParEffectOffset()));
 	connect(m_pwidget->parEffectIndentBox, SIGNAL(toggled(bool)), this, SLOT(slotParEffectIndent(bool)));
 	connect(m_pwidget->parEffectCharStyleCombo, SIGNAL(activated(int)), this, SLOT(slotParEffectCharStyle(int)));
-//	connect(m_pwidget->bulletBox, SIGNAL(toggled(bool)), this, SLOT(slotBullet(bool)));
 	connect(m_pwidget->bulletStrEdit, SIGNAL(editTextChanged(QString)), this, SLOT(slotBulletStr(QString)));
-//	connect(m_pwidget->numBox, SIGNAL(toggled(bool)), this, SLOT(slotNumeration(bool)));
 	connect(m_pwidget->numComboBox, SIGNAL(textActivated(QString)), this, SLOT(slotNumName(QString)));
 	connect(m_pwidget->numLevelSpin, SIGNAL(valueChanged(int)), this, SLOT(slotNumLevel(int)));
 	connect(m_pwidget->numFormatCombo, SIGNAL(activated(int)), this, SLOT(slotNumFormat(int)));
@@ -553,10 +550,6 @@
 	connect(m_pwidget->cpage->effects_->UnderlineVal->LWidth, SIGNAL(valueChanged(double)), this, SLOT(slotEffectProperties()));
 	connect(m_pwidget->cpage->effects_->StrikeVal->LPos, SIGNAL(valueChanged(double)), this, SLOT(slotEffectProperties()));
 	connect(m_pwidget->cpage->effects_->StrikeVal->LWidth, SIGNAL(valueChanged(double)), this, SLOT(slotEffectProperties()));
-	// connect(m_pwidget->cpage->fillColor_, SIGNAL(currentTextChanged(QString)), this, SLOT(slotFillColor()));
-	// connect(m_pwidget->cpage->fillShade_, SIGNAL(clicked()), this, SLOT(slotFillShade()));
-	// connect(m_pwidget->cpage->strokeColor_, SIGNAL(currentTextChanged(QString)), this, SLOT(slotStrokeColor()));
-	// connect(m_pwidget->cpage->strokeShade_, SIGNAL(clicked()), this, SLOT(slotStrokeShade()));
 	connect(m_pwidget->cpage->language_, SIGNAL(activated(int)), this, SLOT(slotLanguage()));
 	connect(m_pwidget->cpage->fontSize_, SIGNAL(valueChanged(double)), this, SLOT(slotFontSize()));
 	connect(m_pwidget->cpage->fontHScale_, SIGNAL(valueChanged(double)), this, SLOT(slotScaleH()));
@@ -565,8 +558,6 @@
 	connect(m_pwidget->cpage->widthSpaceSpin, SIGNAL(valueChanged(double)), this, SLOT(slotWordTracking()));
 	connect(m_pwidget->cpage->baselineOffset_, SIGNAL(valueChanged(double)), this, SLOT(slotBaselineOffset()));
 	connect(m_pwidget->cpage->parentCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(slotCharParentChanged(QString)));
-	// connect(m_pwidget->cpage->backColor_, SIGNAL(currentTextChanged(QString)), this, SLOT(slotBackColor()));
-	// connect(m_pwidget->cpage->backShade_, SIGNAL(clicked()), this, SLOT(slotBackShade()));
 
 	connect(m_pwidget->cpage->textColor->colorButton, SIGNAL(colorChanged()), this, SLOT(slotFillColor()));
 	connect(m_pwidget->cpage->textColor->parentButton, SIGNAL(clicked()), this, SLOT(slotFillColor()));
@@ -612,14 +603,11 @@
 
 	disconnect(m_pwidget, SIGNAL(useParentParaEffects()), this, SLOT(slotParentParaEffects()));
 	disconnect(m_pwidget->peCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPargraphEffects(int)));
-//	disconnect(m_pwidget->dropCapsBox, SIGNAL(toggled(bool)), this, SLOT(slotDropCap(bool)));
 	disconnect(m_pwidget->dropCapLines, SIGNAL(valueChanged(int)), this, SLOT(slotDropCapLines(int)));
 	disconnect(m_pwidget->parEffectOffset, SIGNAL(valueChanged(double)), this, SLOT(slotParEffectOffset()));
 	disconnect(m_pwidget->parEffectIndentBox, SIGNAL(toggled(bool)), this, SLOT(slotParEffectIndent(bool)));
 	disconnect(m_pwidget->parEffectCharStyleCombo, SIGNAL(activated(int)), this, SLOT(slotParEffectCharStyle(int)));
-//	disconnect(m_pwidget->bulletBox, SIGNAL(toggled(bool)), this, SLOT(slotBullet(bool)));
 	disconnect(m_pwidget->bulletStrEdit, SIGNAL(editTextChanged(QString)), this, SLOT(slotBulletStr(QString)));
-//	disconnect(m_pwidget->numBox, SIGNAL(toggled(bool)), this, SLOT(slotNumeration(bool)));
 	disconnect(m_pwidget->numComboBox, SIGNAL(textActivated(QString)), this, SLOT(slotNumName(QString)));
 	disconnect(m_pwidget->numFormatCombo, SIGNAL(activated(int)), this, SLOT(slotNumFormat(int)));
 	disconnect(m_pwidget->numLevelSpin, SIGNAL(valueChanged(int)), this, SLOT(slotNumLevel(int)));
@@ -655,10 +643,6 @@
 	disconnect(m_pwidget->cpage->effects_->UnderlineVal->LWidth, SIGNAL(valueChanged(double)), this, SLOT(slotEffectProperties()));
 	disconnect(m_pwidget->cpage->effects_->StrikeVal->LPos, SIGNAL(valueChanged(double)), this, SLOT(slotEffectProperties()));
 	disconnect(m_pwidget->cpage->effects_->StrikeVal->LWidth, SIGNAL(valueChanged(double)), this, SLOT(slotEffectProperties()));
-	// disconnect(m_pwidget->cpage->fillColor_, SIGNAL(currentTextChanged(QString)), this, SLOT(slotFillColor()));
-	// disconnect(m_pwidget->cpage->fillShade_, SIGNAL(clicked()), this, SLOT(slotFillShade()));
-	// disconnect(m_pwidget->cpage->strokeColor_, SIGNAL(currentTextChanged(QString)), this, SLOT(slotStrokeColor()));
-	// disconnect(m_pwidget->cpage->strokeShade_, SIGNAL(clicked()), this, SLOT(slotStrokeShade()));
 	disconnect(m_pwidget->cpage->language_, SIGNAL(activated(int)), this, SLOT(slotLanguage()));
 	disconnect(m_pwidget->cpage->fontSize_, SIGNAL(valueChanged(double)), this, SLOT(slotFontSize()));
 	disconnect(m_pwidget->cpage->fontHScale_, SIGNAL(valueChanged(double)), this, SLOT(slotScaleH()));
@@ -667,8 +651,6 @@
 	disconnect(m_pwidget->cpage->widthSpaceSpin, SIGNAL(valueChanged(double)), this, SLOT(slotWordTracking()));
 	disconnect(m_pwidget->cpage->baselineOffset_, SIGNAL(valueChanged(double)), this, SLOT(slotBaselineOffset()));
 	disconnect(m_pwidget->cpage->parentCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(slotCharParentChanged(QString)));
-	// disconnect(m_pwidget->cpage->backColor_, SIGNAL(currentTextChanged(QString)), this, SLOT(slotBackColor()));
-	// disconnect(m_pwidget->cpage->backShade_, SIGNAL(clicked()), this, SLOT(slotBackShade()));
 
 	disconnect(m_pwidget->cpage->textColor->colorButton, SIGNAL(colorChanged()), this, SLOT(slotFillColor()));
 	disconnect(m_pwidget->cpage->textColor->parentButton, SIGNAL(clicked()), this, SLOT(slotFillColor()));
@@ -796,23 +778,21 @@
 	ParagraphStyle::OpticalMarginType omt( static_cast<ParagraphStyle::OpticalMarginType>(m_pwidget->optMarginWidget->opticalMargin()));
 	if (m_pwidget->optMarginWidget->useParentValue())
 		for (int i = 0; i < m_selection.count(); ++i)
+		{
 			m_selection[i]->resetOpticalMargins();
+			m_selection[i]->resetOpticalMarginSetId();
+		}
 	else
 		for (int i = 0; i < m_selection.count(); ++i)
-			m_selection[i]->setOpticalMargins(omt);
+		{
+			m_selection[i]->setOpticalMarginSetId(m_pwidget->optMarginWidget->opticalMarginSetId());
+			m_selection[i]->setOpticalMargins(omt);			
+		}
 
 	slotSelectionDirty();
 
 }
 
-void SMParagraphStyle::slotParentOpticalMargin()
-{
-	for (int i = 0; i < m_selection.count(); ++i)
-		m_selection[i]->resetOpticalMargins();
-
-	slotSelectionDirty();
-}
-
 void SMParagraphStyle::slotMinSpace()
 {
 	if (m_pwidget->minSpaceSpin->useParentValue())
Index: scribus/ui/smtextstyles.h
===================================================================
--- scribus/ui/smtextstyles.h	(Revision 26436)
+++ scribus/ui/smtextstyles.h	(Arbeitskopie)
@@ -97,7 +97,6 @@
 	void slotDirection();
 	void slotOpticalMargin(int i);
 	void slotOpticalMarginSelector();
-	void slotParentOpticalMargin();
 	void slotMinSpace();
 	void slotMinGlyphExt();
 	void slotMaxGlyphExt();
Index: scribus/ui/stylemanager.cpp
===================================================================
--- scribus/ui/stylemanager.cpp	(Revision 26436)
+++ scribus/ui/stylemanager.cpp	(Arbeitskopie)
@@ -473,6 +473,9 @@
 		QStringList neededColors;
 		neededColors.clear();
 
+		QStringList neededOpticalMarginSets;
+		neededOpticalMarginSets.clear();
+
 		foreach (const QString& aStyle, dia2->paragraphStyles())
 		{
 			ParagraphStyle& sty(tmpParaStyles[tmpParaStyles.find(aStyle)]);
@@ -493,6 +496,9 @@
 				neededColors.append(sty.charStyle().strokeColor());
 			if ((!m_doc->PageColors.contains(sty.charStyle().fillColor())) && (!neededColors.contains(sty.charStyle().fillColor())))
 				neededColors.append(sty.charStyle().fillColor());
+
+			if ((!m_doc->opticalMarginSets().contains(sty.opticalMarginSetId())) && (!neededOpticalMarginSets.contains(sty.opticalMarginSetId())))
+				neededOpticalMarginSets.append(sty.opticalMarginSetId());
 		}
 
 		foreach (const QString& aStyle, dia2->characterStyles())
@@ -554,6 +560,11 @@
 				}
 			}
 		}
+
+		if (!neededOpticalMarginSets.isEmpty())
+		{
+			// TODO: Load missing optical margin sets from doc
+		}
 	}
 	delete dia2;
 // Start hack part 2
Index: scribus/util_text.cpp
===================================================================
--- scribus/util_text.cpp	(Revision 26436)
+++ scribus/util_text.cpp	(Arbeitskopie)
@@ -94,3 +94,29 @@
 	return QString(xml.c_str());
 }
 
+
+const 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);
+
+}
+
+const 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 26436)
+++ scribus/util_text.h	(Arbeitskopie)
@@ -30,4 +30,7 @@
 //returns string with saxed story
 QString SCRIBUS_API saxedText(StoryText* story);
 
+const QString SCRIBUS_API stringToUnicode(const QString &text);
+const QString SCRIBUS_API unicodeToString(const QString &text);
+
 #endif
doc_prefs.png (91,162 bytes)   
doc_prefs.png (91,162 bytes)   

jghali

2024-11-20 22:20

administrator   ~0051619

1) The implementation of Prefs_Typography::updateParagraphStyles() is not ok as you only iterate through items corresponding to the current page/masterpage modes. As a consequence frames used by table cells, inline items or patterns would not be updated. To solve this I strongly advise you to integrate the optical margin sets replacement inside the ScribusDoc's replaceNamedResources() framework :
- add handling of optical margin sets to ResourceCollection class
- handle optical margin set replacement in ParagraphStyle::replaceNamedResources()
- then instead of iterating manually through items inside Prefs_Typography::updateParagraphStyles(), call ScribusDoc::replaceNamedResources()

2) Some coding style errors in prefsmanager::readPref()
- missing spaces : if( => if (, for( => for (
- some big blocks of code can be avoided by continuing, for eg:

for(int i = 0; i < setNodes.count(); i++)
{
    QDomNode setNode = setNodes.at(i);
    if(setNode.isElement())
    {
         // big block of code here
    }
}

can be rewritten to:

for (int i = 0; i < setNodes.count(); i++)
{
    QDomNode setNode = setNodes.at(i);
    if (!setNode.isElement())
        continue;
     
     // big block of code here
}

nitramr

2024-11-22 01:00

developer   ~0051630

Hi jean, thanks for your advice. I changed the implementation as you recommended. In addition, I fixed a few small bugs from the previous patch, but it makes no difference to the user because the features are still disabled from the UI.

If someone is interested in testing these features, you have to remove these code lines from opticalmargineditor.cpp

buttonAddNewSet->setVisible(false);
buttonRemoveSet->setVisible(false);
buttonResetAllSets->setVisible(false);
buttonLoadMissingDefaults->setVisible(false);
label_2->setVisible(false);
files-4.zip (11,320 bytes)
opticalmargins_2024-11-22_01.patch (74,897 bytes)   
Index: resources/iconsets/1_7_0/1_7_0.xml
===================================================================
--- resources/iconsets/1_7_0/1_7_0.xml	(Revision 26446)
+++ 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" />
@@ -146,15 +148,14 @@
 		<icon id="expand" file="16/action-chevron-down.svg" />
 		<icon id="inherit-opacity-off" file="16/action-inherit-opacity-off.svg" />
 		<icon id="inherit-opacity-on" file="16/action-inherit-opacity-on.svg" />
+		<icon id="remove" file="16/action-remove.svg" />
 		<icon id="reset" file="16/action-reload.svg" />
 		<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 26446)
+++ 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 26446)
+++ 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"
@@ -647,11 +648,22 @@
 		lineData.ascent = lineData.descent = 0;
 		if (glyphs.isEmpty())
 			return;
-		const CharStyle& cStyle(glyphs.at(0).style());
-		double scaleV = cStyle.scaleV() / 1000.0;
-		double offset = (cStyle.fontSize() / 10) * (cStyle.baselineOffset() / 1000.0);
-		lineData.ascent = cStyle.font().ascent(cStyle.fontSize() / 10.00) * scaleV + offset;
-		lineData.descent = cStyle.font().descent(cStyle.fontSize() / 10.00) * scaleV - offset;
+
+		// #17286 seek for largest glphy in line
+		for (int i = 0; i < glyphs.count(); i++)
+		{
+			const CharStyle& cStyle(glyphs.at(i).style());
+			double scaleV = cStyle.scaleV() / 1000.0;
+			double offset = (cStyle.fontSize() / 10) * (cStyle.baselineOffset() / 1000.0);
+			double tmpAscent = cStyle.font().ascent(cStyle.fontSize() / 10.00) * scaleV + offset;
+
+			if (lineData.ascent < tmpAscent)
+			{
+				lineData.ascent = tmpAscent;
+				lineData.descent = cStyle.font().descent(cStyle.fontSize() / 10.00) * scaleV;
+			}
+
+		}
 	}
 
 // yPos should not be changed when all line is already calculated - at new y position there can be overflow!!!
@@ -901,44 +913,34 @@
 
 
 	//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 ParagraphStyle& pStyle = itemText.paragraphStyle(lineData.lastCluster);
+			const ScFace& font = style.font();
+			double chs = style.fontSize() * (style.scaleH() / 1000.0);
+			QChar chr = itemText.text(glyphs[b].firstChar());
+			double leftCorr = 0;
+
+			OpticalMarginSet set = doc->opticalMarginSets().value(pStyle.opticalMarginSetId());
+			OpticalMarginRule rule = OpticalMarginLookup::instance().offset(chr, set);
+			if ( rule.Unit == SC_PERCENT)
+				leftCorr = font.glyphBBox(font.char2CMap(chr.unicode()), chs / 10.0).width * rule.Left;
+			else
+				leftCorr = rule.Left;
+
+			return leftCorr;
+		}
+		return 0.0;
+	}
+
 	/// calculate how much the last char should stick out to the right
 	double opticalRightMargin(const StoryText& itemText)
 	{
@@ -951,46 +953,20 @@
 		if (b >= 0)
 		{
 			const CharStyle& style = glyphs[b].style();
+			const ParagraphStyle& pStyle = itemText.paragraphStyle(lineData.lastCluster);
 			const ScFace& font = style.font();
 			double chs = style.fontSize() * (style.scaleH() / 1000.0);
-			QChar chr = itemText.text(glyphs[b].lastChar());
-			double rightCorr;
-			if (glyphs[b].hasFlag(ScLayout_SoftHyphenVisible))
-				rightCorr = font.hyphenWidth(style, chs / 10.0);
+			bool softHypen = glyphs[b].hasFlag(ScLayout_SoftHyphenVisible);
+			QChar chr = softHypen ? QChar(style.hyphenChar()) : itemText.text(glyphs[b].lastChar());
+			double rightCorr = 0;
+
+			OpticalMarginSet set = doc->opticalMarginSets().value(pStyle.opticalMarginSetId());
+			OpticalMarginRule rule = OpticalMarginLookup::instance().offset(chr, set);
+			if ( rule.Unit == SC_PERCENT)
+				rightCorr = softHypen ? font.hyphenWidth(style, chs / 10.0) * rule.Right : font.glyphBBox(font.char2CMap(chr.unicode()), chs / 10.0).width * rule.Right;
 			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 = rule.Right;
+
 			return rightCorr;
 		}
 		return 0.0;
@@ -2438,6 +2414,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 +2448,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 +2537,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 +2568,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 +2833,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 +2866,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 26446)
+++ scribus/plugins/fileloader/scribus170format/scribus170format.cpp	(Arbeitskopie)
@@ -1974,6 +1974,11 @@
 			if (!success) break;
 			m_Doc->setUsesMarksAndNotes(true);
 		}
+		else if (tagName == QLatin1String("OpticalMarginSets"))
+		{
+			success = readOpticalMarginSets(m_Doc, reader);
+			if (!success) break;
+		}
 		else
 		{
 			reader.skipCurrentElement();
@@ -3166,6 +3171,10 @@
 	if (attrs.hasAttribute(OpticalMargins))
 		newStyle.setOpticalMargins(attrs.valueAsInt(OpticalMargins));
 
+	static const QString OpticalMarginSetId("OpticalMarginSetId");
+	if (attrs.hasAttribute(OpticalMarginSetId))
+		newStyle.setOpticalMarginSetId(attrs.valueAsString(OpticalMarginSetId));
+
 	static const QString HyphenConsecutiveLines("HyphenConsecutiveLines");
 	if (attrs.hasAttribute(HyphenConsecutiveLines))
 		newStyle.setHyphenConsecutiveLines(attrs.valueAsInt(HyphenConsecutiveLines));
@@ -3974,6 +3983,52 @@
 	return !reader.hasError();
 }
 
+bool Scribus170Format::readOpticalMarginSets(ScribusDoc *doc, ScXmlStreamReader &reader) const
+{
+	QString tagName(reader.nameAsString());
+	OpticalMarginSets sets;
+	OpticalMarginSet set;
+	QMap<QString, QString> names;
+	OpticalMarginRules rules;
+
+	while (!reader.atEnd() && !reader.hasError())
+	{
+		reader.readNext();
+		if (reader.isEndElement() && reader.name() == tagName)
+			break;
+
+		if (reader.isStartElement() && reader.name() == QLatin1String("Set"))
+		{
+			ScXmlStreamAttributes attrs = reader.scAttributes();
+			set.id = attrs.valueAsString("Id");
+			set.type = attrs.valueAsString("Type");
+			set.name = attrs.valueAsString("Name");
+		}
+
+		if (reader.isStartElement() && reader.name() == QLatin1String("Rule"))
+		{
+			ScXmlStreamAttributes attrs = reader.scAttributes();
+			OpticalMarginRule rule(unicodeToString(attrs.valueAsString("Characters")), attrs.valueAsDouble("Left"), attrs.valueAsDouble("Right"), attrs.valueAsInt("Unit"));
+			rules.append(rule);
+		}
+
+		if (reader.isEndElement() && reader.name() == QLatin1String("Rules"))
+		{
+			set.rules = OpticalMarginLookup::instance().splitRulesToChars(rules);
+			rules.clear();
+		}
+
+		if (reader.isEndElement() && reader.name() == QLatin1String("Set"))
+			sets.insert(set.id, set);
+
+	}
+
+	doc->typographicPrefs().opticalMarginSets = sets;
+	m_Doc->setOpticalMarginSets(sets);
+
+	return !reader.hasError();
+}
+
 bool Scribus170Format::readSections(ScribusDoc* doc, ScXmlStreamReader& reader) const
 {
 	QString tagName(reader.nameAsString());
@@ -5553,6 +5608,8 @@
 		pstyle.setMaxGlyphExtension(attrs.valueAsDouble("MaxGlyphExtend"));
 	if (attrs.hasAttribute("OpticalMargins"))
 		pstyle.setOpticalMargins(attrs.valueAsInt("OpticalMargins"));
+	if (attrs.hasAttribute("OpticalMarginSetId"))
+		pstyle.setOpticalMarginSetId(attrs.valueAsString("OpticalMarginSetId"));
 	if (attrs.hasAttribute("HyphenationMode"))
 		pstyle.setHyphenationMode(attrs.valueAsInt("HyphenationMode"));
 	if (attrs.hasAttribute("leftMargin"))
Index: scribus/plugins/fileloader/scribus170format/scribus170format.h
===================================================================
--- scribus/plugins/fileloader/scribus170format/scribus170format.h	(Revision 26446)
+++ 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 readOpticalMarginSets(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 writeOpticalMarginSets(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 26446)
+++ 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)
 {
@@ -540,6 +540,7 @@
 	writeTOC(docu);
 	writeMarks(docu);
 	writeNotesStyles(docu);
+	writeOpticalMarginSets(docu);
 	writeNotesFrames(docu);
 	writeNotes(docu);
 	writePageSets(docu);
@@ -882,6 +883,8 @@
 		docu.writeAttribute("NumerationHigher", static_cast<int>(style.numHigher()));
 	if ( ! style.isInhOpticalMargins())
 		docu.writeAttribute("OpticalMargins", style.opticalMargins());
+	if ( ! style.isInhOpticalMarginSetId())
+		docu.writeAttribute("OpticalMarginSetId", style.opticalMarginSetId());
 	if ( ! style.isInhHyphenConsecutiveLines())
 		docu.writeAttribute("HyphenConsecutiveLines", style.hyphenConsecutiveLines());
 	if ( ! style.isInhHyphenationMode())
@@ -1531,6 +1534,40 @@
 	docu.writeEndElement();
 }
 
+void Scribus170Format::writeOpticalMarginSets(ScXmlStreamWriter & docu)
+{
+	docu.writeStartElement("OpticalMarginSets");
+
+	OpticalMarginSets sets = m_Doc->typographicPrefs().opticalMarginSets;
+
+	for (const auto &set : sets)
+	{
+		docu.writeStartElement("Set");
+		docu.writeAttribute("Id", set.id);
+		docu.writeAttribute("Type", set.type);
+		docu.writeAttribute("Name", set.name);
+
+		docu.writeStartElement("Rules");
+
+		OpticalMarginRules rules = OpticalMarginLookup::instance().mergeCharsToRules(set.rules);
+
+		for (const auto &rule : rules)
+		{
+			docu.writeEmptyElement("Rule");
+			docu.writeAttribute("Left", rule.Left);
+			docu.writeAttribute("Right", rule.Right);
+			docu.writeAttribute("Unit", rule.Unit);
+			docu.writeAttribute("Characters", stringToUnicode(rule.Chars));
+		}
+
+		docu.writeEndElement(); // Rules
+		docu.writeEndElement(); // Set
+
+	}
+
+	docu.writeEndElement(); // OpticalMarginSets
+}
+
 void Scribus170Format::writeNotesStyles(ScXmlStreamWriter & docu)
 {
 	if (m_Doc->m_docNotesStylesList.isEmpty())
Index: scribus/prefsmanager.cpp
===================================================================
--- scribus/prefsmanager.cpp	(Revision 26446)
+++ 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"
@@ -224,6 +225,7 @@
 	appPrefs.typoPrefs.valueStrikeThruPos = -1;
 	appPrefs.typoPrefs.valueStrikeThruWidth = -1;
 	appPrefs.guidesPrefs.valueBaselineGrid = 14.4;
+	appPrefs.typoPrefs.opticalMarginSets = OpticalMarginLookup::instance().defaultSets();
 	appPrefs.guidesPrefs.offsetBaselineGrid = 0.0;
 	appPrefs.displayPrefs.showToolTips = true;
 	appPrefs.displayPrefs.showMouseCoordinates = true;
@@ -1920,6 +1922,34 @@
 	experimentalElem.setAttribute("NotesEnabled", appPrefs.experimentalFeaturePrefs.notesEnabled);
 	elem.appendChild(experimentalElem);
 
+	QDomElement opticalMarginSets = docu.createElement("OpticalMarginSets");
+	OpticalMarginSets &oms = appPrefs.typoPrefs.opticalMarginSets;
+	for (const auto &set : oms)
+	{
+		QDomElement setItem = docu.createElement("Set");
+		setItem.setAttribute("Id", set.id);
+		setItem.setAttribute("Type", set.type);
+		setItem.setAttribute("Name", set.name);
+
+		QDomElement rulesItem = docu.createElement("Rules");
+		setItem.appendChild(rulesItem);
+
+		OpticalMarginRules rules = OpticalMarginLookup::instance().mergeCharsToRules(set.rules);
+
+		for (auto &rule : rules)
+		{
+			QDomElement ruleItem = docu.createElement("Rule");
+			ruleItem.setAttribute("Left", rule.Left);
+			ruleItem.setAttribute("Right", rule.Right);
+			ruleItem.setAttribute("Unit", rule.Unit);
+			ruleItem.setAttribute("Characters", stringToUnicode(rule.Chars));
+			rulesItem.appendChild(ruleItem);
+		}
+
+		opticalMarginSets.appendChild(setItem);
+	}
+	elem.appendChild(opticalMarginSets);
+
 	// write file
 	bool result = false;
 	QFile f(filePath);
@@ -2768,6 +2798,60 @@
 			appPrefs.experimentalFeaturePrefs.notesEnabled = static_cast<bool>(dc.attribute("NotesEnabled", "0").toInt());
 		}
 
+		// optical margin sets
+		if (dc.tagName() == "OpticalMarginSets")
+		{
+			OpticalMarginSets sets;
+			OpticalMarginSet set;
+
+			QDomNodeList setNodes = dc.elementsByTagName("Set");
+
+			for (int i = 0; i < setNodes.count(); i++) {
+				QDomNode setNode = setNodes.at(i);
+				if (!setNode.isElement())
+					continue;
+
+				QDomElement setElement = setNode.toElement();
+				set.id = setElement.attribute("Id");
+				set.type = setElement.attribute("Type");
+				set.name = setElement.attribute("Name");
+
+				QDomNodeList childrenList = setElement.childNodes();
+				for (int i = 0; i < childrenList.count(); i++) {
+					QDomNode child = childrenList.at(i);
+					if (!child.isElement())
+						continue;
+
+					QDomElement childElement = child.toElement();
+					if (childElement.tagName() != "Rules")
+						continue;
+
+					OpticalMarginRules rules;
+
+					QDomNodeList ruleList = childElement.elementsByTagName("Rule");
+					for (int i = 0; i < ruleList.count(); i++) {
+						QDomNode rule = ruleList.at(i);
+						if (!rule.isElement())
+							continue;
+
+						QDomElement ruleElement = rule.toElement();
+						OpticalMarginRule omr(unicodeToString(ruleElement.attribute("Characters")),
+											  ruleElement.attribute("Left").toDouble(),
+											  ruleElement.attribute("Right").toDouble(),
+											  ruleElement.attribute("Unit").toInt());
+
+						rules.append(omr);
+					}
+
+					set.rules = OpticalMarginLookup::instance().splitRulesToChars(rules);
+				}
+
+				sets.insert(set.id, set);
+			}
+
+			appPrefs.typoPrefs.opticalMarginSets = sets;
+		}
+
 		domNode = domNode.nextSibling();
 	}
 	// Some sanity checks
Index: scribus/prefsstructs.h
===================================================================
--- scribus/prefsstructs.h	(Revision 26446)
+++ 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
+	OpticalMarginSets opticalMarginSets; //! OpticalMargin Group List
 
 	inline bool operator==(const TypoPrefs &other) const
 	{
Index: scribus/resourcecollection.h
===================================================================
--- scribus/resourcecollection.h	(Revision 26446)
+++ scribus/resourcecollection.h	(Arbeitskopie)
@@ -45,65 +45,70 @@
 	public:
 		ResourceCollection() {}
 
-		void collectFont(const QString& name)         { if (!name.isEmpty()) m_fonts.insert(name, name); }
-		void collectFontfeatures(const QString& name) { if (!name.isEmpty()) m_fontfeatures.insert(name, name); }
-		void collectPattern(const QString& name)      { if (!name.isEmpty()) m_patterns.insert(name, name); }
-		void collectColor(const QString& name)        { if (!name.isEmpty()) m_colors.insert(name, name); }
-		void collectGradient(const QString& name)     { if (!name.isEmpty()) m_gradients.insert(name, name); }
-		void collectStyle(const QString& name)        { if (!name.isEmpty()) m_pstyles.insert(name, name); }
-		void collectCharStyle(const QString& name)    { if (!name.isEmpty()) m_cstyles.insert(name, name); }
-		void collectLineStyle(const QString& name)    { if (!name.isEmpty()) m_linestyles.insert(name, name); }
-		void collectTableStyle(const QString& name)   { if (!name.isEmpty()) m_tableStyles.insert(name, name); }
-		void collectCellStyle(const QString& name)    { if (!name.isEmpty()) m_cellStyles.insert(name, name); }
-		void collectNoteStyle(const QString& name)    { if (!name.isEmpty()) m_noteStyles.insert(name, name); }
+		void collectFont(const QString& name)				{ if (!name.isEmpty()) m_fonts.insert(name, name); }
+		void collectFontfeatures(const QString& name)		{ if (!name.isEmpty()) m_fontfeatures.insert(name, name); }
+		void collectPattern(const QString& name)			{ if (!name.isEmpty()) m_patterns.insert(name, name); }
+		void collectColor(const QString& name)				{ if (!name.isEmpty()) m_colors.insert(name, name); }
+		void collectGradient(const QString& name)			{ if (!name.isEmpty()) m_gradients.insert(name, name); }
+		void collectStyle(const QString& name)				{ if (!name.isEmpty()) m_pstyles.insert(name, name); }
+		void collectCharStyle(const QString& name)			{ if (!name.isEmpty()) m_cstyles.insert(name, name); }
+		void collectLineStyle(const QString& name)			{ if (!name.isEmpty()) m_linestyles.insert(name, name); }
+		void collectTableStyle(const QString& name)			{ if (!name.isEmpty()) m_tableStyles.insert(name, name); }
+		void collectCellStyle(const QString& name)			{ if (!name.isEmpty()) m_cellStyles.insert(name, name); }
+		void collectNoteStyle(const QString& name)			{ if (!name.isEmpty()) m_noteStyles.insert(name, name); }
+		void collectOpticalMarginSet(const QString& name)   { if (!name.isEmpty()) m_opticalMarginSets.insert(name, name); }
 
-		void mapFont(const QString& oldname, const QString& newname)         { m_fonts.insert(oldname, newname); }
-		void mapPattern(const QString& oldname, const QString& newname)      { m_patterns.insert(oldname, newname); }
-		void mapFontfeatures(const QString& oldname, const QString& newname) { m_fontfeatures.insert(oldname, newname); }
-		void mapColor(const QString& oldname, const QString& newname)        { m_colors.insert(oldname, newname); }
-		void mapGradients(const QString& oldname, const QString& newname)    { m_gradients.insert(oldname, newname); }
-		void mapStyle(const QString& oldname, const QString& newname)        { m_pstyles.insert(oldname, newname); }
-		void mapCharStyle(const QString& oldname, const QString& newname)    { m_cstyles.insert(oldname, newname); }
-		void mapLineStyle(const QString& oldname, const QString& newname)    { m_linestyles.insert(oldname, newname); }
-		void mapTableStyle(const QString& oldname, const QString& newname)   { m_tableStyles.insert(oldname, newname); }
-		void mapCellStyle(const QString& oldname, const QString& newname)    { m_cellStyles.insert(oldname, newname); }
-		void mapNoteStyle(const QString& oldname, const QString& newname)    { m_noteStyles.insert(oldname, newname); }
+		void mapFont(const QString& oldname, const QString& newname)				{ m_fonts.insert(oldname, newname); }
+		void mapPattern(const QString& oldname, const QString& newname)				{ m_patterns.insert(oldname, newname); }
+		void mapFontfeatures(const QString& oldname, const QString& newname)		{ m_fontfeatures.insert(oldname, newname); }
+		void mapColor(const QString& oldname, const QString& newname)				{ m_colors.insert(oldname, newname); }
+		void mapGradients(const QString& oldname, const QString& newname)			{ m_gradients.insert(oldname, newname); }
+		void mapStyle(const QString& oldname, const QString& newname)				{ m_pstyles.insert(oldname, newname); }
+		void mapCharStyle(const QString& oldname, const QString& newname)			{ m_cstyles.insert(oldname, newname); }
+		void mapLineStyle(const QString& oldname, const QString& newname)			{ m_linestyles.insert(oldname, newname); }
+		void mapTableStyle(const QString& oldname, const QString& newname)			{ m_tableStyles.insert(oldname, newname); }
+		void mapCellStyle(const QString& oldname, const QString& newname)			{ m_cellStyles.insert(oldname, newname); }
+		void mapNoteStyle(const QString& oldname, const QString& newname)			{ m_noteStyles.insert(oldname, newname); }
+		void mapOpticalMarginSet(const QString& oldname, const QString& newname)	{ m_opticalMarginSets.insert(oldname, newname); }
 
-		void mapFonts(const QMap<QString, QString>& newnames)        { m_fonts = newnames; }
-		void mapPatterns(const QMap<QString, QString>& newnames)     { m_patterns = newnames; }
-		void mapFontfeatures(const QMap<QString, QString>& newnames) { m_fontfeatures = newnames; }
-		void mapColors(const QMap<QString, QString>& newnames)       { m_colors = newnames; }
-		void mapGradients(const QMap<QString, QString>& newnames)    { m_gradients = newnames; }
-		void mapStyles(const QMap<QString, QString>& newnames)       { m_pstyles = newnames; }
-		void mapCharStyles(const QMap<QString, QString>& newnames)   { m_cstyles = newnames; }
-		void mapLineStyles(const QMap<QString, QString>& newnames)   { m_linestyles = newnames; }
-		void mapTableStyles(const QMap<QString, QString>& newnames)  { m_tableStyles = newnames; }
-		void mapCellStyles(const QMap<QString, QString>& newnames)   { m_cellStyles = newnames; }
-		void mapNoteStyles(const QMap<QString, QString>& newnames)   { m_noteStyles = newnames; }
+		void mapFonts(const QMap<QString, QString>& newnames)				{ m_fonts = newnames; }
+		void mapPatterns(const QMap<QString, QString>& newnames)			{ m_patterns = newnames; }
+		void mapFontfeatures(const QMap<QString, QString>& newnames)		{ m_fontfeatures = newnames; }
+		void mapColors(const QMap<QString, QString>& newnames)				{ m_colors = newnames; }
+		void mapGradients(const QMap<QString, QString>& newnames)			{ m_gradients = newnames; }
+		void mapStyles(const QMap<QString, QString>& newnames)				{ m_pstyles = newnames; }
+		void mapCharStyles(const QMap<QString, QString>& newnames)			{ m_cstyles = newnames; }
+		void mapLineStyles(const QMap<QString, QString>& newnames)			{ m_linestyles = newnames; }
+		void mapTableStyles(const QMap<QString, QString>& newnames)			{ m_tableStyles = newnames; }
+		void mapCellStyles(const QMap<QString, QString>& newnames)			{ m_cellStyles = newnames; }
+		void mapNoteStyles(const QMap<QString, QString>& newnames)			{ m_noteStyles = newnames; }
+		void mapOpticalMarginSets(const QMap<QString, QString>& newnames)	{ m_opticalMarginSets = newnames; }
 
-		const QMap<QString, QString>& fonts() const        { return m_fonts; }
-		const QMap<QString, QString>& fontfeatures() const { return m_fontfeatures; }
-		const QMap<QString, QString>& patterns()  const    { return m_patterns; }
-		const QMap<QString, QString>& colors() const       { return m_colors; }
-		const QMap<QString, QString>& gradients() const    { return m_gradients; }
-		const QMap<QString, QString>& styles() const       { return m_pstyles; }
-		const QMap<QString, QString>& charStyles() const   { return m_cstyles; }
-		const QMap<QString, QString>& lineStyles() const   { return m_linestyles; }
-		const QMap<QString, QString>& tableStyles() const  { return m_tableStyles; }
-		const QMap<QString, QString>& cellStyles() const   { return m_cellStyles; }
-		const QMap<QString, QString>& noteStyles() const   { return m_noteStyles; }
+		const QMap<QString, QString>& fonts() const				{ return m_fonts; }
+		const QMap<QString, QString>& fontfeatures() const		{ return m_fontfeatures; }
+		const QMap<QString, QString>& patterns()  const			{ return m_patterns; }
+		const QMap<QString, QString>& colors() const			{ return m_colors; }
+		const QMap<QString, QString>& gradients() const			{ return m_gradients; }
+		const QMap<QString, QString>& styles() const			{ return m_pstyles; }
+		const QMap<QString, QString>& charStyles() const		{ return m_cstyles; }
+		const QMap<QString, QString>& lineStyles() const		{ return m_linestyles; }
+		const QMap<QString, QString>& tableStyles() const		{ return m_tableStyles; }
+		const QMap<QString, QString>& cellStyles() const		{ return m_cellStyles; }
+		const QMap<QString, QString>& noteStyles() const		{ return m_noteStyles; }
+		const QMap<QString, QString>& opticalMarginSets() const	{ return m_opticalMarginSets; }
 
-		QList<QString> fontNames() const         { return m_fonts.keys(); }
-		QList<QString> patternNames() const      { return m_patterns.keys(); }
-		QList<QString> fontfeaturesNames() const { return m_fontfeatures.keys(); }
-		QList<QString> colorNames() const        { return m_colors.keys(); }
-		QList<QString> gradientNames() const     { return m_gradients.keys(); }
-		QList<QString> styleNames() const        { return m_pstyles.keys(); }
-		QList<QString> charStyleNames() const    { return m_cstyles.keys(); }
-		QList<QString> lineStyleNames() const    { return m_linestyles.keys(); }
-		QList<QString> tableStyleNames() const   { return m_tableStyles.keys(); }
-		QList<QString> cellStyleNames() const    { return m_cellStyles.keys(); }
-		QList<QString> noteStyleNames() const    { return m_noteStyles.keys(); }
+		QList<QString> fontNames() const				{ return m_fonts.keys(); }
+		QList<QString> patternNames() const				{ return m_patterns.keys(); }
+		QList<QString> fontfeaturesNames() const		{ return m_fontfeatures.keys(); }
+		QList<QString> colorNames() const				{ return m_colors.keys(); }
+		QList<QString> gradientNames() const			{ return m_gradients.keys(); }
+		QList<QString> styleNames() const				{ return m_pstyles.keys(); }
+		QList<QString> charStyleNames() const			{ return m_cstyles.keys(); }
+		QList<QString> lineStyleNames() const			{ return m_linestyles.keys(); }
+		QList<QString> tableStyleNames() const			{ return m_tableStyles.keys(); }
+		QList<QString> cellStyleNames() const			{ return m_cellStyles.keys(); }
+		QList<QString> noteStyleNames() const			{ return m_noteStyles.keys(); }
+		QList<QString> opticalMarginSetNames() const	{ return m_opticalMarginSets.keys(); }
 
 		// modifies newNames so that forall x in both newNames.key() and in existingNames, newNames[x] will map to a new unique name
 		static void makeUnique(QMap<QString, QString>& newNames, const QList<QString> existingNames);
@@ -126,6 +131,7 @@
 		QMap<QString, QString> m_tableStyles;
 		QMap<QString, QString> m_cellStyles;
 		QMap<QString, QString> m_noteStyles;
+		QMap<QString, QString> m_opticalMarginSets;
 };
 
 #endif
Index: scribus/scribusdoc.cpp
===================================================================
--- scribus/scribusdoc.cpp	(Revision 26446)
+++ scribus/scribusdoc.cpp	(Arbeitskopie)
@@ -1227,7 +1227,7 @@
 	// replace names in styles...
 	for (int i = m_docParagraphStyles.count() - 1; i >= 0; --i)
 	{
-		if (newNames.styles().contains(m_docParagraphStyles[i].name()))
+		if (newNames.styles().contains(m_docParagraphStyles[i].name()) && !m_docParagraphStyles[i].isDefaultStyle())
 			m_docParagraphStyles.remove(i);
 		else
 			m_docParagraphStyles[i].replaceNamedResources(newNames);
@@ -1314,7 +1314,7 @@
 	}
 	if (!isLoading() && !(newNames.colors().isEmpty() && newNames.fonts().isEmpty() && newNames.patterns().isEmpty() 
 			&& newNames.styles().isEmpty() && newNames.charStyles().isEmpty() && newNames.lineStyles().isEmpty()
-			&& newNames.tableStyles().isEmpty() && newNames.cellStyles().isEmpty()))
+			&& newNames.tableStyles().isEmpty() && newNames.cellStyles().isEmpty() && newNames.opticalMarginSets().isEmpty()))
 		changed();
 }
 
@@ -9046,10 +9046,11 @@
 	itemSelection_ApplyCharStyle(newStyle, customSelection, "EFFECTS");
 }
 
-void ScribusDoc::itemSelection_SetOpticalMargins(int i, Selection* customSelection)
+void ScribusDoc::itemSelection_SetOpticalMargins(int i, const QString id, Selection* customSelection)
 {
 	ParagraphStyle newStyle;
-	newStyle.setOpticalMargins(i);
+	newStyle.setOpticalMarginSetId(id);
+	newStyle.setOpticalMargins(i);	
 	itemSelection_ApplyParagraphStyle(newStyle, customSelection);
 }
 
Index: scribus/scribusdoc.h
===================================================================
--- scribus/scribusdoc.h	(Revision 26446)
+++ 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,9 @@
 	int marginPreset() const { return m_docPrefsData.docSetupPrefs.marginPreset; }
 	void setMarginPreset(int mp) { m_docPrefsData.docSetupPrefs.marginPreset = mp; }
 
+	OpticalMarginSets opticalMarginSets() { return m_docPrefsData.typoPrefs.opticalMarginSets; }
+	void setOpticalMarginSets(OpticalMarginSets sets) { m_docPrefsData.typoPrefs.opticalMarginSets = sets; }
+
 	TypoPrefs& typographicPrefs() { return m_docPrefsData.typoPrefs; }
 	GuidesPrefs& guidesPrefs() { return m_docPrefsData.guidesPrefs; }
 	ItemToolPrefs& itemToolPrefs() { return m_docPrefsData.itemToolPrefs; }
@@ -1156,7 +1160,7 @@
 	void itemSelection_SetUnderline(int pos, int wid, Selection* customSelection = nullptr);
 	void itemSelection_SetStrikethru(int pos, int wid, Selection* customSelection = nullptr);
 	void itemSelection_SetEffects(int s, Selection* customSelection = nullptr);
-	void itemSelection_SetOpticalMargins(int i, Selection* customSelection = nullptr);
+	void itemSelection_SetOpticalMargins(int i, const QString id, Selection* customSelection = nullptr);
 	void itemSelection_SetColorProfile(const QString& profileName, Selection* customSelection = nullptr);
 	void itemSelection_SetRenderIntent(int intentIndex, Selection* customSelection = nullptr);
 	void itemSelection_SetCompressionMethod(int cmIndex, Selection* customSelection = nullptr);
Index: scribus/styles/paragraphstyle.attrdefs.cxx
===================================================================
--- scribus/styles/paragraphstyle.attrdefs.cxx	(Revision 26446)
+++ scribus/styles/paragraphstyle.attrdefs.cxx	(Arbeitskopie)
@@ -57,3 +57,4 @@
 ATTRDEF(QString, backgroundColor, BackgroundColor, "None")
 ATTRDEF(double, backgroundShade, BackgroundShade, 100)
 ATTRDEF(int, hyphenConsecutiveLines, HyphenConsecutiveLines, 2)
+ATTRDEF(QString, opticalMarginSetId, OpticalMarginSetId, OpticalMarginLookup::instance().defaultSetId())
Index: scribus/styles/paragraphstyle.cpp
===================================================================
--- scribus/styles/paragraphstyle.cpp	(Revision 26446)
+++ scribus/styles/paragraphstyle.cpp	(Arbeitskopie)
@@ -219,10 +219,14 @@
 	if (backgroundColorName != CommonStrings::None)
 		lists.collectColor(backgroundColorName);
 
+	const QString& opticalMarginSet = opticalMarginSetId();
+	if (!opticalMarginSet.isEmpty())
+		lists.collectOpticalMarginSet(opticalMarginSet);
+
 	QString parEffectStyle = peCharStyleName();
 	if (parEffectStyle.length() > 0)
 	{
-		const CharStyle *peCharStyle = dynamic_cast<const CharStyle*>(m_cstyleContext.resolve(parEffectStyle));
+		const CharStyle* peCharStyle = dynamic_cast<const CharStyle*>(m_cstyleContext.resolve(parEffectStyle));
 		if (peCharStyle)
 			peCharStyle->getNamedResources(lists);
 		lists.collectCharStyle(parEffectStyle);
@@ -243,6 +247,9 @@
 	if (!inh_BackgroundColor && (it = newNames.colors().find(backgroundColor())) != newNames.colors().end())
 		setBackgroundColor(it.value());
 
+	if (!inh_OpticalMarginSetId && (it = newNames.opticalMarginSets().find(opticalMarginSetId())) != newNames.opticalMarginSets().end())
+		setOpticalMarginSetId(it.value());
+
 	if ((it = (newNames.charStyles().find(peCharStyleName()))) != newNames.charStyles().end())
 		setPeCharStyleName(it.value());
 	m_cstyle.replaceNamedResources(newNames);
@@ -358,7 +365,6 @@
 	return Tablist();
 }
 
-
 using namespace desaxe;
 
 const Xml_string ParagraphStyle::saxxDefaultElem("style");
Index: scribus/styles/paragraphstyle.h
===================================================================
--- scribus/styles/paragraphstyle.h	(Revision 26446)
+++ scribus/styles/paragraphstyle.h	(Arbeitskopie)
@@ -18,6 +18,7 @@
 
 #include "style.h"
 #include "charstyle.h"
+#include "opticalmarginlookup.h"
 #include "styles/stylecontextproxy.h"
 
 
Index: scribus/ui/opticalmarginswidget.cpp
===================================================================
--- scribus/ui/opticalmarginswidget.cpp	(Revision 26446)
+++ scribus/ui/opticalmarginswidget.cpp	(Arbeitskopie)
@@ -22,6 +22,8 @@
 	parentButton = new QToolButton();
 	parentButton->hide(); // only used for style manager
 
+	setsCombo = new QComboBox();
+
 	QHBoxLayout* mainLayout = new QHBoxLayout();
 	mainLayout->setSpacing(4);
 	mainLayout->setContentsMargins(0, 0, 0, 0);
@@ -29,6 +31,7 @@
 	mainLayout->addWidget(leftButton);
 	mainLayout->addWidget(rightButton);
 	mainLayout->addSpacerItem(new QSpacerItem(4, 0, QSizePolicy::Fixed));
+	mainLayout->addWidget(setsCombo);
 	mainLayout->addWidget(parentButton);
 	mainLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
 
@@ -40,6 +43,7 @@
 	connect(ScQApp, SIGNAL(iconSetChanged()), this, SLOT(iconSetChange()));
 	connect(rightButton, SIGNAL(clicked()), this, SIGNAL(opticalMarginChanged()));
 	connect(leftButton, SIGNAL(clicked()), this, SIGNAL(opticalMarginChanged()));
+	connect(setsCombo, SIGNAL(currentIndexChanged(int)), this, SIGNAL(opticalMarginChanged()));
 }
 
 int OpticalMarginsWidget::opticalMargin()
@@ -86,6 +90,26 @@
 	blockSignals(blocked);
 }
 
+void OpticalMarginsWidget::setOpticalMarginSets(OpticalMarginSets sets)
+{
+	m_sets = sets;
+}
+
+void OpticalMarginsWidget::setOpticalMarginSetId(const QString id)
+{	
+	if (m_sets.size() <= 0)
+		return;
+
+	m_setId = m_sets.contains(id) ? id : m_sets.firstKey();
+
+	updateSetSelector();
+}
+
+const QString OpticalMarginsWidget::opticalMarginSetId()
+{
+	return setsCombo->currentData().toString();
+}
+
 void OpticalMarginsWidget::iconSetChange()
 {
 	IconManager &im = IconManager::instance();
@@ -103,9 +127,25 @@
 	QWidget::changeEvent(e);
 }
 
+void OpticalMarginsWidget::updateSetSelector()
+{
+	if (m_sets.size() <= 0)
+		return;
+
+	QSignalBlocker blockSets(setsCombo);
+	setsCombo->setVisible( m_sets.size() > 1 ? true : false );
+	setsCombo->clear();
+
+	for (auto [id, set] : m_sets.asKeyValueRange())
+		setsCombo->addItem(set.name, id);
+
+	int index = setsCombo->findData(m_setId);
+	setsCombo->setCurrentIndex(index);
+}
+
 void OpticalMarginsWidget::languageChange()
 {
-	setText(tr("Optical &Margins"));
+	setText( tr("Optical &Margins") );
 
 	leftButton->setToolTip( tr("Optical character offset on left side"));
 	rightButton->setToolTip( tr("Optical character offset on right side"));
Index: scribus/ui/opticalmarginswidget.h
===================================================================
--- scribus/ui/opticalmarginswidget.h	(Revision 26446)
+++ scribus/ui/opticalmarginswidget.h	(Arbeitskopie)
@@ -7,7 +7,9 @@
 #ifndef OPTICALMARGINSWIDGET_H
 #define OPTICALMARGINSWIDGET_H
 
+#include <QComboBox>
 #include <QToolButton>
+#include "opticalmarginlookup.h"
 #include "ui/widgets/form_widget.h"
 
 class ParagraphStyle;
@@ -25,13 +27,23 @@
 	QToolButton *leftButton = {nullptr};
 	QToolButton *rightButton = {nullptr};
 	QToolButton *parentButton = {nullptr};
+	QComboBox *setsCombo = {nullptr};
 
 	int opticalMargin();
 	void setOpticalMargin(int omt);
 
+	void setOpticalMarginSets(OpticalMarginSets sets);
+	OpticalMarginSets opticalMarginSets() { return m_sets; };
+
+	void setOpticalMarginSetId(const QString id);
+	const QString opticalMarginSetId();
+
 protected:
 
 	void changeEvent(QEvent *e) override;
+	void updateSetSelector();
+	OpticalMarginSets m_sets;
+	QString m_setId;
 
 public slots:
 
Index: scribus/ui/prefs_typography.cpp
===================================================================
--- scribus/ui/prefs_typography.cpp	(Revision 26446)
+++ scribus/ui/prefs_typography.cpp	(Arbeitskopie)
@@ -7,10 +7,14 @@
 
 #include "prefs_typography.h"
 #include "prefsstructs.h"
+#include "scribus.h"
 #include "scribusdoc.h"
+#include "stylemanager.h"
+#include "ui/smtextstyles.h"
 
-Prefs_Typography::Prefs_Typography(QWidget* parent, ScribusDoc* /*doc*/)
-	: Prefs_Pane(parent)
+Prefs_Typography::Prefs_Typography(QWidget* parent, ScribusDoc* doc)
+	: Prefs_Pane(parent),
+	  m_Doc(doc)
 {
 	setupUi(this);
 	languageChange();
@@ -17,6 +21,8 @@
 
 	m_caption = tr("Typography");
 	m_icon = "16/draw-text.png";
+
+	connect(opticalMarginsEditor, &OpticalMarginEditor::setDeleted, this, &Prefs_Typography::updateParagraphStyles);
 }
 
 Prefs_Typography::~Prefs_Typography() = default;
@@ -41,6 +47,21 @@
 	automaticLineSpacingSpinBox->setToolTip( tr( "Percentage increase over the font size for the line spacing" ) );
 }
 
+void Prefs_Typography::updateParagraphStyles(QString removedID, QString newID)
+{
+	if (!m_Doc)
+		return;
+
+	ResourceCollection res;
+	m_Doc->getNamedResources(res);
+	res.mapOpticalMarginSet(removedID, newID);
+	m_Doc->replaceNamedResources(res);
+
+	// We also need to update the Style Manager to load the updated paragraph styles.
+	// updateColorList() does what we want.
+	m_Doc->scMW()->styleMgr()->updateColorList();
+}
+
 void Prefs_Typography::restoreDefaults(struct ApplicationPrefs *prefsData)
 {
 	subscriptDisplacementSpinBox->setValue(prefsData->typoPrefs.valueSubScript);
@@ -53,6 +74,7 @@
 	strikeoutLineWidthSpinBox->setValue(prefsData->typoPrefs.valueStrikeThruWidth / 10.0);
 	smallcapsScalingSpinBox->setValue(prefsData->typoPrefs.valueSmallCaps);
 	automaticLineSpacingSpinBox->setValue(prefsData->typoPrefs.autoLineSpacing);
+	opticalMarginsEditor->setOpticalMarginSets(prefsData->typoPrefs.opticalMarginSets);
 }
 
 void Prefs_Typography::saveGuiToPrefs(struct ApplicationPrefs *prefsData) const
@@ -67,5 +89,6 @@
 	prefsData->typoPrefs.valueStrikeThruWidth = strikeoutLineWidthSpinBox->value() * 10.0;
 	prefsData->typoPrefs.valueSmallCaps = smallcapsScalingSpinBox->value();
 	prefsData->typoPrefs.autoLineSpacing = automaticLineSpacingSpinBox->value();
+	prefsData->typoPrefs.opticalMarginSets = opticalMarginsEditor->opticalMarginSets();
 }
 
Index: scribus/ui/prefs_typography.h
===================================================================
--- scribus/ui/prefs_typography.h	(Revision 26446)
+++ scribus/ui/prefs_typography.h	(Arbeitskopie)
@@ -25,8 +25,15 @@
 		void restoreDefaults(struct ApplicationPrefs *prefsData) override;
 		void saveGuiToPrefs(struct ApplicationPrefs *prefsData) const override;
 
+	protected:
+		ScribusDoc* m_Doc {nullptr};
+
 	public slots:
 		void languageChange();
+
+	private slots:
+		void updateParagraphStyles(QString removedID, QString newID);
+
 };
 
 #endif // PREFS_TYPOGRAPHY_H
Index: scribus/ui/propertywidget_alignment.cpp
===================================================================
--- scribus/ui/propertywidget_alignment.cpp	(Revision 26446)
+++ scribus/ui/propertywidget_alignment.cpp	(Arbeitskopie)
@@ -44,7 +44,7 @@
 
 	m_haveItem = tmp;
 
-	showOpticalMargins(newCurrent.opticalMargins());
+	showOpticalMargins(newCurrent.opticalMargins(), newCurrent.opticalMarginSetId());
 }
 
 void PropertyWidget_Alignment::setMainWindow(ScribusMainWindow* mw)
@@ -150,7 +150,6 @@
 	connect(tabsButton    , SIGNAL(clicked())           , this, SLOT(handleTabs()), Qt::UniqueConnection);
 	connect(flopWidget->flopGroup, SIGNAL(idClicked(int)), this, SLOT(handleFirstLinePolicy(int)));
 	connect(opticalMarginsWidget, SIGNAL(opticalMarginChanged()), this, SLOT(handleOpticalMargins()) );
-//	connect(opticalMarginsWidget->resetButton, SIGNAL(clicked()), this, SLOT(resetOpticalMargins()) );
 	connect(textAlignment , SIGNAL(State(int))   , this, SLOT(handleAlignment(int)));
 	connect(textVerticalAlignment, SIGNAL(State(int)), this, SLOT(handleVAlignment(int)));
 	connect(textDirection , SIGNAL(State(int))   , this, SLOT(handleDirection(int)));
@@ -161,7 +160,6 @@
 	disconnect(tabsButton    , SIGNAL(clicked())           , this, SLOT(handleTabs()));
 	disconnect(flopWidget->flopGroup, SIGNAL(idClicked(int)), this, SLOT(handleFirstLinePolicy(int)));
 	disconnect(opticalMarginsWidget, SIGNAL(opticalMarginChanged()), this, SLOT(handleOpticalMargins()) );
-//	disconnect(opticalMarginsWidget->resetButton, SIGNAL(clicked()), this, SLOT(resetOpticalMargins()) );
 	disconnect(textAlignment , SIGNAL(State(int))   , this, SLOT(handleAlignment(int)));
 	disconnect(textVerticalAlignment, SIGNAL(State(int)), this, SLOT(handleVAlignment(int)));
 	disconnect(textDirection , SIGNAL(State(int))   , this, SLOT(handleDirection(int)));
@@ -254,7 +252,7 @@
 		flopWidget->flopBaselineGrid->setChecked(true);
 }
 
-void PropertyWidget_Alignment::showOpticalMargins(int e)
+void PropertyWidget_Alignment::showOpticalMargins(int e, const QString &setId)
 {
 	if (!m_ScMW || m_ScMW->scriptIsRunning())
 		return;
@@ -261,6 +259,8 @@
 	bool tmp = m_haveItem;
 	m_haveItem = false;
 	opticalMarginsWidget->setOpticalMargin(e);
+	opticalMarginsWidget->setOpticalMarginSets(m_doc->opticalMarginSets());
+	opticalMarginsWidget->setOpticalMarginSetId(setId);
 	m_haveItem = tmp;
 }
 
@@ -389,8 +389,6 @@
 	if (!m_doc || !m_item || !m_ScMW || m_ScMW->scriptIsRunning())
 		return;
 
-	int omt(opticalMarginsWidget->opticalMargin());
-
 	PageItem *item = m_item;
 	if (m_doc->appMode == modeEditTable)
 		item = m_item->asTable()->activeCell().textFrame();
@@ -398,25 +396,10 @@
 	{
 		Selection tempSelection(this, false);
 		tempSelection.addItem(item, true);
-		m_doc->itemSelection_SetOpticalMargins(omt, &tempSelection);
+		m_doc->itemSelection_SetOpticalMargins(opticalMarginsWidget->opticalMargin(), opticalMarginsWidget->opticalMarginSetId(), &tempSelection);
 	}
 }
 
-// void PropertyWidget_Alignment::resetOpticalMargins()
-// {
-// 	// if (!m_doc || !m_item || !m_ScMW || m_ScMW->scriptIsRunning())
-// 	// 	return;
-// 	// PageItem *item = m_item;
-// 	// if (m_doc->appMode == modeEditTable)
-// 	// 	item = m_item->asTable()->activeCell().textFrame();
-// 	// if (item != nullptr)
-// 	// {
-// 	// 	Selection tempSelection(this, false);
-// 	// 	tempSelection.addItem(item, true);
-// 	// 	m_doc->itemSelection_resetOpticalMargins(&tempSelection);
-// 	// }
-// }
-
 void PropertyWidget_Alignment::changeEvent(QEvent *e)
 {
 	if (e->type() == QEvent::LanguageChange)
Index: scribus/ui/propertywidget_alignment.h
===================================================================
--- scribus/ui/propertywidget_alignment.h	(Revision 26446)
+++ scribus/ui/propertywidget_alignment.h	(Arbeitskopie)
@@ -53,7 +53,7 @@
 	void toggleLabelVisibility(bool v);
 
 	void showFirstLinePolicy(FirstLineOffsetPolicy f);
-	void showOpticalMargins(int e);
+	void showOpticalMargins(int e, const QString &setId);
 	void showAlignment(int e);
 	void showDirection(int e);
 
@@ -62,8 +62,6 @@
 	void handleTabs();
 	void handleFirstLinePolicy(int);
 	void handleOpticalMargins();
-//	void resetOpticalMargins();
-
 	void handleAlignment(int a);
 	void handleVAlignment(int a);
 	void handleDirection(int d);
Index: scribus/ui/smopticalmarginswidget.cpp
===================================================================
--- scribus/ui/smopticalmarginswidget.cpp	(Revision 26446)
+++ scribus/ui/smopticalmarginswidget.cpp	(Arbeitskopie)
@@ -54,12 +54,50 @@
 	connect(parentButton, SIGNAL(pressed()), this, SLOT(pbPressed()));
 }
 
+void SMOpticalMarginsWidget::setOpticalMarginSetId(const QString o)
+{
+	disconnect(parentButton, SIGNAL(pressed()), this, SLOT(pbPressed()));
+	disconnect(this, SIGNAL(opticalMarginChanged()), this, SLOT(styleChanged()));
+
+	m_hasParent = false;
+	parentButton->hide();
+	m_pSetId = "";
+
+	OpticalMarginsWidget::setOpticalMarginSetId(o);
+
+	connect(this, SIGNAL(opticalMarginChanged()), this, SLOT(styleChanged()));
+	connect(parentButton, SIGNAL(pressed()), this, SLOT(pbPressed()));
+}
+
+void SMOpticalMarginsWidget::setOpticalMarginSetId(const QString o, const QString ps, bool isParentValue)
+{
+	disconnect(parentButton, SIGNAL(pressed()), this, SLOT(pbPressed()));
+	disconnect(this, SIGNAL(opticalMarginChanged()), this, SLOT(styleChanged()));
+
+	if (!ps.isEmpty())
+	{
+		m_hasParent = true;
+		parentButton->setVisible(!isParentValue);
+		m_pSetId = ps;
+	}
+	else
+		m_hasParent = false;
+
+	OpticalMarginsWidget::setOpticalMarginSetId(o);
+
+	connect(this, SIGNAL(opticalMarginChanged()), this, SLOT(styleChanged()));
+	connect(parentButton, SIGNAL(pressed()), this, SLOT(pbPressed()));
+}
+
 bool SMOpticalMarginsWidget::useParentValue()
 {
 	bool ret = m_useParentStyle;
 	m_useParentStyle = false;
 	if (ret)
+	{
 		setOpticalMargins(m_pStyle, m_pStyle, true);
+		setOpticalMarginSetId(m_pSetId, m_pSetId, true);
+	}
 
 	return ret;
 }
Index: scribus/ui/smopticalmarginswidget.h
===================================================================
--- scribus/ui/smopticalmarginswidget.h	(Revision 26446)
+++ scribus/ui/smopticalmarginswidget.h	(Arbeitskopie)
@@ -20,6 +20,9 @@
 	void setOpticalMargins(int o);
 	void setOpticalMargins(int o, int ps, bool isParentValue);
 
+	void setOpticalMarginSetId(const QString o);
+	void setOpticalMarginSetId(const QString o, const QString ps, bool isParentValue);
+
 	bool useParentValue();
 
 private:
@@ -26,6 +29,7 @@
 	bool m_hasParent = false;
 	bool m_useParentStyle = false;
 	int m_pStyle = -1;
+	QString m_pSetId;
 
 private slots:
 	void styleChanged();
Index: scribus/ui/smpstylewidget.cpp
===================================================================
--- scribus/ui/smpstylewidget.cpp	(Revision 26446)
+++ scribus/ui/smpstylewidget.cpp	(Arbeitskopie)
@@ -224,6 +224,8 @@
 	//fillNumerationsCombo();
 	//fillNumRestartCombo();
 
+	optMarginWidget->setOpticalMarginSets(m_Doc->opticalMarginSets());
+
 	if (m_hasParent)
 	{
 		lineSpacingMode->setCurrentItem(pstyle->lineSpacingMode(), pstyle->isInhLineSpacingMode());
@@ -231,7 +233,8 @@
 		
 //		optMarginCombo->setCurrentItemByData( pstyle->opticalMargins(),  pstyle->isInhOpticalMargins() );
 //		optMarginCombo->setParentItem(optMarginCombo->getItemIndexForData( parent->opticalMargins()));
-		optMarginWidget->setOpticalMargins(pstyle->opticalMargins(), parent->opticalMargins(), pstyle->isInhOpticalMargins());
+		optMarginWidget->setOpticalMargins(pstyle->opticalMargins(), parent->opticalMargins(), pstyle->isInhOpticalMargins());		
+		optMarginWidget->setOpticalMarginSetId(pstyle->opticalMarginSetId(), parent->opticalMarginSetId(), pstyle->isInhOpticalMarginSetId());
 		
 		minSpaceSpin->setValue(pstyle->minWordTracking() * 100.0,  pstyle->isInhMinWordTracking());
 		minSpaceSpin->setParentValue(parent->minWordTracking());
@@ -350,7 +353,8 @@
 		spaceAbove->setValue(pstyle->gapBefore());
 		spaceBelow->setValue(pstyle->gapAfter());
 //		optMarginCombo->setCurrentItemByData( pstyle->opticalMargins() );
-		optMarginWidget->setOpticalMargins(pstyle->opticalMargins());
+		optMarginWidget->setOpticalMargins(pstyle->opticalMargins());
+		optMarginWidget->setOpticalMarginSetId(pstyle->opticalMarginSetId());
 		minSpaceSpin->setValue(pstyle->minWordTracking() * 100.0);
 		minGlyphExtSpin->setValue(pstyle->minGlyphExtension() * 100.0);
 		maxGlyphExtSpin->setValue(pstyle->maxGlyphExtension() * 100.0);
@@ -658,7 +662,9 @@
 //		}
 //	}
 //	optMarginCombo->setCurrentItemByData(o);
-	optMarginWidget->setOpticalMargins(pstyles[0]->opticalMargins());
+	optMarginWidget->setOpticalMargins(pstyles[0]->opticalMargins());
+	optMarginWidget->setOpticalMarginSets(m_Doc->opticalMarginSets());
+	optMarginWidget->setOpticalMarginSetId(pstyles[0]->opticalMarginSetId());
 }
 
 void SMPStyleWidget::showMinSpace(const QList< ParagraphStyle * > & pstyles)
@@ -870,21 +876,21 @@
 	bulletStrEdit->lineEdit()->setText(chars);
 }
 
-void SMPStyleWidget::slotParentParEffects()
+void SMPStyleWidget::slotParentParEffects()
 {
-	parentParEffectsButton->hide();
+	parentParEffectsButton->hide();
 
-	if (m_parentDC)
-		setParagraphEffect(1);
-	else if (m_parentBul)
-		setParagraphEffect(2);
-	else if (m_parentNum)
-		setParagraphEffect(3);
+	if (m_parentDC)
+		setParagraphEffect(1);
+	else if (m_parentBul)
+		setParagraphEffect(2);
+	else if (m_parentNum)
+		setParagraphEffect(3);
 	else
-		setParagraphEffect(0);
+		setParagraphEffect(0);
 
 	emit useParentParaEffects();
-
+
 }
 
 void SMPStyleWidget::openEnhanced()
@@ -938,178 +944,178 @@
 	if (updateFlags & reqNumUpdate)
 		fillNumerationsCombo();
 }
-
-void SMPStyleWidget::fillBulletStrEditCombo()
-{
-	bulletStrEdit->clear();
-	bulletStrEdit->addItem(QChar(0x2022));
-	bulletStrEdit->addItem("*");
-	bulletStrEdit->addItem(QChar(0x2013));
-	bulletStrEdit->setMinimumWidth(50);
-	if (bulletStrEdit->currentText().isEmpty())
-		bulletStrEdit->setEditText(QChar(0x2022));
-}
-
-void SMPStyleWidget::fillNumerationsCombo()
-{
-	QStringList numNames;
-	const auto numerationKeys = m_Doc->numerations.keys();
-	for (const QString& numName : numerationKeys)
-	{
-		if (numName.isEmpty())
-			continue;
-		numNames.append(numName);
-	}
-	numNames.sort();
-	numComboBox->clear();
-	numComboBox->insertItems(0, numNames);
-	numComboBox->setCurrentItem(0);
-}
-
-void SMPStyleWidget::fillNumRestartCombo()
-{
-	numRestartCombo->clear();
-	numRestartCombo->addItem(tr("Document"), static_cast<int>(NSRdocument));
-	numRestartCombo->addItem(tr("Story") , static_cast<int>(NSRstory));
-}
-
-void SMPStyleWidget::fillPECombo()
-{
-	QSignalBlocker sb(peCombo);
-	int currIndex = peCombo->currentIndex();
-	peCombo->clear();
-	peCombo->addItem(tr("No Paragraph Effects"), -1);
-	peCombo->addItem(tr("Drop Caps"), 0);
-	peCombo->addItem(tr("Bulleted List"), 1);
-	peCombo->addItem(tr("Numbered List"), 2);
-	peCombo->setCurrentIndex(currIndex);
-}
-
-void SMPStyleWidget::setParagraphEffect(int index)
-{
-	QSignalBlocker sigPECombo(peCombo);
-
-	peCombo->setCurrentIndex(index);
-	int id = peCombo->currentIndex();
-
-	if (id == 1 || id == 2 || id == 3)
-	{
-		stackedWidget->setCurrentIndex(id - 1);
-		stackedWidget->setVisible(true);
-		peGroup->setVisible(true);
-
-		if (id == 2) // Bullet List
-		{
-			if (bulletStrEdit->currentText().isEmpty())
-				bulletStrEdit->setEditText(bulletStrEdit->itemText(0));
-		}
-		else if (id == 3) // Numbered List
-		{
-			if (numComboBox->currentIndex() < 0)
-				numComboBox->setCurrentIndex(0);
-		}
-	}
-	else // No effect
-	{
-		stackedWidget->setVisible(false);
-		peGroup->setVisible(false);
-		peCombo->setCurrentIndex(0);
-	}
-}
-
-void SMPStyleWidget::showDropCap(const QList<ParagraphStyle*> &pstyles, const QList<CharStyle> &cstyles, int unitIndex)
-{
-	bool dc = pstyles[0]->hasDropCap();
-	for (int i = 0; i < pstyles.count(); ++i)
-	{
-		if (dc != pstyles[i]->hasDropCap())
-		{
-			dc = false;
-			break;
-		}
-	}
-
-	if (dc)
-		setParagraphEffect(1);
-
-	int lines = -1;
-	for (int i = 0; i < pstyles.count(); ++i)
-	{
-		if (lines > -1 && pstyles[i]->dropCapLines() != lines)
-		{
-			lines = -1;
-			break;
-		}
-		lines = pstyles[i]->dropCapLines();
-	}
-	if (lines == -1)
-		dropCapLines->clear();
-	else
-		dropCapLines->setValue(lines);
-}
-
-void SMPStyleWidget::showBullet(const QList<ParagraphStyle *> &pstyles, const QList<CharStyle> &cstyles, int unitIndex)
-{
-//	double unitRatio = unitGetRatioFromIndex(unitIndex);
-
-	bool hb = pstyles[0]->hasBullet();
-	for (int i = 0; i < pstyles.count(); ++i)
-	{
-		if (hb != pstyles[i]->hasBullet())
-		{
-			hb = false;
-			break;
-		}
-	}
-
-	if (hb)
-		setParagraphEffect(2);
-
-	QString chStr = pstyles[0]->bulletStr();
-	for (int i = 0; i < pstyles.count(); ++i)
-	{
-		if (chStr != pstyles[i]->bulletStr())
-		{
-			chStr.clear();
-			break;
-		}
-		chStr = pstyles[i]->bulletStr();
-	}
-	bulletStrEdit->setEditText(chStr);
-}
-
-void SMPStyleWidget::showNumeration(const QList<ParagraphStyle *> &pstyles, const QList<CharStyle> &cstyles, int unitIndex)
-{
-
-	QString prefix = pstyles[0]->numPrefix();
-	for (int i = 0; i < pstyles.count(); ++i)
-	{
-		if (prefix != pstyles[i]->numPrefix())
-		{
-			prefix.clear();
-			break;
-		}
-		prefix = pstyles[i]->numPrefix();
-	}
-	numPrefix->setText(prefix);
-
-	QString suffix = pstyles[0]->numSuffix();
-	for (int i = 0; i < pstyles.count(); ++i)
-	{
-		if (suffix != pstyles[i]->numSuffix())
-		{
-			suffix.clear();
-			break;
-		}
-		suffix = pstyles[i]->numSuffix();
-	}
-	numSuffix->setText(suffix);
-}
-
-void SMPStyleWidget::handleParEffectUse(int index)
-{
-	if (m_hasParent)
-		parentParEffectsButton->show();
-
-	setParagraphEffect(index);
-}
+
+void SMPStyleWidget::fillBulletStrEditCombo()
+{
+	bulletStrEdit->clear();
+	bulletStrEdit->addItem(QChar(0x2022));
+	bulletStrEdit->addItem("*");
+	bulletStrEdit->addItem(QChar(0x2013));
+	bulletStrEdit->setMinimumWidth(50);
+	if (bulletStrEdit->currentText().isEmpty())
+		bulletStrEdit->setEditText(QChar(0x2022));
+}
+
+void SMPStyleWidget::fillNumerationsCombo()
+{
+	QStringList numNames;
+	const auto numerationKeys = m_Doc->numerations.keys();
+	for (const QString& numName : numerationKeys)
+	{
+		if (numName.isEmpty())
+			continue;
+		numNames.append(numName);
+	}
+	numNames.sort();
+	numComboBox->clear();
+	numComboBox->insertItems(0, numNames);
+	numComboBox->setCurrentItem(0);
+}
+
+void SMPStyleWidget::fillNumRestartCombo()
+{
+	numRestartCombo->clear();
+	numRestartCombo->addItem(tr("Document"), static_cast<int>(NSRdocument));
+	numRestartCombo->addItem(tr("Story") , static_cast<int>(NSRstory));
+}
+
+void SMPStyleWidget::fillPECombo()
+{
+	QSignalBlocker sb(peCombo);
+	int currIndex = peCombo->currentIndex();
+	peCombo->clear();
+	peCombo->addItem(tr("No Paragraph Effects"), -1);
+	peCombo->addItem(tr("Drop Caps"), 0);
+	peCombo->addItem(tr("Bulleted List"), 1);
+	peCombo->addItem(tr("Numbered List"), 2);
+	peCombo->setCurrentIndex(currIndex);
+}
+
+void SMPStyleWidget::setParagraphEffect(int index)
+{
+	QSignalBlocker sigPECombo(peCombo);
+
+	peCombo->setCurrentIndex(index);
+	int id = peCombo->currentIndex();
+
+	if (id == 1 || id == 2 || id == 3)
+	{
+		stackedWidget->setCurrentIndex(id - 1);
+		stackedWidget->setVisible(true);
+		peGroup->setVisible(true);
+
+		if (id == 2) // Bullet List
+		{
+			if (bulletStrEdit->currentText().isEmpty())
+				bulletStrEdit->setEditText(bulletStrEdit->itemText(0));
+		}
+		else if (id == 3) // Numbered List
+		{
+			if (numComboBox->currentIndex() < 0)
+				numComboBox->setCurrentIndex(0);
+		}
+	}
+	else // No effect
+	{
+		stackedWidget->setVisible(false);
+		peGroup->setVisible(false);
+		peCombo->setCurrentIndex(0);
+	}
+}
+
+void SMPStyleWidget::showDropCap(const QList<ParagraphStyle*> &pstyles, const QList<CharStyle> &cstyles, int unitIndex)
+{
+	bool dc = pstyles[0]->hasDropCap();
+	for (int i = 0; i < pstyles.count(); ++i)
+	{
+		if (dc != pstyles[i]->hasDropCap())
+		{
+			dc = false;
+			break;
+		}
+	}
+
+	if (dc)
+		setParagraphEffect(1);
+
+	int lines = -1;
+	for (int i = 0; i < pstyles.count(); ++i)
+	{
+		if (lines > -1 && pstyles[i]->dropCapLines() != lines)
+		{
+			lines = -1;
+			break;
+		}
+		lines = pstyles[i]->dropCapLines();
+	}
+	if (lines == -1)
+		dropCapLines->clear();
+	else
+		dropCapLines->setValue(lines);
+}
+
+void SMPStyleWidget::showBullet(const QList<ParagraphStyle *> &pstyles, const QList<CharStyle> &cstyles, int unitIndex)
+{
+//	double unitRatio = unitGetRatioFromIndex(unitIndex);
+
+	bool hb = pstyles[0]->hasBullet();
+	for (int i = 0; i < pstyles.count(); ++i)
+	{
+		if (hb != pstyles[i]->hasBullet())
+		{
+			hb = false;
+			break;
+		}
+	}
+
+	if (hb)
+		setParagraphEffect(2);
+
+	QString chStr = pstyles[0]->bulletStr();
+	for (int i = 0; i < pstyles.count(); ++i)
+	{
+		if (chStr != pstyles[i]->bulletStr())
+		{
+			chStr.clear();
+			break;
+		}
+		chStr = pstyles[i]->bulletStr();
+	}
+	bulletStrEdit->setEditText(chStr);
+}
+
+void SMPStyleWidget::showNumeration(const QList<ParagraphStyle *> &pstyles, const QList<CharStyle> &cstyles, int unitIndex)
+{
+
+	QString prefix = pstyles[0]->numPrefix();
+	for (int i = 0; i < pstyles.count(); ++i)
+	{
+		if (prefix != pstyles[i]->numPrefix())
+		{
+			prefix.clear();
+			break;
+		}
+		prefix = pstyles[i]->numPrefix();
+	}
+	numPrefix->setText(prefix);
+
+	QString suffix = pstyles[0]->numSuffix();
+	for (int i = 0; i < pstyles.count(); ++i)
+	{
+		if (suffix != pstyles[i]->numSuffix())
+		{
+			suffix.clear();
+			break;
+		}
+		suffix = pstyles[i]->numSuffix();
+	}
+	numSuffix->setText(suffix);
+}
+
+void SMPStyleWidget::handleParEffectUse(int index)
+{
+	if (m_hasParent)
+		parentParEffectsButton->show();
+
+	setParagraphEffect(index);
+}
Index: scribus/ui/smtextstyles.cpp
===================================================================
--- scribus/ui/smtextstyles.cpp	(Revision 26446)
+++ scribus/ui/smtextstyles.cpp	(Arbeitskopie)
@@ -230,6 +230,7 @@
 	ParagraphStyle p;
 	p.setDefaultStyle(false);
 	p.setName(s);
+	p.setOpticalMarginSetId(m_doc->opticalMarginSets().firstKey());
 	p.charStyle().setLanguage(m_doc->language());
 	m_tmpStyles.create(p);
 	return s;
@@ -508,14 +509,11 @@
 
 	connect(m_pwidget, SIGNAL(useParentParaEffects()), this, SLOT(slotParentParaEffects()));
 	connect(m_pwidget->peCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPargraphEffects(int)));
-//	connect(m_pwidget->dropCapsBox, SIGNAL(toggled(bool)), this, SLOT(slotDropCap(bool)));
 	connect(m_pwidget->dropCapLines, SIGNAL(valueChanged(int)), this, SLOT(slotDropCapLines(int)));
 	connect(m_pwidget->parEffectOffset, SIGNAL(valueChanged(double)), this, SLOT(slotParEffectOffset()));
 	connect(m_pwidget->parEffectIndentBox, SIGNAL(toggled(bool)), this, SLOT(slotParEffectIndent(bool)));
 	connect(m_pwidget->parEffectCharStyleCombo, SIGNAL(activated(int)), this, SLOT(slotParEffectCharStyle(int)));
-//	connect(m_pwidget->bulletBox, SIGNAL(toggled(bool)), this, SLOT(slotBullet(bool)));
 	connect(m_pwidget->bulletStrEdit, SIGNAL(editTextChanged(QString)), this, SLOT(slotBulletStr(QString)));
-//	connect(m_pwidget->numBox, SIGNAL(toggled(bool)), this, SLOT(slotNumeration(bool)));
 	connect(m_pwidget->numComboBox, SIGNAL(textActivated(QString)), this, SLOT(slotNumName(QString)));
 	connect(m_pwidget->numLevelSpin, SIGNAL(valueChanged(int)), this, SLOT(slotNumLevel(int)));
 	connect(m_pwidget->numFormatCombo, SIGNAL(activated(int)), this, SLOT(slotNumFormat(int)));
@@ -553,10 +551,6 @@
 	connect(m_pwidget->cpage->effects_->UnderlineVal->LWidth, SIGNAL(valueChanged(double)), this, SLOT(slotEffectProperties()));
 	connect(m_pwidget->cpage->effects_->StrikeVal->LPos, SIGNAL(valueChanged(double)), this, SLOT(slotEffectProperties()));
 	connect(m_pwidget->cpage->effects_->StrikeVal->LWidth, SIGNAL(valueChanged(double)), this, SLOT(slotEffectProperties()));
-	// connect(m_pwidget->cpage->fillColor_, SIGNAL(currentTextChanged(QString)), this, SLOT(slotFillColor()));
-	// connect(m_pwidget->cpage->fillShade_, SIGNAL(clicked()), this, SLOT(slotFillShade()));
-	// connect(m_pwidget->cpage->strokeColor_, SIGNAL(currentTextChanged(QString)), this, SLOT(slotStrokeColor()));
-	// connect(m_pwidget->cpage->strokeShade_, SIGNAL(clicked()), this, SLOT(slotStrokeShade()));
 	connect(m_pwidget->cpage->language_, SIGNAL(activated(int)), this, SLOT(slotLanguage()));
 	connect(m_pwidget->cpage->fontSize_, SIGNAL(valueChanged(double)), this, SLOT(slotFontSize()));
 	connect(m_pwidget->cpage->fontHScale_, SIGNAL(valueChanged(double)), this, SLOT(slotScaleH()));
@@ -565,8 +559,6 @@
 	connect(m_pwidget->cpage->widthSpaceSpin, SIGNAL(valueChanged(double)), this, SLOT(slotWordTracking()));
 	connect(m_pwidget->cpage->baselineOffset_, SIGNAL(valueChanged(double)), this, SLOT(slotBaselineOffset()));
 	connect(m_pwidget->cpage->parentCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(slotCharParentChanged(QString)));
-	// connect(m_pwidget->cpage->backColor_, SIGNAL(currentTextChanged(QString)), this, SLOT(slotBackColor()));
-	// connect(m_pwidget->cpage->backShade_, SIGNAL(clicked()), this, SLOT(slotBackShade()));
 
 	connect(m_pwidget->cpage->textColor->colorButton, SIGNAL(colorChanged()), this, SLOT(slotFillColor()));
 	connect(m_pwidget->cpage->textColor->parentButton, SIGNAL(clicked()), this, SLOT(slotFillColor()));
@@ -612,14 +604,11 @@
 
 	disconnect(m_pwidget, SIGNAL(useParentParaEffects()), this, SLOT(slotParentParaEffects()));
 	disconnect(m_pwidget->peCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPargraphEffects(int)));
-//	disconnect(m_pwidget->dropCapsBox, SIGNAL(toggled(bool)), this, SLOT(slotDropCap(bool)));
 	disconnect(m_pwidget->dropCapLines, SIGNAL(valueChanged(int)), this, SLOT(slotDropCapLines(int)));
 	disconnect(m_pwidget->parEffectOffset, SIGNAL(valueChanged(double)), this, SLOT(slotParEffectOffset()));
 	disconnect(m_pwidget->parEffectIndentBox, SIGNAL(toggled(bool)), this, SLOT(slotParEffectIndent(bool)));
 	disconnect(m_pwidget->parEffectCharStyleCombo, SIGNAL(activated(int)), this, SLOT(slotParEffectCharStyle(int)));
-//	disconnect(m_pwidget->bulletBox, SIGNAL(toggled(bool)), this, SLOT(slotBullet(bool)));
 	disconnect(m_pwidget->bulletStrEdit, SIGNAL(editTextChanged(QString)), this, SLOT(slotBulletStr(QString)));
-//	disconnect(m_pwidget->numBox, SIGNAL(toggled(bool)), this, SLOT(slotNumeration(bool)));
 	disconnect(m_pwidget->numComboBox, SIGNAL(textActivated(QString)), this, SLOT(slotNumName(QString)));
 	disconnect(m_pwidget->numFormatCombo, SIGNAL(activated(int)), this, SLOT(slotNumFormat(int)));
 	disconnect(m_pwidget->numLevelSpin, SIGNAL(valueChanged(int)), this, SLOT(slotNumLevel(int)));
@@ -655,10 +644,6 @@
 	disconnect(m_pwidget->cpage->effects_->UnderlineVal->LWidth, SIGNAL(valueChanged(double)), this, SLOT(slotEffectProperties()));
 	disconnect(m_pwidget->cpage->effects_->StrikeVal->LPos, SIGNAL(valueChanged(double)), this, SLOT(slotEffectProperties()));
 	disconnect(m_pwidget->cpage->effects_->StrikeVal->LWidth, SIGNAL(valueChanged(double)), this, SLOT(slotEffectProperties()));
-	// disconnect(m_pwidget->cpage->fillColor_, SIGNAL(currentTextChanged(QString)), this, SLOT(slotFillColor()));
-	// disconnect(m_pwidget->cpage->fillShade_, SIGNAL(clicked()), this, SLOT(slotFillShade()));
-	// disconnect(m_pwidget->cpage->strokeColor_, SIGNAL(currentTextChanged(QString)), this, SLOT(slotStrokeColor()));
-	// disconnect(m_pwidget->cpage->strokeShade_, SIGNAL(clicked()), this, SLOT(slotStrokeShade()));
 	disconnect(m_pwidget->cpage->language_, SIGNAL(activated(int)), this, SLOT(slotLanguage()));
 	disconnect(m_pwidget->cpage->fontSize_, SIGNAL(valueChanged(double)), this, SLOT(slotFontSize()));
 	disconnect(m_pwidget->cpage->fontHScale_, SIGNAL(valueChanged(double)), this, SLOT(slotScaleH()));
@@ -667,8 +652,6 @@
 	disconnect(m_pwidget->cpage->widthSpaceSpin, SIGNAL(valueChanged(double)), this, SLOT(slotWordTracking()));
 	disconnect(m_pwidget->cpage->baselineOffset_, SIGNAL(valueChanged(double)), this, SLOT(slotBaselineOffset()));
 	disconnect(m_pwidget->cpage->parentCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(slotCharParentChanged(QString)));
-	// disconnect(m_pwidget->cpage->backColor_, SIGNAL(currentTextChanged(QString)), this, SLOT(slotBackColor()));
-	// disconnect(m_pwidget->cpage->backShade_, SIGNAL(clicked()), this, SLOT(slotBackShade()));
 
 	disconnect(m_pwidget->cpage->textColor->colorButton, SIGNAL(colorChanged()), this, SLOT(slotFillColor()));
 	disconnect(m_pwidget->cpage->textColor->parentButton, SIGNAL(clicked()), this, SLOT(slotFillColor()));
@@ -796,23 +779,21 @@
 	ParagraphStyle::OpticalMarginType omt( static_cast<ParagraphStyle::OpticalMarginType>(m_pwidget->optMarginWidget->opticalMargin()));
 	if (m_pwidget->optMarginWidget->useParentValue())
 		for (int i = 0; i < m_selection.count(); ++i)
+		{
 			m_selection[i]->resetOpticalMargins();
+			m_selection[i]->resetOpticalMarginSetId();
+		}
 	else
 		for (int i = 0; i < m_selection.count(); ++i)
-			m_selection[i]->setOpticalMargins(omt);
+		{
+			m_selection[i]->setOpticalMarginSetId(m_pwidget->optMarginWidget->opticalMarginSetId());
+			m_selection[i]->setOpticalMargins(omt);			
+		}
 
 	slotSelectionDirty();
 
 }
 
-void SMParagraphStyle::slotParentOpticalMargin()
-{
-	for (int i = 0; i < m_selection.count(); ++i)
-		m_selection[i]->resetOpticalMargins();
-
-	slotSelectionDirty();
-}
-
 void SMParagraphStyle::slotMinSpace()
 {
 	if (m_pwidget->minSpaceSpin->useParentValue())
Index: scribus/ui/smtextstyles.h
===================================================================
--- scribus/ui/smtextstyles.h	(Revision 26446)
+++ scribus/ui/smtextstyles.h	(Arbeitskopie)
@@ -97,7 +97,6 @@
 	void slotDirection();
 	void slotOpticalMargin(int i);
 	void slotOpticalMarginSelector();
-	void slotParentOpticalMargin();
 	void slotMinSpace();
 	void slotMinGlyphExt();
 	void slotMaxGlyphExt();
Index: scribus/ui/stylemanager.cpp
===================================================================
--- scribus/ui/stylemanager.cpp	(Revision 26446)
+++ scribus/ui/stylemanager.cpp	(Arbeitskopie)
@@ -473,6 +473,9 @@
 		QStringList neededColors;
 		neededColors.clear();
 
+		QStringList neededOpticalMarginSets;
+		neededOpticalMarginSets.clear();
+
 		foreach (const QString& aStyle, dia2->paragraphStyles())
 		{
 			ParagraphStyle& sty(tmpParaStyles[tmpParaStyles.find(aStyle)]);
@@ -493,6 +496,9 @@
 				neededColors.append(sty.charStyle().strokeColor());
 			if ((!m_doc->PageColors.contains(sty.charStyle().fillColor())) && (!neededColors.contains(sty.charStyle().fillColor())))
 				neededColors.append(sty.charStyle().fillColor());
+
+			if ((!m_doc->opticalMarginSets().contains(sty.opticalMarginSetId())) && (!neededOpticalMarginSets.contains(sty.opticalMarginSetId())))
+				neededOpticalMarginSets.append(sty.opticalMarginSetId());
 		}
 
 		foreach (const QString& aStyle, dia2->characterStyles())
@@ -554,6 +560,11 @@
 				}
 			}
 		}
+
+		if (!neededOpticalMarginSets.isEmpty())
+		{
+			// TODO: Load missing optical margin sets from doc
+		}
 	}
 	delete dia2;
 // Start hack part 2
Index: scribus/util_text.cpp
===================================================================
--- scribus/util_text.cpp	(Revision 26446)
+++ scribus/util_text.cpp	(Arbeitskopie)
@@ -94,3 +94,29 @@
 	return QString(xml.c_str());
 }
 
+
+const 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);
+
+}
+
+const 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 26446)
+++ scribus/util_text.h	(Arbeitskopie)
@@ -30,4 +30,7 @@
 //returns string with saxed story
 QString SCRIBUS_API saxedText(StoryText* story);
 
+const QString SCRIBUS_API stringToUnicode(const QString &text);
+const QString SCRIBUS_API unicodeToString(const QString &text);
+
 #endif

nitramr

2024-11-28 21:52

developer   ~0051689

@jghali, did you found some time to check the patch?

jghali

2024-12-10 21:49

administrator   ~0051717

Now I did :-) I have committed your patch with fixes related to use of references and cosnt-correctness. Thank you!

JLuc

2024-12-10 22:55

developer   ~0051718

Great !

Issue History

Date Modified Username Field Change
2012-01-25 09:22 meho_r New Issue
2012-10-07 19:14 bubu Note Added: 0029020
2012-10-07 19:48 bubu Note Edited: 0029020
2014-04-23 13:56 Pancho Note Added: 0031767
2014-08-20 10:40 nasfarley88 Note Added: 0033298
2014-08-20 12:42 Kunda Severity minor => major
2014-08-20 12:43 Kunda Status new => confirmed
2014-08-20 12:47 Kunda Note Added: 0033301
2014-08-20 12:47 Kunda Target Version => 1.5.1
2016-01-23 17:17 cbradney Target Version 1.5.1 => 1.5.3
2016-04-10 14:36 Kunda Relationship added related to 0012265
2016-06-29 01:14 jeff Note Added: 0041743
2016-12-08 22:04 Kunda Target Version 1.5.3 => 1.5.4
2017-05-29 18:16 jghali Relationship added has duplicate 0014832
2017-06-28 08:41 Scriber Note Added: 0044103
2018-12-01 10:52 MZaske Note Added: 0045670
2018-12-01 11:14 MZaske Note Added: 0045671
2018-12-03 15:57 ale Note Added: 0045676
2019-01-17 16:11 Scriber Note Added: 0045803
2024-01-04 00:50 Nathan Note Added: 0050792
2024-01-04 00:50 Nathan File Added: HangingPunctuation.png
2024-10-24 19:05 nitramr Relationship added related to 0016113
2024-10-24 19:19 nitramr Note Added: 0051483
2024-10-24 19:19 nitramr File Added: opticalmargin_2024-10-24_01.patch
2024-10-24 19:19 nitramr File Added: Bildschirmfoto vom 2024-10-24 21-07-55.png
2024-10-24 19:19 nitramr Assigned To => nitramr
2024-10-24 19:19 nitramr Status confirmed => assigned
2024-10-24 19:19 nitramr Target Version 1.5.4 => 1.6.3.svn
2024-10-24 19:19 nitramr Patch => Yes
2024-10-26 21:45 nitramr Note Added: 0051486
2024-10-26 21:45 nitramr File Added: files.zip
2024-10-26 21:45 nitramr File Added: opticalmargin_2024-10-26_01.patch
2024-10-26 21:45 nitramr File Added: optical_margin_lookup.png
2024-10-27 18:05 nitramr Note Added: 0051492
2024-10-27 18:05 nitramr File Added: files-2.zip
2024-10-27 18:05 nitramr File Added: opticalmargins_2024-10-27_01.patch
2024-10-27 18:05 nitramr File Added: opticalmargins.png
2024-10-27 19:39 ale Note Added: 0051496
2024-10-27 20:02 nitramr Note Added: 0051497
2024-10-27 20:26 nitramr Note Added: 0051498
2024-10-27 21:31 utnik Note Added: 0051499
2024-11-06 10:55 nitramr Note Added: 0051530
2024-11-06 10:55 nitramr File Added: Bildschirmfoto 2024-11-06 um 11.42.08.png
2024-11-06 11:13 utnik Note Added: 0051531
2024-11-06 20:27 cbradney Note Added: 0051533
2024-11-06 21:24 JLuc Note Added: 0051534
2024-11-06 21:24 JLuc Note Edited: 0051534
2024-11-07 14:20 ale Note Added: 0051536
2024-11-12 18:59 nitramr Note Added: 0051567
2024-11-12 19:51 cbradney Note Added: 0051568
2024-11-12 22:42 utnik Note Added: 0051570
2024-11-18 22:29 nitramr Note Added: 0051593
2024-11-18 22:29 nitramr File Added: files-3.zip
2024-11-18 22:29 nitramr File Added: opticalmargin_2024-11-18_01.patch
2024-11-18 22:29 nitramr File Added: doc_prefs.png
2024-11-20 22:20 jghali Note Added: 0051619
2024-11-22 01:00 nitramr Note Added: 0051630
2024-11-22 01:00 nitramr File Added: files-4.zip
2024-11-22 01:00 nitramr File Added: opticalmargins_2024-11-22_01.patch
2024-11-22 01:01 nitramr Target Version 1.6.3.svn => 1.7.0
2024-11-28 21:52 nitramr Note Added: 0051689
2024-12-10 21:49 jghali Note Added: 0051717
2024-12-10 21:49 jghali Status assigned => resolved
2024-12-10 21:49 jghali Resolution open => fixed
2024-12-10 21:49 jghali Fixed in Version => 1.7.0.svn
2024-12-10 22:55 JLuc Note Added: 0051718
2024-12-11 22:02 cbradney Status resolved => closed