Krita Source Code Documentation
Loading...
Searching...
No Matches
SvgStyleParser.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2002-2005, 2007 Rob Buis <buis@kde.org>
3 * SPDX-FileCopyrightText: 2002-2004 Nicolas Goutte <nicolasg@snafu.de>
4 * SPDX-FileCopyrightText: 2005-2006 Tim Beaulen <tbscope@gmail.com>
5 * SPDX-FileCopyrightText: 2005-2009 Jan Hambrecht <jaham@gmx.net>
6 * SPDX-FileCopyrightText: 2005, 2007 Thomas Zander <zander@kde.org>
7 * SPDX-FileCopyrightText: 2006-2007 Inge Wallin <inge@lysator.liu.se>
8 * SPDX-FileCopyrightText: 2007-2008, 2010 Thorsten Zachmann <zachmann@kde.org>
9
10 * SPDX-License-Identifier: LGPL-2.0-or-later
11 */
12
13#include "SvgStyleParser.h"
14#include "SvgLoadingContext.h"
15#include "SvgGraphicContext.h"
16#include "SvgUtil.h"
17
18#include "kis_dom_utils.h"
19
20#include <text/KoSvgText.h>
22
23#include <QStringList>
24#include <QColor>
25#include <QGradientStops>
26#include <KoColor.h>
27
28class Q_DECL_HIDDEN SvgStyleParser::Private
29{
30public:
31 Private(SvgLoadingContext &loadingContext)
32 : context(loadingContext)
33 {
35 textAttributes.removeAll("xml:space");
36
37 // the order of the font attributes is important, don't change without reason !!!
38 fontAttributes << "font-family"
39 << "font-size"
40 << "font-weight"
41 << "font-style"
42 << "font-variant-caps"
43 << "font-variant-alternates"
44 << "font-variant-ligatures"
45 << "font-variant-numeric"
46 << "font-variant-east-asian"
47 << "font-variant-position"
48 << "font-variant"
49 << "font-feature-settings"
50 << "font-stretch"
51 << "font-size-adjust"
52 << "font" // why are we doing this after the rest?
53 << "font-optical-sizing"
54 << "font-variation-settings"
55 << "font-synthesis-weight"
56 << "font-synthesis-style"
57 << "font-synthesis-small-caps"
58 << "font-synthesis-position"
59 << "font-synthesis"
60 << "text-decoration"
61 << "text-decoration-line"
62 << "text-decoration-style"
63 << "text-decoration-color"
64 << "text-decoration-position"
65 << "font-kerning"
66 << "letter-spacing"
67 << "word-spacing"
68 << "baseline-shift"
69 << "vertical-align"
70 << "line-height"
71 << "xml:space"
72 << "white-space"
73 << "text-transform"
74 << "text-indent"
75 << "word-break"
76 << "line-break"
77 << "hanging-punctuation"
78 << "text-align"
79 << "text-align-all"
80 << "text-align-last"
81 << "inline-size"
82 << "overflow"
83 << "text-overflow"
84 << "tab-size"
85 << "overflow-wrap"
86 << "word-wrap"
87 << "text-orientation";
88 // the order of the style attributes is important, don't change without reason !!!
89 styleAttributes << "color" << "display" << "visibility";
90 styleAttributes << "fill" << "fill-rule" << "fill-opacity";
91 styleAttributes << "stroke" << "stroke-width" << "stroke-linejoin" << "stroke-linecap";
92 styleAttributes << "stroke-dasharray" << "stroke-dashoffset" << "stroke-opacity" << "stroke-miterlimit";
93 styleAttributes << "opacity" << "paint-order" << "filter" << "clip-path" << "clip-rule" << "mask";
94 styleAttributes << "shape-inside" << "shape-subtract" << "shape-padding" << "shape-margin";
95 styleAttributes << "marker" << "marker-start" << "marker-mid" << "marker-end" << "krita:marker-fill-method";
96 }
97
102};
103
105 : d(new Private(context))
106{
107
108}
109
111{
112 delete d;
113}
114
115void SvgStyleParser::parseStyle(const SvgStyles &styles, const bool inheritByDefault)
116{
117 SvgGraphicsContext *gc = d->context.currentGC();
118 if (!gc) return;
119
120 if (inheritByDefault) {
123 }
124
125 // make sure we parse the style attributes in the right order
126 Q_FOREACH (const QString & command, d->styleAttributes) {
127 const QString &params = styles.value(command);
128 if (params.isEmpty())
129 continue;
130 parsePA(gc, command, params);
131 }
132}
133
135{
136 SvgGraphicsContext *gc = d->context.currentGC();
137 if (!gc)
138 return;
139
140 // make sure to only parse font attributes here
141 Q_FOREACH (const QString & command, d->fontAttributes) {
142 const QString &params = styles.value(command);
143 if (params.isEmpty())
144 continue;
145 parsePA(gc, command, params);
146 }
147
148 Q_FOREACH (const QString & command, d->textAttributes) {
149 const QString &params = styles.value(command);
150 if (params.isEmpty())
151 continue;
152 parsePA(gc, command, params);
153 }
154}
155#include <kis_debug.h>
156void SvgStyleParser::parsePA(SvgGraphicsContext *gc, const QString &command, const QString &params)
157{
158 QColor fillcolor = gc->fillColor;
159 QColor strokecolor = gc->stroke->color();
160
161 if (params == "inherit")
162 return;
163
164 if (command == "fill") {
165 if (params == "none") {
167 } else if (params.startsWith(QLatin1String("url("))) {
168 unsigned int start = params.indexOf('#') + 1;
169 unsigned int end = params.indexOf(')', start);
170 gc->fillId = params.mid(start, end - start);
172 // check if there is a fallback color
173 parseColor(fillcolor, params.mid(end + 1).trimmed());
174 } else {
175 // great we have a solid fill
177 parseColor(fillcolor, params);
178 }
179 } else if (command == "fill-rule") {
180 if (params == "nonzero")
181 gc->fillRule = Qt::WindingFill;
182 else if (params == "evenodd")
183 gc->fillRule = Qt::OddEvenFill;
184 } else if (command == "stroke") {
185 if (params == "none") {
187 } else if (params.startsWith(QLatin1String("url("))) {
188 unsigned int start = params.indexOf('#') + 1;
189 unsigned int end = params.indexOf(')', start);
190 gc->strokeId = params.mid(start, end - start);
192 // check if there is a fallback color
193 parseColor(strokecolor, params.mid(end + 1).trimmed());
194 } else {
195 // great we have a solid stroke
197 parseColor(strokecolor, params);
198 }
199 } else if (command == "stroke-width") {
200 gc->stroke->setLineWidth(SvgUtil::parseUnitXY(gc, d->context.resolvedProperties(), params));
201 } else if (command == "stroke-linejoin") {
202 if (params == "miter")
203 gc->stroke->setJoinStyle(Qt::MiterJoin);
204 else if (params == "round")
205 gc->stroke->setJoinStyle(Qt::RoundJoin);
206 else if (params == "bevel")
207 gc->stroke->setJoinStyle(Qt::BevelJoin);
208 } else if (command == "stroke-linecap") {
209 if (params == "butt")
210 gc->stroke->setCapStyle(Qt::FlatCap);
211 else if (params == "round")
212 gc->stroke->setCapStyle(Qt::RoundCap);
213 else if (params == "square")
214 gc->stroke->setCapStyle(Qt::SquareCap);
215 } else if (command == "stroke-miterlimit") {
216 gc->stroke->setMiterLimit(params.toFloat());
217 } else if (command == "stroke-dasharray") {
218 QVector<qreal> array;
219 if (params != "none") {
220 QString dashString = params;
221 QStringList dashes = dashString.replace(',', ' ').simplified().split(' ');
222 for (QStringList::Iterator it = dashes.begin(); it != dashes.end(); ++it) {
223 array.append(SvgUtil::parseUnitXY(gc, d->context.resolvedProperties(), *it));
224 }
225
226 // if the array is odd repeat it according to the standard
227 if (array.size() & 1) {
228 array << array;
229 }
230 }
231 gc->stroke->setLineStyle(Qt::CustomDashLine, array);
232 } else if (command == "stroke-dashoffset") {
233 gc->stroke->setDashOffset(params.toFloat());
234 }
235 // handle opacity
236 else if (command == "stroke-opacity")
237 strokecolor.setAlphaF(SvgUtil::fromPercentage(params));
238 else if (command == "fill-opacity") {
239 float opacity = SvgUtil::fromPercentage(params);
240 if (opacity < 0.0)
241 opacity = 0.0;
242 if (opacity > 1.0)
243 opacity = 1.0;
244 fillcolor.setAlphaF(opacity);
245 } else if (command == "opacity") {
246 gc->opacity = SvgUtil::fromPercentage(params);
247 } else if (command == "paint-order") {
248 gc->paintOrder = params;
249 } else if (command == "font-family") {
250 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
251 } else if (command == "font-size") {
252 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
253 } else if (command == "font-style") {
254 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
255
256 } else if (command == "font-variant" || command == "font-variant-caps" || command == "font-variant-alternates" || command == "font-variant-ligatures"
257 || command == "font-variant-numeric" || command == "font-variant-east-asian" || command == "font-variant-position") {
258 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
259
260 } else if (command == "font-feature-settings") {
261 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
262 } else if (command == "font-stretch") {
263 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
264 } else if (command == "font-weight") {
265 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
266 } else if (command == "font-variation-settings") {
267 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
268 } else if (command == "font-optical-sizing") {
269 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
270 } else if (command == "font-size-adjust") {
271 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
272 } else if (command == "font-synthesis"
273 || command == "font-synthesis-weight" || command == "font-synthesis-style"
274 || command == "font-synthesis-small-caps" || command == "font-synthesis-position") {
275 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
276 } else if (command == "font") {
277 qWarning() << "Krita does not support the 'font' shorthand";
278 } else if (command == "text-decoration" || command == "text-decoration-line" || command == "text-decoration-style" || command == "text-decoration-color"
279 || command == "text-decoration-position") {
280 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
281
282 } else if (command == "color") {
283 QColor color;
284 parseColor(color, params);
285 gc->currentColor = color;
286 } else if (command == "display") {
287 if (params == "none")
288 gc->display = false;
289 } else if (command == "visibility") {
290 // visible is inherited!
291 gc->visible = params == "visible";
292 } else if (command == "filter") {
293 if (params != "none" && params.startsWith("url(")) {
294 unsigned int start = params.indexOf('#') + 1;
295 unsigned int end = params.indexOf(')', start);
296 gc->filterId = params.mid(start, end - start);
297 }
298 } else if (command == "clip-path") {
299 if (params != "none" && params.startsWith("url(")) {
300 unsigned int start = params.indexOf('#') + 1;
301 unsigned int end = params.indexOf(')', start);
302 gc->clipPathId = params.mid(start, end - start);
303 }
304 } else if (command == "shape-inside") {
305 gc->shapeInsideValue = params;
306 } else if (command == "shape-subtract") {
307 gc->shapeSubtractValue = params;
308 } else if (command == "clip-rule") {
309 if (params == "nonzero")
310 gc->clipRule = Qt::WindingFill;
311 else if (params == "evenodd")
312 gc->clipRule = Qt::OddEvenFill;
313 } else if (command == "mask") {
314 if (params != "none" && params.startsWith("url(")) {
315 unsigned int start = params.indexOf('#') + 1;
316 unsigned int end = params.indexOf(')', start);
317 gc->clipMaskId = params.mid(start, end - start);
318 }
319 } else if (command == "marker-start") {
320 if (params != "none" && params.startsWith("url(")) {
321 unsigned int start = params.indexOf('#') + 1;
322 unsigned int end = params.indexOf(')', start);
323 gc->markerStartId = params.mid(start, end - start);
324 }
325 } else if (command == "marker-end") {
326 if (params != "none" && params.startsWith("url(")) {
327 unsigned int start = params.indexOf('#') + 1;
328 unsigned int end = params.indexOf(')', start);
329 gc->markerEndId = params.mid(start, end - start);
330 }
331 } else if (command == "marker-mid") {
332 if (params != "none" && params.startsWith("url(")) {
333 unsigned int start = params.indexOf('#') + 1;
334 unsigned int end = params.indexOf(')', start);
335 gc->markerMidId = params.mid(start, end - start);
336 }
337 } else if (command == "marker") {
338 if (params != "none" && params.startsWith("url(")) {
339 unsigned int start = params.indexOf('#') + 1;
340 unsigned int end = params.indexOf(')', start);
341 gc->markerStartId = params.mid(start, end - start);
342 gc->markerMidId = gc->markerStartId;
343 gc->markerEndId = gc->markerStartId;
344 }
345 } else if (command == "font-kerning" || command == "line-height" || command == "white-space" || command == "xml:space" || command == "text-transform" || command == "text-indent"
346 || command == "word-break" || command == "line-break" || command == "hanging-punctuation" || command == "text-align"
347 || command == "text-align-all" || command == "text-align-last" || command == "inline-size" || command == "overflow" || command == "text-overflow"
348 || command == "tab-size" || command == "overflow-wrap" || command == "word-wrap" || command == "vertical-align"
349 || command == "shape-padding" || command == "shape-margin" || command == "text-orientation" || command == "text-rendering") {
350 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
351 } else if (command == "krita:marker-fill-method") {
352 gc->autoFillMarkers = params == "auto";
353 } else if (d->textAttributes.contains(command)) {
354 gc->textProperties.parseSvgTextAttribute(d->context, command, params);
355 }
356
357 gc->fillColor = fillcolor;
358 gc->stroke->setColor(strokecolor);
359}
360
361bool SvgStyleParser::parseColor(QColor &color, const QString &s)
362{
363 if (s.isEmpty() || s == "none")
364 return false;
365
366 KoColor current = KoColor();
367 current.fromQColor(d->context.currentGC()->currentColor);
368 KoColor c = KoColor::fromSVG11(s, d->context.profiles(), current);
369 c.toQColor(&color);
370
371 return true;
372}
373
374QPair<qreal, QColor> SvgStyleParser::parseColorStop(const QDomElement& stop,
375 SvgGraphicsContext *context,
376 qreal& previousOffset)
377{
378 qreal offset = 0.0;
379 QString offsetStr = stop.attribute("offset").trimmed();
380 if (offsetStr.endsWith('%')) {
381 offsetStr = offsetStr.left(offsetStr.length() - 1);
382 offset = offsetStr.toFloat() / 100.0;
383 } else {
384 offset = offsetStr.toFloat();
385 }
386
387 // according to SVG the value must be within [0; 1] interval
388 offset = qBound(0.0, offset, 1.0);
389
390 // according to SVG the stops' offset must be non-decreasing
391 offset = qMax(offset, previousOffset);
392 previousOffset = offset;
393
394 QColor color;
395
396 QString stopColorStr = stop.attribute("stop-color");
397 QString stopOpacityStr = stop.attribute("stop-opacity");
398
399 const QStringList attributes({"stop-color", "stop-opacity"});
400 SvgStyles styles = parseOneCssStyle(stop.attribute("style"), attributes);
401
402 // SVG: CSS values have precedence over presentation attributes!
403 if (styles.contains("stop-color")) {
404 stopColorStr = styles.value("stop-color");
405 }
406
407 if (styles.contains("stop-opacity")) {
408 stopOpacityStr = styles.value("stop-opacity");
409 }
410
411 if (stopColorStr.isEmpty() && stopColorStr == "inherit") {
412 color = context->currentColor;
413 } else {
414 parseColor(color, stopColorStr);
415 }
416
417 if (!stopOpacityStr.isEmpty() && stopOpacityStr != "inherit") {
418 color.setAlphaF(qBound(0.0, KisDomUtils::toDouble(stopOpacityStr), 1.0));
419 }
420 return QPair<qreal, QColor>(offset, color);
421}
422
423#define forEachElement( elem, parent ) \
424 for ( QDomNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) \
425 if ( ( elem = _node.toElement() ).isNull() ) {} else
426
427void SvgStyleParser::parseColorStops(QGradient *gradient,
428 const QDomElement &e,
429 SvgGraphicsContext *context,
430 const QGradientStops &defaultStops)
431{
432 QGradientStops stops;
433
434 qreal previousOffset = 0.0;
435
436 QDomElement stop;
437 forEachElement(stop, e) {
438 if (stop.tagName() == "stop") {
439 stops.append(parseColorStop(stop, context, previousOffset));
440 }
441 }
442
443 if (!stops.isEmpty()) {
444 gradient->setStops(stops);
445 } else {
446 gradient->setStops(defaultStops);
447 }
448}
449
450SvgStyles SvgStyleParser::parseOneCssStyle(const QString &style, const QStringList &interestingAttributes)
451{
452 SvgStyles parsedStyles;
453 if (style.isEmpty()) return parsedStyles;
454
455 QStringList substyles = style.simplified().split(';', Qt::SkipEmptyParts);
456 if (!substyles.count()) return parsedStyles;
457
458 for (QStringList::Iterator it = substyles.begin(); it != substyles.end(); ++it) {
459 QStringList substyle = it->split(':');
460 if (substyle.count() != 2)
461 continue;
462 QString command = substyle[0].trimmed();
463 QString params = substyle[1].trimmed();
464
465 if (interestingAttributes.isEmpty() || interestingAttributes.contains(command)) {
466 parsedStyles[command] = params;
467 }
468 }
469
470 return parsedStyles;
471}
472
474{
475 SvgStyles styleMap;
476
477 // collect individual presentation style attributes which have the priority 0
478 // according to SVG standard
479 // NOTE: font attributes should be parsed the first, because they defines 'em' and 'ex'
480 Q_FOREACH (const QString & command, d->fontAttributes) {
481 const QString attribute = e.attribute(command);
482 if (!attribute.isEmpty())
483 styleMap[command] = attribute;
484 }
485 Q_FOREACH (const QString &command, d->styleAttributes) {
486 const QString attribute = e.attribute(command);
487 if (!attribute.isEmpty())
488 styleMap[command] = attribute;
489 }
490 Q_FOREACH (const QString & command, d->textAttributes) {
491 const QString attribute = e.attribute(command);
492 if (!attribute.isEmpty())
493 styleMap[command] = attribute;
494 }
495
496 // match css style rules to element
497 QStringList cssStyles = d->context.matchingCssStyles(e);
498
499 // collect all css style attributes
500 Q_FOREACH (const QString &style, cssStyles) {
501 QStringList substyles = style.split(';', Qt::SkipEmptyParts);
502 if (!substyles.count())
503 continue;
504 for (QStringList::Iterator it = substyles.begin(); it != substyles.end(); ++it) {
505 QStringList substyle = it->split(':');
506 if (substyle.count() != 2)
507 continue;
508 QString command = substyle[0].trimmed();
509 QString params = substyle[1].trimmed();
510
511 // toggle the namespace selector into the xml-like one
512 command.replace("|", ":");
513
514 // only use style and font attributes
515 if (d->styleAttributes.contains(command) ||
516 d->fontAttributes.contains(command) ||
517 d->textAttributes.contains(command)) {
518
519 styleMap[command] = params;
520 }
521 }
522 }
523
524 // FIXME: if 'inherit' we should just remove the property and use the one from the context!
525
526 // replace keyword "inherit" for style values
527 QMutableMapIterator<QString, QString> it(styleMap);
528 while (it.hasNext()) {
529 it.next();
530 if (it.value() == "inherit") {
531 it.setValue(inheritedAttribute(it.key(), e));
532 }
533 }
534
535 return styleMap;
536}
537
538SvgStyles SvgStyleParser::mergeStyles(const SvgStyles &referencedBy, const SvgStyles &referencedStyles)
539{
540 // 1. use all styles of the referencing styles
541 SvgStyles mergedStyles = referencedBy;
542 // 2. use all styles of the referenced style which are not in the referencing styles
543 SvgStyles::const_iterator it = referencedStyles.constBegin();
544 for (; it != referencedStyles.constEnd(); ++it) {
545 if (!referencedBy.contains(it.key())) {
546 mergedStyles.insert(it.key(), it.value());
547 }
548 }
549 return mergedStyles;
550}
551
552SvgStyles SvgStyleParser::mergeStyles(const QDomElement &e1, const QDomElement &e2)
553{
554 return mergeStyles(collectStyles(e1), collectStyles(e2));
555}
556
557QString SvgStyleParser::inheritedAttribute(const QString &attributeName, const QDomElement &e)
558{
559 QDomNode parent = e.parentNode();
560 while (!parent.isNull()) {
561 QDomElement currentElement = parent.toElement();
562 if (currentElement.hasAttribute(attributeName)) {
563 return currentElement.attribute(attributeName);
564 }
565 parent = currentElement.parentNode();
566 }
567 return QString();
568}
#define forEachElement(elem, parent)
QMap< QString, QString > SvgStyles
static KoColor fromSVG11(const QString value, QHash< QString, const KoColorProfile * > profileList, KoColor current=KoColor())
fromSVG11 Parses a color attribute value and returns a KoColor. SVG defines the colorprofiles elsewhe...
Definition KoColor.cpp:534
void fromQColor(const QColor &c)
Convenient function for converting from a QColor.
Definition KoColor.cpp:213
void toQColor(QColor *c) const
a convenience method for the above.
Definition KoColor.cpp:198
void parseSvgTextAttribute(const SvgLoadingContext &context, const QString &command, const QString &value)
parseSvgTextAttribute add a property according to an XML attribute value.
static QStringList supportedXmlAttributes()
QString clipPathId
the current clip path id
Qt::FillRule fillRule
the current fill rule
Qt::FillRule clipRule
the current clip rule
bool visible
controls visibility of the shape (inherited)
QString fillId
the current fill id (used for gradient/pattern fills)
KoSvgTextProperties textProperties
Stores textProperties.
QString shapeSubtractValue
String of value shape-subtract, will be parsed later.
QString strokeId
the current stroke id (used for gradient strokes)
bool display
controls display of shape
StyleType strokeType
the current stroke type
QString filterId
the current filter id
QString paintOrder
String list indicating paint order;.
QString shapeInsideValue
String of value shape-inside, will be parsed later.
KoShapeStrokeSP stroke
the current stroke
QString clipMaskId
the current clip mask id
StyleType fillType
the current fill type
@ Complex
gradient or pattern style
qreal opacity
the shapes opacity
QColor currentColor
the current color
QColor fillColor
the current fill color. Default is black fill as per svg spec
Contains data used for loading svg.
QPair< qreal, QColor > parseColorStop(const QDomElement &, SvgGraphicsContext *context, qreal &previousOffset)
QStringList textAttributes
text related attributes
QStringList styleAttributes
style related attributes
void parsePA(SvgGraphicsContext *, const QString &, const QString &)
Parses a single style attribute.
Private *const d
SvgStyleParser(SvgLoadingContext &context)
QStringList fontAttributes
font related attributes
QString inheritedAttribute(const QString &attributeName, const QDomElement &e)
Returns inherited attribute value for specified element.
void parseColorStops(QGradient *, const QDomElement &, SvgGraphicsContext *context, const QGradientStops &defaultStops)
Parses gradient color stops.
void parseStyle(const SvgStyles &styles, const bool inheritByDefault=false)
Parses specified style attributes.
bool parseColor(QColor &, const QString &)
Parses a color attribute.
Private(SvgLoadingContext &loadingContext)
SvgStyles collectStyles(const QDomElement &)
Creates style map from given xml element.
SvgStyles parseOneCssStyle(const QString &style, const QStringList &interestingAttributes)
void parseFont(const SvgStyles &styles)
Parses font attributes.
SvgLoadingContext & context
SvgStyles mergeStyles(const SvgStyles &, const SvgStyles &)
Merges two style elements, returning the merged style.
static double fromPercentage(QString s, bool *ok=nullptr)
Definition SvgUtil.cpp:64
static qreal parseUnitXY(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
parses a length attribute in xy-direction
Definition SvgUtil.cpp:322
double toDouble(const QString &str, bool *ok=nullptr)