View Issue Details

IDProjectCategoryView StatusLast Update
0017558ScribusGeneralpublic2025-06-11 20:50
Reporterale Assigned Toale  
PrioritynormalSeverityfeatureReproducibilityN/A
Status assignedResolutionopen 
Product Version1.7.1.svn 
Summary0017558: [PATCH] Preflight verifier: check for trailing spaces
DescriptionThe preflight verifier should optionally check for trailing spaces.
Steps To Reproducethe attached patch adds an entry in the preflight verifier, off by default, that check for spaces before a new line or at the end of the frame
TagsNo tags attached.
PatchYes

Relationships

related to 0014369 confirmed whitespace affects justification for centred text in undesirable ways 

Activities

ale

2025-06-11 20:49

manager  

check-trailing-spaces.diff (17,359 bytes)   
From f418a07ba3eb9f6098679e210fc41a67c9662f35 Mon Sep 17 00:00:00 2001
From: ale rimoldi <ale@graphicslab.org>
Date: Wed, 11 Jun 2025 22:13:26 +0200
Subject: preflight verifier: check for trailing spaces


diff --git a/scribus/documentchecker.cpp b/scribus/documentchecker.cpp
index 0338e7bd6..6a9bbd3fd 100644
--- a/scribus/documentchecker.cpp
+++ b/scribus/documentchecker.cpp
@@ -23,6 +23,8 @@ for which a new license (GPL+exception) is in place.
 
 #include <QList>
 
+#include <QRegularExpression>
+
 #include "commonstrings.h"
 #include "documentchecker.h"
 #include "pageitem.h"
@@ -33,6 +35,7 @@ for which a new license (GPL+exception) is in place.
 #include "scribusdoc.h"
 #include "scribusstructs.h"
 #include "text/textlayoutpainter.h"
+#include "text/storytext.h"
 #include "util_formats.h"
 
 class MissingGlyphsPainter: public TextLayoutPainter
