1 /** -*- mode: c++ ; c-basic-offset: 2 -*-
2 *
3 * @file Misc.cpp
4 *
5 * Copyright 2017 Sebastien Fourey
6 *
7 * This file is part of G'MIC-Qt, a generic plug-in for raster graphics
8 * editors, offering hundreds of filters thanks to the underlying G'MIC
9 * image processing framework.
10 *
11 * gmic_qt is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * gmic_qt is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with gmic_qt. If not, see <http://www.gnu.org/licenses/>.
23 *
24 */
25
26 #include "Misc.h"
27 #include <QByteArray>
28 #include <QChar>
29 #include <QDebug>
30 #include <QMap>
31 #include <QRegularExpression>
32 #include <QString>
33 #include <QStringList>
34 #include <QTextStream>
35 #include <algorithm>
36 #include <cctype>
37 #include "Common.h"
38 #include "Globals.h"
39 #include "HtmlTranslator.h"
40 #include "Logger.h"
41 #include "gmic.h"
42
43 namespace
44 {
skipSpaces(const char * & pc)45 inline void skipSpaces(const char *& pc)
46 {
47 while (isspace(*pc)) {
48 ++pc;
49 }
50 }
isEmptyOrSpaceSequence(const char * pc)51 inline bool isEmptyOrSpaceSequence(const char * pc)
52 {
53 while (*pc) {
54 if (!isspace(*pc)) {
55 return false;
56 }
57 ++pc;
58 }
59 return true;
60 }
61 } // namespace
62
63 namespace GmicQt
64 {
65
commandFromOutputMessageMode(OutputMessageMode mode)66 QString commandFromOutputMessageMode(OutputMessageMode mode)
67 {
68 switch (mode) {
69 case OutputMessageMode::Quiet:
70 case OutputMessageMode::VerboseConsole:
71 case OutputMessageMode::VerboseLogFile:
72 case OutputMessageMode::Unspecified:
73 return "";
74 case OutputMessageMode::VeryVerboseConsole:
75 case OutputMessageMode::VeryVerboseLogFile:
76 return "v 3";
77 case OutputMessageMode::DebugConsole:
78 case OutputMessageMode::DebugLogFile:
79 return "debug";
80 }
81 return "";
82 }
83
appendWithSpace(QString & str,const QString & other)84 void appendWithSpace(QString & str, const QString & other)
85 {
86 if (str.isEmpty() || other.isEmpty()) {
87 str += other;
88 return;
89 }
90 str += QChar(' ');
91 str += other;
92 }
93
downcaseCommandTitle(QString & title)94 void downcaseCommandTitle(QString & title)
95 {
96 QMap<int, QString> acronyms;
97 // Acronyms
98 QRegExp re("([A-Z0-9]{2,255})");
99 int index = 0;
100 while ((index = re.indexIn(title, index)) != -1) {
101 QString pattern = re.cap(0);
102 acronyms[index] = pattern;
103 index += pattern.length();
104 }
105
106 // 3D
107 re.setPattern("([1-9])[dD] ");
108 if ((index = re.indexIn(title, 0)) != -1) {
109 acronyms[index] = re.cap(1) + "d ";
110 }
111
112 // B&W
113 re.setPattern("(B&W|[ \\[]Lab|[ \\[]YCbCr)");
114 index = 0;
115 while ((index = re.indexIn(title, index)) != -1) {
116 acronyms[index] = re.cap(1);
117 index += re.cap(1).length();
118 }
119
120 // Uppercase letter in last position, after a space
121 re.setPattern(" ([A-Z])$");
122 if ((index = re.indexIn(title, 0)) != -1) {
123 acronyms[index] = re.cap(0);
124 }
125 title = title.toLower();
126 QMap<int, QString>::const_iterator it = acronyms.cbegin();
127 while (it != acronyms.cend()) {
128 title.replace(it.key(), it.value().length(), it.value());
129 ++it;
130 }
131 title[0] = title[0].toUpper();
132 }
133
parseGmicFilterParameters(const QString & text,QStringList & args)134 bool parseGmicFilterParameters(const QString & text, QStringList & args)
135 {
136 return parseGmicFilterParameters(text.toUtf8().constData(), args);
137 }
138
parseGmicFilterParameters(const char * text,QStringList & args)139 bool parseGmicFilterParameters(const char * text, QStringList & args)
140 {
141 args.clear();
142 if (!text) {
143 return false;
144 }
145 skipSpaces(text);
146 const char * pc = text;
147 bool quoted = false;
148 bool escaped = false;
149 bool meaningfulSpaceFound = false;
150 char * buffer = new char[strlen(pc)]();
151 char * output = buffer;
152 while (*pc && !((meaningfulSpaceFound = (!quoted && !escaped && isspace(*pc))))) {
153 if (escaped) {
154 *output++ = *pc++;
155 escaped = false;
156 } else if (*pc == '\\') {
157 escaped = true;
158 ++pc;
159 } else if (*pc == '"') {
160 quoted = !quoted;
161 ++pc;
162 } else if (!quoted && !escaped && (*pc == ',')) {
163 *output = '\0';
164 args.push_back(QString::fromUtf8(buffer));
165 output = buffer;
166 ++pc;
167 } else {
168 *output++ = *pc++;
169 }
170 }
171 const bool endsWidthComma = (output == buffer) && (pc > text) && (!quoted && !escaped && (pc[-1] == ','));
172 if ((output != buffer) || endsWidthComma) {
173 *output = '\0';
174 args.push_back(QString::fromUtf8(buffer));
175 }
176 delete[] buffer;
177 if (quoted || (meaningfulSpaceFound && !isEmptyOrSpaceSequence(pc))) {
178 args.clear();
179 return false;
180 }
181 return true;
182 }
183
parseGmicUniqueFilterCommand(const char * text,QString & command,QString & arguments)184 bool parseGmicUniqueFilterCommand(const char * text, QString & command, QString & arguments)
185 {
186 arguments.clear();
187 command.clear();
188 if (!text) {
189 return false;
190 }
191 const char * commandBegin = text;
192 skipSpaces(commandBegin);
193 if (*commandBegin == '\0') {
194 return false;
195 }
196 const char * pc = commandBegin;
197 while (isalnum(*pc) || (*pc == '_')) {
198 ++pc;
199 }
200 if ((*pc != '\0') && !isspace(*pc)) {
201 return false;
202 }
203 const char * const commandEnd = pc;
204 skipSpaces(pc);
205
206 bool quoted = false;
207 bool escaped = false;
208 bool meaningfulSpaceFound = false;
209 const char * argumentStart = pc;
210 while (*pc && !((meaningfulSpaceFound = (!quoted && !escaped && isspace(*pc))))) {
211 if (escaped) {
212 escaped = false;
213 } else if (*pc == '\\') {
214 escaped = true;
215 } else if (*pc == '"') {
216 quoted = !quoted;
217 }
218 ++pc;
219 }
220 if (quoted || (meaningfulSpaceFound && !isEmptyOrSpaceSequence(pc))) {
221 return false;
222 }
223 command = QString::fromUtf8(commandBegin, static_cast<int>(commandEnd - commandBegin));
224 arguments = QString::fromUtf8(argumentStart, static_cast<int>(pc - argumentStart));
225 return true;
226 }
227
escapeUnescapedQuotes(const QString & text)228 QString escapeUnescapedQuotes(const QString & text)
229 {
230 std::string source_str = text.toStdString();
231 const char * pc = source_str.c_str();
232 std::vector<char> output_str(2 * source_str.size() + 1, static_cast<char>(0));
233 char * out = output_str.data();
234 bool escaped = false;
235 while (*pc) {
236 if (escaped) {
237 escaped = false;
238 } else if (*pc == '\\') {
239 escaped = true;
240 } else if (*pc == '"') {
241 *out++ = '\\';
242 }
243 *out++ = *pc++;
244 }
245 QString result = QString::fromUtf8(output_str.data());
246 return result;
247 }
248
filterFullPathWithoutTags(const QList<QString> & path,const QString & name)249 QString filterFullPathWithoutTags(const QList<QString> & path, const QString & name)
250 {
251 QStringList noTags = {QString()};
252 for (QString str : path) {
253 if (str.startsWith(WarningPrefix)) {
254 str.remove(0, 1);
255 }
256 noTags.push_back(HtmlTranslator::removeTags(str));
257 }
258 noTags.push_back(HtmlTranslator::removeTags(name));
259 return noTags.join('/');
260 }
261
filterFullPathBasename(const QString & path)262 QString filterFullPathBasename(const QString & path)
263 {
264 QString result = path;
265 result.remove(QRegularExpression("^.*/"));
266 return result;
267 }
268
flattenGmicParameterList(const QList<QString> & list,const QVector<bool> & quotedParameters)269 QString flattenGmicParameterList(const QList<QString> & list, const QVector<bool> & quotedParameters)
270 {
271 QString result;
272 if (list.isEmpty()) {
273 return result;
274 }
275 QList<QString>::const_iterator itList = list.constBegin();
276 QVector<bool>::const_iterator itQuoting = quotedParameters.constBegin();
277 result += (*itQuoting++) ? quotedString(*itList++) : (*itList++);
278 while (itList != list.end()) {
279 result += QString(",%1").arg((*itQuoting++) ? quotedString(*itList++) : (*itList++));
280 }
281 return result;
282 }
283
mergedWithSpace(const QString & prefix,const QString & suffix)284 QString mergedWithSpace(const QString & prefix, const QString & suffix)
285 {
286 if (prefix.isEmpty() || suffix.isEmpty()) {
287 return prefix + suffix;
288 }
289 return prefix + QChar(' ') + suffix;
290 }
291
elided(const QString & text,int width)292 QString elided(const QString & text, int width)
293 {
294 if (text.length() <= width) {
295 return text;
296 }
297 return text.left(std::max(0, width - 3)) + "...";
298 }
299
quotedParameters(const QList<QString> & parameters)300 QVector<bool> quotedParameters(const QList<QString> & parameters)
301 {
302 QVector<bool> result;
303 for (const auto & str : parameters) {
304 result.push_back(str.startsWith("\""));
305 }
306 return result;
307 }
308
mergeSubsequences(const QStringList & sequence,const QVector<int> & subSequenceLengths)309 QStringList mergeSubsequences(const QStringList & sequence, const QVector<int> & subSequenceLengths)
310 {
311 QStringList result;
312 QVector<int> lengths = subSequenceLengths;
313 QStringList::const_iterator itInput = sequence.constBegin();
314 QVector<int>::iterator itLength = lengths.begin();
315 while ((itInput != sequence.constEnd()) && (itLength != lengths.end())) {
316 if (*itLength <= 0) {
317 ++itLength;
318 continue;
319 }
320 QString text = *itInput++;
321 --(*itLength);
322 while (*itLength > 0) {
323 text += QString(",%1").arg(*itInput++);
324 --(*itLength);
325 }
326 result.push_back(text);
327 ++itLength;
328 }
329 if ((itInput != sequence.constEnd()) || (itLength != lengths.end())) {
330 Logger::warning(QObject::tr("List %1 cannot be merged considering theses runs: %2").arg(stringify(sequence)).arg(stringify(subSequenceLengths)));
331 return QStringList();
332 }
333 return result;
334 }
335
completePrefixFromFullList(const QStringList & prefix,const QStringList & fullList)336 QStringList completePrefixFromFullList(const QStringList & prefix, const QStringList & fullList)
337 {
338 if (fullList.size() <= prefix.size()) {
339 return prefix;
340 }
341 QStringList result = prefix;
342 QStringList::const_iterator it = fullList.constBegin();
343 it += prefix.size();
344 while (it != fullList.constEnd()) {
345 result.push_back(*it++);
346 }
347 return result;
348 }
349
quotedString(QString text)350 QString quotedString(QString text)
351 {
352 return QString("\"%1\"").arg(escapeUnescapedQuotes(text));
353 }
354
quotedStringList(const QStringList & stringList)355 QStringList quotedStringList(const QStringList & stringList)
356 {
357 QStringList result;
358 for (const auto & text : stringList) {
359 result.push_back(quotedString(text));
360 }
361 return result;
362 }
363
unescaped(const QString & text)364 QString unescaped(const QString & text)
365 {
366 QByteArray ba = text.toUtf8();
367 cimg_library::cimg::strunescape(ba.data());
368 return QString::fromUtf8(ba.data());
369 }
370
unquoted(const QString & text)371 QString unquoted(const QString & text)
372 {
373 QRegularExpression re("^\\s*\"(.*)\"\\s*$");
374 auto match = re.match(text);
375 if (match.hasMatch()) {
376 return match.captured(1);
377 } else {
378 return text.trimmed();
379 }
380 }
381
expandParameterList(const QStringList & parameters,QVector<int> sizes)382 QStringList expandParameterList(const QStringList & parameters, QVector<int> sizes)
383 {
384 // FIXME : Handle errors here
385 QStringList result;
386 Q_ASSERT_X(parameters.size() == sizes.size(), __PRETTY_FUNCTION__, QString("Sizes are different (parameters: %1, sizes: %2)").arg(parameters.size()).arg(sizes.size()).toStdString().c_str());
387 QStringList::const_iterator itParam = parameters.constBegin();
388 auto itSize = sizes.constBegin();
389 while (itParam != parameters.constEnd() && itSize != sizes.constEnd()) {
390 if (*itSize > 1) {
391 result.append(itParam->split(","));
392 } else if (*itSize == 1) {
393 result.push_back(*itParam);
394 } else {
395 Q_ASSERT_X((*itSize >= 1), __PRETTY_FUNCTION__, QString("Parameter size should be at least 1 (it is %1)").arg(*itSize).toStdString().c_str());
396 }
397 ++itParam;
398 ++itSize;
399 }
400 return result;
401 }
402
403 } // namespace GmicQt
404