aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMitch Curtis <mitch.curtis@digia.com>2013-08-01 20:16:58 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-08-05 14:31:03 +0200
commit0cca56f30d6a0290841859a58f517cece13d8d81 (patch)
treeb45edddde4393f4a35fc2d062a9f8b11be90f063
parent467d28a1449e9b67920fc416c232d314ae9bcb96 (diff)
Support multiple quoted font families in Context2D font property.
Currently, it's not possible to specify more than one font family in Context2D's font property. Also, any family with spaces in its name will fail to be read, and quoting the name will also fail. This patch makes it possible to specify several font families as per the CSS shorthand font property spec, as well as quoting of font families with spaces in their names. Task-number: QTBUG-32727 Change-Id: I2bc1f1d2b7f5f8f0519e73f4001b4a8242bb039c Reviewed-by: Karim Pinter <karim.pinter@digia.com> Reviewed-by: Allan Sandfeld Jensen <allan.jensen@digia.com>
-rw-r--r--src/quick/items/context2d/qquickcontext2d.cpp231
-rw-r--r--tests/auto/quick/qquickcanvasitem/data/tst_context.qml77
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 &currentFont) {
- 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 &current
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);
}
}