View Issue Details

IDProjectCategoryView StatusLast Update
0010539ScribusTypographypublic2024-10-27 21:31
Reportermeho_r Assigned Tonitramr  
PrioritynormalSeveritymajorReproducibilityalways
Status assignedResolutionopen 
PlatformLinuxOSDebianOS VersionWheezy
Product Version1.4.0 
Target Version1.6.3.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 confirmednitramr "Optical margins" is invalid for Chinese punctuation. 
has duplicate 0014832 closedjghali Optical Margins (hanging punctuation) misses quotation marks on the left 
related to 0012265 assignednitramr 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'…

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