diff options
-rw-r--r-- | src/quick/items/context2d/qquickcontext2d.cpp | 231 | ||||
-rw-r--r-- | tests/auto/quick/qquickcanvasitem/data/tst_context.qml | 77 |
2 files changed, 223 insertions, 85 deletions
diff --git a/src/quick/items/context2d/qquickcontext2d.cpp b/src/quick/items/context2d/qquickcontext2d.cpp index e1bf9830e6..428ab08bb6 100644 --- a/src/quick/items/context2d/qquickcontext2d.cpp +++ b/src/quick/items/context2d/qquickcontext2d.cpp @@ -192,57 +192,141 @@ QColor qt_color_from_string(v8::Local<v8::Value> name) return QColor(); } +static int qParseFontSizeFromToken(const QString &fontSizeToken, bool &ok) +{ + ok = false; + float size = fontSizeToken.trimmed().toFloat(&ok); + if (ok) { + return int(size); + } + qWarning().nospace() << "Context2D: A font size of " << fontSizeToken << " is invalid."; + return 0; +} + +/* + Attempts to set the font size of \a font to \a fontSizeToken, returning + \c true if successful. If the font size is invalid, \c false is returned + and a warning is printed. +*/ static bool qSetFontSizeFromToken(QFont &font, const QString &fontSizeToken) { const QString trimmedToken = fontSizeToken.trimmed(); - QString unit = trimmedToken.right(2); - QString value = trimmedToken.left(fontSizeToken.size() - 2); + const QString unitStr = trimmedToken.right(2); + const QString value = trimmedToken.left(trimmedToken.size() - 2); bool ok = false; - float size = value.trimmed().toFloat(&ok); - if (ok) { - int intSize = int(size); - if (unit.compare(QLatin1String("px")) == 0) { - font.setPixelSize(intSize); + int size = 0; + if (unitStr == QStringLiteral("px")) { + size = qParseFontSizeFromToken(value, ok); + if (ok) { + font.setPixelSize(size); return true; - } else if (unit.compare(QLatin1String("pt")) == 0) { - font.setPointSize(intSize); + } + } else if (unitStr == QStringLiteral("pt")) { + size = qParseFontSizeFromToken(value, ok); + if (ok) { + font.setPointSize(size); return true; } + } else { + qWarning().nospace() << "Context2D: Invalid font size unit in font string."; } - qWarning().nospace() << "Context2D: A font size of " << fontSizeToken << " is invalid."; return false; } -static bool qSetFontFamilyFromToken(QFont &font, const QString &fontFamilyToken) -{ - const QString trimmedToken = fontFamilyToken.trimmed(); - QFontDatabase fontDatabase; - if (fontDatabase.hasFamily(trimmedToken)) { - font.setFamily(trimmedToken); - return true; - } else { - // Can't find a family matching this name; if it's a generic family, - // try searching for the default family for it by using style hints. - QFont tmp; - int styleHint = -1; - if (fontFamilyToken.compare(QLatin1String("serif")) == 0) { - styleHint = QFont::Serif; - } else if (fontFamilyToken.compare(QLatin1String("sans-serif")) == 0) { - styleHint = QFont::SansSerif; - } else if (fontFamilyToken.compare(QLatin1String("cursive")) == 0) { - styleHint = QFont::Cursive; - } else if (fontFamilyToken.compare(QLatin1String("monospace")) == 0) { - styleHint = QFont::Monospace; - } else if (fontFamilyToken.compare(QLatin1String("fantasy")) == 0) { - styleHint = QFont::Fantasy; +/* + Returns a list of all of the families in \a fontFamiliesString, where + each family is separated by spaces. Families with spaces in their name + must be quoted. +*/ +static QStringList qExtractFontFamiliesFromString(const QString &fontFamiliesString) +{ + QStringList extractedFamilies; + int quoteIndex = -1; + QString currentFamily; + for (int index = 0; index < fontFamiliesString.size(); ++index) { + const QChar ch = fontFamiliesString.at(index); + if (ch == '"' || ch == '\'') { + if (quoteIndex == -1) { + quoteIndex = index; + } else { + if (ch == fontFamiliesString.at(quoteIndex)) { + // Found the matching quote. +1/-1 because we don't want the quote as part of the name. + const QString family = fontFamiliesString.mid(quoteIndex + 1, index - quoteIndex - 1); + extractedFamilies.push_back(family); + currentFamily.clear(); + quoteIndex = -1; + } else { + qWarning().nospace() << "Context2D: Mismatched quote in font string."; + return QStringList(); + } + } + } else if (ch == ' ' && quoteIndex == -1) { + // This is a space that's not within quotes... + if (!currentFamily.isEmpty()) { + // and there is a current family; consider it the end of the current family. + extractedFamilies.push_back(currentFamily); + currentFamily.clear(); + } // else: ignore the space + } else { + currentFamily.push_back(ch); } - if (styleHint != -1) { - tmp.setStyleHint(static_cast<QFont::StyleHint>(styleHint)); - font.setFamily(tmp.defaultFamily()); + } + if (!currentFamily.isEmpty()) { + if (quoteIndex == -1) { + // This is the end of the string, so add this family to our list. + extractedFamilies.push_back(currentFamily); + } else { + qWarning().nospace() << "Context2D: Unclosed quote in font string."; + return QStringList(); + } + } + if (extractedFamilies.isEmpty()) { + qWarning().nospace() << "Context2D: Missing or misplaced font family in font string" + << " (it must come after the font size)."; + } + return extractedFamilies; +} + +/*! + Tries to set a family on \a font using the families provided in \a fontFamilyTokens. + + The list is ordered by preference, with the first family having the highest preference. + If the first family is invalid, the next family in the list is evaluated. + This process is repeated until a valid font is found (at which point the function + will return \c true and the family set on \a font) or there are no more + families left, at which point a warning is printed and \c false is returned. +*/ +static bool qSetFontFamilyFromTokens(QFont &font, const QStringList &fontFamilyTokens) +{ + foreach (QString fontFamilyToken, fontFamilyTokens) { + QFontDatabase fontDatabase; + if (fontDatabase.hasFamily(fontFamilyToken)) { + font.setFamily(fontFamilyToken); return true; + } else { + // Can't find a family matching this name; if it's a generic family, + // try searching for the default family for it by using style hints. + int styleHint = -1; + if (fontFamilyToken.compare(QLatin1String("serif")) == 0) { + styleHint = QFont::Serif; + } else if (fontFamilyToken.compare(QLatin1String("sans-serif")) == 0) { + styleHint = QFont::SansSerif; + } else if (fontFamilyToken.compare(QLatin1String("cursive")) == 0) { + styleHint = QFont::Cursive; + } else if (fontFamilyToken.compare(QLatin1String("monospace")) == 0) { + styleHint = QFont::Monospace; + } else if (fontFamilyToken.compare(QLatin1String("fantasy")) == 0) { + styleHint = QFont::Fantasy; + } + if (styleHint != -1) { + QFont tmp; + tmp.setStyleHint(static_cast<QFont::StyleHint>(styleHint)); + font.setFamily(tmp.defaultFamily()); + return true; + } } } - qWarning().nospace() << "Context2D: The font family " << fontFamilyToken << " is invalid."; + qWarning("Context2D: The font families specified are invalid: %s", qPrintable(fontFamilyTokens.join(QString()).trimmed())); return false; } @@ -269,18 +353,63 @@ if (!(usedTokens & token)) { \ See: http://www.w3.org/TR/css3-fonts/#font-prop */ static QFont qt_font_from_string(const QString& fontString, const QFont ¤tFont) { - const QStringList tokens = fontString.split(QLatin1Char(' ')); - if (tokens.size() < 2) { - qWarning().nospace() << "Context2D: Insufficent amount of tokens in font string."; + if (fontString.isEmpty()) { + qWarning().nospace() << "Context2D: Font string is empty."; return currentFont; } + // We know that font-size must be specified and it must be before font-family + // (which could potentially have "px" or "pt" in its name), so extract it now. + int fontSizeEnd = fontString.indexOf(QStringLiteral("px")); + if (fontSizeEnd == -1) + fontSizeEnd = fontString.indexOf(QStringLiteral("pt")); + if (fontSizeEnd == -1) { + qWarning().nospace() << "Context2D: Invalid font size unit in font string."; + return currentFont; + } + + int fontSizeStart = fontString.lastIndexOf(' ', fontSizeEnd); + if (fontSizeStart == -1) { + // The font size might be the first token in the font string, which is OK. + // Regardless, we'll find out if the font is invalid with qSetFontSizeFromToken(). + fontSizeStart = 0; + } else { + // Don't want to take the leading space. + ++fontSizeStart; + } + + // + 2 for the unit, +1 for the space that we require. + fontSizeEnd += 3; + QFont newFont; - QStringList remainingTokens = tokens; + if (!qSetFontSizeFromToken(newFont, fontString.mid(fontSizeStart, fontSizeEnd - fontSizeStart))) + return currentFont; + + // We don't want to parse the size twice, so remove it now. + QString remainingFontString = fontString; + remainingFontString.remove(fontSizeStart, fontSizeEnd - fontSizeStart); + + // Next, we have to take any font families out, as QString::split() will ruin quoted family names. + const QString fontFamiliesString = remainingFontString.mid(fontSizeStart); + remainingFontString.chop(remainingFontString.length() - fontSizeStart); + QStringList fontFamilies = qExtractFontFamiliesFromString(fontFamiliesString); + if (fontFamilies.isEmpty()) { + return currentFont; + } + if (!qSetFontFamilyFromTokens(newFont, fontFamilies)) + return currentFont; + + // Now that we've removed the messy parts, we can split the font string on spaces. + const QString trimmedTokensStr = remainingFontString.trimmed(); + if (trimmedTokensStr.isEmpty()) { + // No optional properties. + return newFont; + } + const QStringList tokens = trimmedTokensStr.split(QLatin1Char(' ')); + int usedTokens = NoTokens; // Optional properties can be in any order, but font-size and font-family must be last. - while (remainingTokens.size() > 2) { - const QString token = remainingTokens.takeFirst(); + foreach (const QString token, tokens) { if (token.compare(QLatin1String("normal")) == 0) { if (!(usedTokens & FontStyle) || !(usedTokens & FontVariant) || !(usedTokens & FontWeight)) { // Could be font-style, font-variant or font-weight. @@ -324,19 +453,6 @@ static QFont qt_font_from_string(const QString& fontString, const QFont ¤t return currentFont; } } - if (remainingTokens.size() == 2) { - // Order must be: font-size font-family. - if (!qSetFontSizeFromToken(newFont, remainingTokens.first())) { - return currentFont; - } - if (!qSetFontFamilyFromToken(newFont, remainingTokens.last())) { - return currentFont; - } - return newFont; - } else { - qWarning().nospace() << "Context2D: Missing font-size and/or font-family tokens in font string."; - return currentFont; - } return newFont; } @@ -2205,8 +2321,9 @@ static v8::Handle<v8::Value> ctx2d_caretBlinkRate(const v8::Arguments &args) \li font-family: See \l {http://www.w3.org/TR/CSS2/fonts.html#propdef-font-family} \endlist - Note that font-size and font-family are mandatory and must be in the order - they are shown in above. + \note The font-size and font-family properties are mandatory and must be in + the order they are shown in above. In addition, a font family with spaces in + its name must be quoted. The default font value is "10px sans-serif". */ diff --git a/tests/auto/quick/qquickcanvasitem/data/tst_context.qml b/tests/auto/quick/qquickcanvasitem/data/tst_context.qml index ab351f0de8..b18250291e 100644 --- a/tests/auto/quick/qquickcanvasitem/data/tst_context.qml +++ b/tests/auto/quick/qquickcanvasitem/data/tst_context.qml @@ -111,6 +111,14 @@ Canvas { { string: "bold 12px sans-serif", expected: "sans-serif,-1,12,5,75,0,0,0,0,0" }, { string: "0 12px sans-serif", expected: "sans-serif,-1,12,5,0,0,0,0,0,0" }, { string: "small-caps 12px sans-serif", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + { string: "12px \"sans-serif\"", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + { string: "12px 'sans-serif'", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + // sans-serif will always be chosen, but this still tests that multiple families can be read. + { string: "12px 'sans-serif' 'cursive'", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + { string: "12px sans-serif 'cursive' monospace", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + { string: "12px sans-serif 'cursive' monospace ", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + { string: " 12px sans-serif 'cursive' monospace ", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + { string: " 12px sans-serif 'cursive' monospace ", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" } ]; for (var i = 0; i < validFonts.length; ++i) { ctx.font = validFonts[i].string; @@ -125,38 +133,51 @@ Canvas { var ctx = canvas.getContext("2d"); var originalFont = ctx.font; - var i = 0; - var insufficientQtyTokens = ["", "12px", "sans-serif"]; - for (i = 0; i < insufficientQtyTokens.length; ++i) { - ignoreWarning("Context2D: Insufficent amount of tokens in font string."); - ctx.font = insufficientQtyTokens[i]; - compare(ctx.font, originalFont); - } + var fontStrings = [ + "", + "12px", + "sans-serif", + "z12px sans-serif", + "1z2px sans-serif", + "12zpx sans-serif", + "12pxz sans-serif", + "sans-serif 12px", + "12px !@weeeeeeee!@!@Don'tNameYourFontThis", + "12px )(&*^^^%#$@*!!@#$JSPOR)", + "normal normal normal normal 12px sans-serif", + "normal normal bold bold 12px sans-serif", + "bold bold 12px sans-serif", + "12px 'cursive\"", + "12px 'cursive\" sans-serif", + "12px 'cursive" + ]; - var invalidFontSizes = ["z12px sans-serif", "1z2px sans-serif", "12zpx sans-serif", - "12pzx sans-serif", "12pxz sans-serif", "sans-serif 12px"]; - for (i = 0; i < invalidFontSizes.length; ++i) { - ignoreWarning("Context2D: A font size of \"" + invalidFontSizes[i].split(" ")[0] + "\" is invalid."); - ctx.font = invalidFontSizes[i]; - compare(ctx.font, originalFont); - } + var ignoredWarnings = [ + "Context2D: Font string is empty.", + "Context2D: Missing or misplaced font family in font string (it must come after the font size).", + "Context2D: Invalid font size unit in font string.", + "Context2D: A font size of \"z12\" is invalid.", + "Context2D: A font size of \"1z2\" is invalid.", + "Context2D: A font size of \"12z\" is invalid.", + "Context2D: Invalid font size unit in font string.", + "Context2D: Missing or misplaced font family in font string (it must come after the font size).", + "Context2D: Unclosed quote in font string.", + "Context2D: The font families specified are invalid: )(&*^^^%#$@*!!@#$JSPOR)", + "Context2D: Duplicate token \"normal\" found in font string.", + "Context2D: Duplicate token \"bold\" found in font string.", + "Context2D: Duplicate token \"bold\" found in font string.", + "Context2D: Mismatched quote in font string.", + "Context2D: Mismatched quote in font string.", + "Context2D: Unclosed quote in font string." + ]; - var invalidFontFamilies = ["12px !@weeeeeeee!@!@Don'tNameYourFontThis", "12px )(&*^^^%#$@*!!@#$JSPOR)"]; - for (i = 0; i < invalidFontFamilies.length; ++i) { - ignoreWarning("Context2D: The font family \"" + invalidFontFamilies[i].split(" ")[1] + "\" is invalid."); - ctx.font = invalidFontFamilies[i]; - compare(ctx.font, originalFont); - } + // Sanity check... + compare(ignoredWarnings.length, fontStrings.length); - var duplicates = [ - { duplicate: "normal", string: "normal normal normal normal 12px sans-serif" }, - { duplicate: "bold", string: "normal normal bold bold 12px sans-serif" }, - { duplicate: "bold", string: "bold bold 12px sans-serif" } - ]; - for (i = 0; i < duplicates.length; ++i) { - ignoreWarning("Context2D: Duplicate token \"" + duplicates[i].duplicate + "\" found in font string."); - ctx.font = duplicates[i].string; + for (var i = 0; i < fontStrings.length; ++i) { + ignoreWarning(ignoredWarnings[i]); + ctx.font = fontStrings[i]; compare(ctx.font, originalFont); } } |