return QColor();
}
-QFont qt_font_from_string(const QString& fontString) {
- QFont font;
- // ### this is simplified and incomplete
- // ### TODO:get code from Qt webkit
- const QStringList tokens = fontString.split(QLatin1Char(' '));
- foreach (const QString &token, tokens) {
- if (token == QLatin1String("italic"))
- font.setItalic(true);
- else if (token == QLatin1String("bold"))
- font.setBold(true);
- else if (token.endsWith(QLatin1String("px"))) {
- QString number = token;
- number.remove(QLatin1String("px"));
- bool ok = false;
- float pixelSize = number.trimmed().toFloat(&ok);
- if (ok)
- font.setPixelSize(int(pixelSize));
- } else
- font.setFamily(token);
- }
-
- return font;
+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);
+ bool ok = false;
+ float size = value.trimmed().toFloat(&ok);
+ if (ok) {
+ int intSize = int(size);
+ if (unit.compare(QLatin1String("px")) == 0) {
+ font.setPixelSize(intSize);
+ return true;
+ } else if (unit.compare(QLatin1String("pt")) == 0) {
+ font.setPointSize(intSize);
+ return true;
+ }
+ }
+ 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;
+ }
+ if (styleHint != -1) {
+ tmp.setStyleHint(static_cast<QFont::StyleHint>(styleHint));
+ font.setFamily(tmp.defaultFamily());
+ return true;
+ }
+ }
+ qWarning().nospace() << "Context2D: The font family " << fontFamilyToken << " is invalid.";
+ return false;
+}
+enum FontToken
+{
+ NoTokens = 0x00,
+ FontStyle = 0x01,
+ FontVariant = 0x02,
+ FontWeight = 0x04
+};
+
+#define Q_TRY_SET_TOKEN(token, value, setStatement) \
+if (!(usedTokens & token)) { \
+ usedTokens |= token; \
+ setStatement; \
+} else { \
+ qWarning().nospace() << "Context2D: Duplicate token " << QLatin1String(value) << " found in font string."; \
+ return currentFont; \
+}
+
+/*!
+ Parses a font string based on the CSS shorthand font property.
+
+ 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.";
+ return currentFont;
+ }
+
+ QFont newFont;
+ QStringList remainingTokens = tokens;
+ 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();
+ if (token.compare(QLatin1String("normal")) == 0) {
+ if (!(usedTokens & FontStyle) || !(usedTokens & FontVariant) || !(usedTokens & FontWeight)) {
+ // Could be font-style, font-variant or font-weight.
+ if (!(usedTokens & FontStyle)) {
+ // QFont::StyleNormal is the default for QFont::style.
+ usedTokens = usedTokens | FontStyle;
+ } else if (!(usedTokens & FontVariant)) {
+ // QFont::MixedCase is the default for QFont::capitalization.
+ usedTokens |= FontVariant;
+ } else if (!(usedTokens & FontWeight)) {
+ // QFont::Normal is the default for QFont::weight.
+ usedTokens |= FontWeight;
+ }
+ } else {
+ qWarning().nospace() << "Context2D: Duplicate token \"normal\" found in font string.";
+ return currentFont;
+ }
+ } else if (token.compare(QLatin1String("bold")) == 0) {
+ Q_TRY_SET_TOKEN(FontWeight, "bold", newFont.setBold(true))
+ } else if (token.compare(QLatin1String("italic")) == 0) {
+ Q_TRY_SET_TOKEN(FontStyle, "italic", newFont.setStyle(QFont::StyleItalic))
+ } else if (token.compare(QLatin1String("oblique")) == 0) {
+ Q_TRY_SET_TOKEN(FontStyle, "oblique", newFont.setStyle(QFont::StyleOblique))
+ } else if (token.compare(QLatin1String("small-caps")) == 0) {
+ Q_TRY_SET_TOKEN(FontVariant, "small-caps", newFont.setCapitalization(QFont::SmallCaps))
+ } else {
+ bool conversionOk = false;
+ int weight = token.toInt(&conversionOk);
+ if (conversionOk) {
+ if (weight >= 0 && weight <= 99) {
+ Q_TRY_SET_TOKEN(FontWeight, "<font-weight>", newFont.setWeight(weight))
+ continue;
+ } else {
+ qWarning().nospace() << "Context2D: Invalid font weight " << weight << " found in font string; "
+ << "must be between 0 and 99, inclusive.";
+ return currentFont;
+ }
+ }
+ // The token is invalid or in the wrong place/order in the font string.
+ qWarning().nospace() << "Context2D: Invalid or misplaced token " << token << " found in font string.";
+ 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;
+}
class QQuickContext2DEngineData : public QV8Engine::Deletable
{
V8THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "Context2D::caretBlinkRate is not supported");
}
-// text
+
/*!
- \qmlproperty string QtQuick2::Context2D::font
- Holds the current font settings.
+ \qmlproperty string QtQuick2::Context2D::font
+ Holds the current font settings.
+
+ A subset of the
+ \l {http://www.w3.org/TR/2dcontext/#dom-context-2d-font}{w3C 2d context standard for font}
+ is supported:
+
+ \list
+ \li font-style (optional):
+ normal | italic | oblique
+ \li font-variant (optional): normal | small-caps
+ \li font-weight (optional): normal | bold | 0 ... 99
+ \li font-size: Npx | Npt (where N is a positive number)
+ \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.
- The default font value is "10px sans-serif".
- See \l {http://www.w3.org/TR/2dcontext/#dom-context-2d-font}{w3C 2d context standard for font}
+ The default font value is "10px sans-serif".
*/
v8::Handle<v8::Value> ctx2d_font(v8::Local<v8::String>, const v8::AccessorInfo &info)
{
QV8Engine *engine = V8ENGINE_ACCESSOR();
QString fs = engine->toString(value);
- QFont font = qt_font_from_string(fs);
+ QFont font = qt_font_from_string(fs, r->context->state.font);
if (font != r->context->state.font) {
r->context->state.font = font;
}
}
}
+ // See: http://www.w3.org/TR/css3-fonts/#font-prop
TestCase {
name: "ContextFontValidation"
when: canvas.available
var ctx = canvas.getContext("2d");
compare(ctx.font, "sans-serif,-1,10,5,50,0,0,0,0,0");
- ctx.font = "80.1px sans-serif";
- compare(ctx.font, "sans-serif,-1,80,5,50,0,0,0,0,0");
+ ctx.font = "80.1px cursive";
+ // Can't verify the chosen font family since it's different for each platform.
+ compare(ctx.font.substr(ctx.font.indexOf(",") + 1), "-1,80,5,50,0,0,0,0,0");
+ }
+
+ function test_valid() {
+ wait(100);
+ compare(contextSpy.count, 1);
+
+ var ctx = canvas.getContext("2d");
+
+ var validFonts = [
+ { string: "12px sans-serif", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" },
+ { string: "12px serif", expected: "serif,-1,12,5,50,0,0,0,0,0" },
+ { string: "12pt sans-serif", expected: "sans-serif,12,-1,5,50,0,0,0,0,0" },
+ { string: "12pt serif", expected: "serif,12,-1,5,50,0,0,0,0,0" },
+ { string: "normal 12px sans-serif", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" },
+ { string: "normal normal 12px sans-serif", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" },
+ { string: "normal normal normal 12px sans-serif", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" },
+ { string: "italic 12px sans-serif", expected: "sans-serif,-1,12,5,50,1,0,0,0,0" },
+ { string: "italic normal 12px sans-serif", expected: "sans-serif,-1,12,5,50,1,0,0,0,0" },
+ { string: "italic normal normal 12px sans-serif", expected: "sans-serif,-1,12,5,50,1,0,0,0,0" },
+ { string: "oblique 12px sans-serif", expected: "sans-serif,-1,12,5,50,2,0,0,0,0" },
+ { string: "oblique normal 12px sans-serif", expected: "sans-serif,-1,12,5,50,2,0,0,0,0" },
+ { string: "oblique normal normal 12px sans-serif", expected: "sans-serif,-1,12,5,50,2,0,0,0,0" },
+ { 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" },
+ ];
+ for (var i = 0; i < validFonts.length; ++i) {
+ ctx.font = validFonts[i].string;
+ compare(ctx.font.substr(ctx.font.indexOf(",") + 1),
+ validFonts[i].expected.substr(validFonts[i].expected.indexOf(",") + 1));
+ }
+ }
+
+ function test_invalid() {
+ wait(100);
+ compare(contextSpy.count, 1);
+
+ 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 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 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);
+ }
+
+ 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;
+ compare(ctx.font, originalFont);
+ }
}
}
}