@@ -390,6 +393,29 @@ void DocumentChecker::checkItems(ScribusDoc *currDoc, const CheckerPrefs& checke
 				if ( currItem->frameOverflows() && (checkerSettings.checkOverflow) && (!((currItem->isAnnotation()) && ((currItem->annotation().Type() == Annotation::Combobox) || (currItem->annotation().Type() == Annotation::Listbox)))))
 					itemError.insert(PreflightError::TextOverflow, 0);
 
+				if (checkerSettings.checkTrailingSpaces)
+				{
+					const PageItem_TextFrame* textFrame = currItem->asTextFrame();
+					if (textFrame->itemText.length() > 0)
+					{
+						const QString fullText = textFrame->itemText.plainText();
+						QRegularExpression re(QStringLiteral("\\s[\\n") + SpecialChars::LINEBREAK + QStringLiteral("]"));
+						QRegularExpressionMatch match = re.match(fullText + "\n");
+						if (match.hasMatch())
+						{
+							itemError.insert(PreflightError::TrailingSpace, match.capturedStart());
+						}
+						// TODO: currently, it's only possible to add one error of the same type per item
+						// In the future, it might be good to have a list of matches.
+						// QRegularExpressionMatchIterator i = re.globalMatch(fullText + "\n");
+						// while (i.hasNext())
+						// {
+						// 	QRegularExpressionMatch match = i.next();
+						// 	itemError.insert(PreflightError::TrailingSpace, match.capturedStart());
+						// }
+					}
+				}
+
 				if (checkerSettings.checkEmptyTextFrames && (currItem->itemText.length() == 0 || currItem->frameUnderflows()))
 				{
 					bool isEmptyAnnotation = (currItem->isAnnotation() && 
@@ -655,6 +681,28 @@ void DocumentChecker::checkItems(ScribusDoc *currDoc, const CheckerPrefs& checke
 			{
 				if ( currItem->frameOverflows() && (checkerSettings.checkOverflow) && (!((currItem->isAnnotation()) && ((currItem->annotation().Type() == Annotation::Combobox) || (currItem->annotation().Type() == Annotation::Listbox)))))
 					itemError.insert(PreflightError::TextOverflow, 0);
+				if (checkerSettings.checkTrailingSpaces)
+				{
+					const PageItem_TextFrame* textFrame = currItem->asTextFrame();
+					if (textFrame->itemText.length() > 0)
+					{
+						const QString fullText = textFrame->itemText.plainText();
+						QRegularExpression re(QStringLiteral("\\s[\\n") + SpecialChars::LINEBREAK + QStringLiteral("]"));
+						QRegularExpressionMatch match = re.match(fullText + "\n");
+						if (match.hasMatch())
+						{
+							itemError.insert(PreflightError::TrailingSpace, match.capturedStart());
+						}
+						// TODO: currently, it's only possible to add one error of the same type per item
+						// In the future, it might be good to have a list of matches.
+						// QRegularExpressionMatchIterator i = re.globalMatch(fullText + "\n");
+						// while (i.hasNext())
+						// {
+						// 	QRegularExpressionMatch match = i.next();
+						// 	itemError.insert(PreflightError::TrailingSpace, match.capturedStart());
+						// }
+					}
+				}
 
 				if (checkerSettings.checkEmptyTextFrames && (currItem->itemText.length() == 0 || currItem->frameUnderflows()))
 				{
diff --git a/scribus/plugins/fileloader/scribus171format/scribus171format.cpp b/scribus/plugins/fileloader/scribus171format/scribus171format.cpp
index 1eb91e9f3..5a9a681ab 100644
--- a/scribus/plugins/fileloader/scribus171format/scribus171format.cpp
+++ b/scribus/plugins/fileloader/scribus171format/scribus171format.cpp
@@ -3065,6 +3065,7 @@ bool Scribus171Format::readCheckProfile(ScribusDoc* doc, const ScXmlStreamAttrib
 		checkerSettings.checkGlyphs = attrs.valueAsBool("checkGlyphs", true);
 		checkerSettings.checkOrphans = attrs.valueAsBool("checkOrphans", true);
 		checkerSettings.checkOverflow = attrs.valueAsBool("checkOverflow", true);
+		checkerSettings.checkTrailingSpaces = attrs.valueAsBool("checkTrailingSpaces", false);
 		checkerSettings.checkPictures = attrs.valueAsBool("checkPictures", true);
 		checkerSettings.checkPartFilledImageFrames = attrs.valueAsBool("checkPartFilledImageFrames", false);
 		checkerSettings.checkResolution = attrs.valueAsBool("checkResolution", true);
@@ -3090,6 +3091,7 @@ bool Scribus171Format::readCheckProfile(ScribusDoc* doc, const ScXmlStreamAttrib
 		checkerSettings.checkGlyphs = attrs.valueAsBool("CheckGlyphs", true);
 		checkerSettings.checkOrphans = attrs.valueAsBool("CheckOrphans", true);
 		checkerSettings.checkOverflow = attrs.valueAsBool("CheckOverflow", true);
+		checkerSettings.checkTrailingSpaces = attrs.valueAsBool("CheckTrailingSpaces", false);
 		checkerSettings.checkPictures = attrs.valueAsBool("CheckPictures", true);
 		checkerSettings.checkPartFilledImageFrames = attrs.valueAsBool("CheckPartFilledImageFrames", false);
 		checkerSettings.checkResolution = attrs.valueAsBool("CheckResolution", true);
diff --git a/scribus/plugins/fileloader/scribus171format/scribus171format_save.cpp b/scribus/plugins/fileloader/scribus171format/scribus171format_save.cpp
index 76185227d..454fb3dfa 100644
--- a/scribus/plugins/fileloader/scribus171format/scribus171format_save.cpp
+++ b/scribus/plugins/fileloader/scribus171format/scribus171format_save.cpp
@@ -580,6 +580,7 @@ void Scribus171Format::writeCheckerProfiles(ScXmlStreamWriter & docu) const
 		docu.writeAttribute("CheckGlyphs", static_cast<int>(itcp.value().checkGlyphs));
 		docu.writeAttribute("CheckOrphans", static_cast<int>(itcp.value().checkOrphans));
 		docu.writeAttribute("CheckOverflow", static_cast<int>(itcp.value().checkOverflow));
+		docu.writeAttribute("CheckTrailingSpaces", static_cast<int>(itcp.value().checkTrailingSpaces));
 		docu.writeAttribute("CheckPictures", static_cast<int>(itcp.value().checkPictures));
 		docu.writeAttribute("CheckPartFilledImageFrames", static_cast<int>(itcp.value().checkPartFilledImageFrames));
 		docu.writeAttribute("CheckResolution", static_cast<int>(itcp.value().checkResolution));
diff --git a/scribus/prefsmanager.cpp b/scribus/prefsmanager.cpp
index eccd2aaf9..1dc8e2478 100644
--- a/scribus/prefsmanager.cpp
+++ b/scribus/prefsmanager.cpp
@@ -1664,6 +1664,7 @@ bool PrefsManager::writePref(const QString& filePath)
 		dcVerifierProfile.setAttribute("CheckGlyphs", static_cast<int>(checkerProfile.checkGlyphs));
 		dcVerifierProfile.setAttribute("CheckOrphans", static_cast<int>(checkerProfile.checkOrphans));
 		dcVerifierProfile.setAttribute("CheckOverflow", static_cast<int>(checkerProfile.checkOverflow));
+		dcVerifierProfile.setAttribute("CheckTrailingSpaces", static_cast<int>(checkerProfile.checkTrailingSpaces));
 		dcVerifierProfile.setAttribute("CheckPictures", static_cast<int>(checkerProfile.checkPictures));
 		dcVerifierProfile.setAttribute("CheckResolution", static_cast<int>(checkerProfile.checkResolution));
 		dcVerifierProfile.setAttribute("CheckPartFilledImageFrames", static_cast<int>(checkerProfile.checkPartFilledImageFrames));
@@ -2483,6 +2484,7 @@ bool PrefsManager::readPref(const QString& filePath)
 			checkerSettings.checkGlyphs = static_cast<bool>(dc.attribute("CheckGlyphs", "1").toInt());
 			checkerSettings.checkOrphans = static_cast<bool>(dc.attribute("CheckOrphans", "1").toInt());
 			checkerSettings.checkOverflow = static_cast<bool>(dc.attribute("CheckOverflow", "1").toInt());
+			checkerSettings.checkTrailingSpaces = static_cast<bool>(dc.attribute("CheckTrailingSpaces", "0").toInt());
 			checkerSettings.checkPictures = static_cast<bool>(dc.attribute("CheckPictures", "1").toInt());
 			checkerSettings.checkResolution = static_cast<bool>(dc.attribute("CheckResolution", "1").toInt());
 			checkerSettings.checkPartFilledImageFrames = static_cast<bool>(dc.attribute("CheckPartFilledImageFrames", "0").toInt());
@@ -2889,6 +2891,7 @@ void PrefsManager::initDefaultCheckerPrefs(CheckerPrefsList& cp)
 	checkerSettings.checkGlyphs = true;
 	checkerSettings.checkOrphans = true;
 	checkerSettings.checkOverflow = true;
+	checkerSettings.checkTrailingSpaces = false;
 	checkerSettings.checkPictures = true;
 	checkerSettings.checkResolution = true;
 	checkerSettings.checkPartFilledImageFrames = false;
diff --git a/scribus/prefsstructs.h b/scribus/prefsstructs.h
index bbf957683..6932469d1 100644
--- a/scribus/prefsstructs.h
+++ b/scribus/prefsstructs.h
@@ -38,6 +38,7 @@ struct CheckerPrefs
 	bool autoCheck;
 	bool checkGlyphs;
 	bool checkOverflow;
+	bool checkTrailingSpaces;
 	bool checkOrphans;
 	bool checkPictures;
 	bool checkResolution;
diff --git a/scribus/scribusstructs.h b/scribus/scribusstructs.h
index 0d6f5cdb2..aefc5862a 100644
--- a/scribus/scribusstructs.h
+++ b/scribus/scribusstructs.h
@@ -482,7 +482,8 @@ enum class PreflightError
 	MarksChanged = 19,
 	AppliedMasterDifferentSide = 20,
 	EmptyTextFrame = 21,
-	ImageHasProgressiveEncoding = 22
+	ImageHasProgressiveEncoding = 22,
+	TrailingSpace = 23
 };
 
 using errorCodes = QMap<PreflightError, int>;
diff --git a/scribus/ui/.prefs_preflightverifier.h.swp b/scribus/ui/.prefs_preflightverifier.h.swp
new file mode 100644
index 000000000..8237b07c0
Binary files /dev/null and b/scribus/ui/.prefs_preflightverifier.h.swp differ
diff --git a/scribus/ui/checkDocument.cpp b/scribus/ui/checkDocument.cpp
index 20f5108d3..d0448ad9d 100644
--- a/scribus/ui/checkDocument.cpp
+++ b/scribus/ui/checkDocument.cpp
@@ -142,6 +142,7 @@ void CheckDocument::languageChange()
 	warnMap.insert(PV_NOT_CMYK_SPOT,			qMakePair(tr("Object colorspace is not CMYK or spot"),					tr("PDF supports many different ways to represent the color of any object including RGB, CMYK and Spot (aka Separation) colors. Some of the PDF standards, such as PDF/X-1a, require the only CMYK and Spot colors be used.")));
 	warnMap.insert(PV_RASTER_PDF,				qMakePair(tr("Object is a placed PDF"),									tr("The warning is verifying for you that there is a PDF loaded into an Image Frame, where it will be rasterized or converted to a bitmap. Its resolution may be less than ideal. See PDF Export to learn how to minimize this problem.")));
 	warnMap.insert(PV_TEXT_OVERFLOW,			qMakePair(tr("Text overflow"),											tr("There is more text than can show in the frame as sized. Nonvisible excess characters like spaces and carriage returns may trigger this if nothing appears to be missing.")));
+	warnMap.insert(PV_TRAILING_SPACES,			qMakePair(tr("Trailing spaces"),											tr("Paragraphs have white spaces at the end: This can lead to errors in the alignment.")));
 	warnMap.insert(PV_TRANSPARENCY,				qMakePair(tr("Object has transparency"),								tr("This warning indicates that your document contains images that have a transparent layer. This is really only an issue if using older printing profiles or PostScript. It is safe to ignore this when exporting to PDF version greater than 1.4.")));
 	warnMap.insert(PV_WRONG_FONT,				qMakePair(tr("Annotation uses a non-TrueType font"),					tr("Annotations support only a standard set of fonts. Choose another one.")));
 	warnMap.insert(PV_LAYER_TRANSPARENCY,		qMakePair(tr("Transparency used"),										tr("This layer uses transparency, only an issue if using older printing profiles. You may safely ignore this when using modern printing methods, or exporting to PDF version greater than 1.4.")));
@@ -302,6 +303,11 @@ void CheckDocument::buildItem(QTreeWidgetItem * item, PreflightError errorType,
 			item->setToolTip(COLUMN_PROBLEM, warnMap[PV_TEXT_OVERFLOW].second);
 			item->setIcon(COLUMN_ITEM, onlyWarning);
 			break;
+		case PreflightError::TrailingSpace:
+			item->setText(COLUMN_PROBLEM, warnMap[PV_TRAILING_SPACES].first);
+			item->setToolTip(COLUMN_PROBLEM, warnMap[PV_TRAILING_SPACES].second);
+			item->setIcon(COLUMN_ITEM, onlyWarning);
+			break;
 		case PreflightError::ObjectNotOnPage:
 			item->setText(COLUMN_PROBLEM, warnMap[PV_NON_ON_PAGE].first);
 			item->setToolTip(COLUMN_PROBLEM, warnMap[PV_NON_ON_PAGE].second);
diff --git a/scribus/ui/checkDocument.h b/scribus/ui/checkDocument.h
index 1038d14de..387bfdbcb 100644
--- a/scribus/ui/checkDocument.h
+++ b/scribus/ui/checkDocument.h
@@ -149,6 +149,7 @@ protected:
 		PV_NOT_CMYK_SPOT,
 		PV_RASTER_PDF,
 		PV_TEXT_OVERFLOW,
+		PV_TRAILING_SPACES,
 		PV_TRANSPARENCY,
 		PV_WRONG_FONT,
 		PV_LAYER_TRANSPARENCY,
diff --git a/scribus/ui/prefs_preflightverifier.cpp b/scribus/ui/prefs_preflightverifier.cpp
index 27a61d787..2c5eddf79 100644
--- a/scribus/ui/prefs_preflightverifier.cpp
+++ b/scribus/ui/prefs_preflightverifier.cpp
@@ -25,7 +25,7 @@ Prefs_PreflightVerifier::Prefs_PreflightVerifier(QWidget* parent, ScribusDoc* /*
 	connect(autoCheckBeforePrintExportCheckBox, SIGNAL(clicked()), this, SLOT(putProfile()));
 	connect(checkMissingGlyphsCheckBox, SIGNAL(clicked()), this, SLOT(putProfile()));
 	connect(checkItemsNotOnAPageCheckBox, SIGNAL(clicked()), this, SLOT(putProfile()));
-	connect(checkTextOverflowCheckBox, SIGNAL(clicked()), this, SLOT(putProfile()));
+	connect(checkTrailingSpacesCheckBox, SIGNAL(clicked()), this, SLOT(putProfile()));
 	connect(checkTransparenciesCheckBox, SIGNAL(clicked()), this, SLOT(putProfile()));
 	connect(checkMissingImagesCheckBox, SIGNAL(clicked()), this, SLOT(putProfile()));
 	connect(checkPartFilledImageFramesCheckBox, SIGNAL(clicked()), this, SLOT(putProfile()));
@@ -70,6 +70,7 @@ void Prefs_PreflightVerifier::restoreDefaults(struct ApplicationPrefs *prefsData
 	checkMissingGlyphsCheckBox->setChecked(checkerProfile.checkGlyphs);
 	checkItemsNotOnAPageCheckBox->setChecked(checkerProfile.checkOrphans);
 	checkTextOverflowCheckBox->setChecked(checkerProfile.checkOverflow);
+	checkTrailingSpacesCheckBox->setChecked(checkerProfile.checkTrailingSpaces);
 	checkTransparenciesCheckBox->setChecked(checkerProfile.checkTransparency);
 	checkMissingImagesCheckBox->setChecked(checkerProfile.checkPictures);
 	checkPartFilledImageFramesCheckBox->setChecked(checkerProfile.checkPartFilledImageFrames);
@@ -111,6 +112,7 @@ void Prefs_PreflightVerifier::putProfile()
 	checkerProfile.checkGlyphs = checkMissingGlyphsCheckBox->isChecked();
 	checkerProfile.checkOrphans = checkItemsNotOnAPageCheckBox->isChecked();
 	checkerProfile.checkOverflow = checkTextOverflowCheckBox->isChecked();
+	checkerProfile.checkTrailingSpaces = checkTrailingSpacesCheckBox->isChecked();
 	checkerProfile.checkPictures = checkMissingImagesCheckBox->isChecked();
 	checkerProfile.checkPartFilledImageFrames = checkPartFilledImageFramesCheckBox->isChecked();
 	checkerProfile.checkResolution = checkImageResolutionCheckBox->isChecked();
@@ -169,6 +171,7 @@ void Prefs_PreflightVerifier::updateProfile(const QString& name)
 	checkMissingGlyphsCheckBox->setChecked(checkerProfile.checkGlyphs);
 	checkItemsNotOnAPageCheckBox->setChecked(checkerProfile.checkOrphans);
 	checkTextOverflowCheckBox->setChecked(checkerProfile.checkOverflow);
+	checkTrailingSpacesCheckBox->setChecked(checkerProfile.checkTrailingSpaces);
 	checkTransparenciesCheckBox->setChecked(checkerProfile.checkTransparency);
 	checkMissingImagesCheckBox->setChecked(checkerProfile.checkPictures);
 	checkPartFilledImageFramesCheckBox->setChecked(checkerProfile.checkPartFilledImageFrames);
@@ -211,6 +214,7 @@ void Prefs_PreflightVerifier::addProf()
 	checkerSettings.checkGlyphs = checkMissingGlyphsCheckBox->isChecked();
 	checkerSettings.checkOrphans = checkItemsNotOnAPageCheckBox->isChecked();
 	checkerSettings.checkOverflow = checkTextOverflowCheckBox->isChecked();
+	checkerSettings.checkTrailingSpaces = checkTrailingSpacesCheckBox->isChecked();
 	checkerSettings.checkPictures = checkMissingImagesCheckBox->isChecked();
 	checkerSettings.checkPartFilledImageFrames = checkPartFilledImageFramesCheckBox->isChecked();
 	checkerSettings.checkResolution = checkImageResolutionCheckBox->isChecked();
diff --git a/scribus/ui/prefs_preflightverifierbase.ui b/scribus/ui/prefs_preflightverifierbase.ui
index e8bd15af0..920d503be 100644
--- a/scribus/ui/prefs_preflightverifierbase.ui
+++ b/scribus/ui/prefs_preflightverifierbase.ui
@@ -95,8 +95,8 @@
        <rect>
         <x>0</x>
         <y>0</y>
-        <width>610</width>
-        <height>615</height>
+        <width>607</width>
+        <height>730</height>
        </rect>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout_2">
@@ -234,6 +234,13 @@
          </property>
         </widget>
        </item>
+       <item>
+        <widget class="QCheckBox" name="checkTrailingSpacesCheckBox">
+         <property name="text">
+          <string>Check for trailing spaces in text frames</string>
+         </property>
+        </widget>
+       </item>
        <item>
         <widget class="QCheckBox" name="checkTransparenciesCheckBox">
          <property name="text">
check-trailing-spaces.diff (17,359 bytes)   

Issue History

Date Modified Username Field Change
2025-06-11 20:49 ale New Issue
2025-06-11 20:49 ale Status new => assigned
2025-06-11 20:49 ale Assigned To => ale
2025-06-11 20:49 ale File Added: check-trailing-spaces.diff
2025-06-11 20:50 ale Relationship added related to 0014369