View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0010539 | Scribus | Typography | public | 2012-01-25 09:22 | 2024-10-27 21:31 |
Reporter | meho_r | Assigned To | nitramr | ||
Priority | normal | Severity | major | Reproducibility | always |
Status | assigned | Resolution | open | ||
Platform | Linux | OS | Debian | OS Version | Wheezy |
Product Version | 1.4.0 | ||||
Target Version | 1.6.3.svn | ||||
Summary | 0010539: Optical margins, protrusion into the left margin doesn't work | ||||
Description | Actually, 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 Reproduce | 1. 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 Information | With 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. | ||||
Tags | No tags attached. | ||||
Patch | Yes | ||||
|
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) |
|
Confirm for 1.4.3. IMHO more than a minor bug. |
|
Can confirm for 1.4.4 |
|
upgrading to 'major' severity. |
|
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. |
|
Can confirm for 1.5.3 |
|
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 |
|
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. |
|
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. |
|
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. |
|
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!). |
|
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 |
|
>> 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? 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(); |
|
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"/> ... 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 |
|
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. |
|
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? |
|
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 |
|
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'… |
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 |