PDA

View Full Version : Setting font has no effect in QGraphicsItem subclass paint() method



philw
10th September 2014, 04:39
My custom drawing of a QGraphicsItem includes text using user-configurable fonts (previously picked with QFontDialog).

Attempting to assign the font to either the provided QPainter OR to the QGraphicsScene within my subclass’ QGaphicsItem::paint() method has no effect. The plan is to render a few different text pieces using distinct fonts. Is it possible to do this in QGraphicsItem::paint()? (virtual method override). [See code excerpt below].

I know I could deploy child QGraphicsTextItems or QGraphicsSimpleTextItems. I will if I have to. (Do I have to?).

(We're using Qt 4.8.5 on Windows).


QFont _labelFont;
const QString labelFontStr = labelFontName();
_labelFont.fromString (labelFontStr);

// virtual from QGraphicsItem
void TeacupGfxItem::paint (QPainter* painter,
const QStyleOptionGraphicsItem* /*options*/,
QWidget* /*widget*/ /*=NULL*/)
{
// ... ... ...

// *** NEITHER OF THESE HAVE ANY EFFECT ***
painter->setFont (_labelFont);
scene()->setFont (_labelFont);

const QString labelStr = teacupLabelStr();
static const int textFlags = Qt::AlignHCenter | Qt::AlignTop |
Qt::TextDontClip | Qt::TextSingleLine;
QRectF drawnBoundRect;
painter->setPen (QPen (Qt::black));
painter->drawText (_labelRect, textFlags, labelStr, &drawnBoundRect);
}

d_stranz
10th September 2014, 07:03
QFont _labelFont;
const QString labelFontStr = labelFontName();
_labelFont.fromString (labelFontStr);


Where does this code live? Does it just sort of float there in limbo at the top of the file? Is it part of some other method?

Read the description of QFont::fromString(). Simply passing in the name of the font is not a valid argument, so you are almost certainly ending up with an invalid font instance, which of course will not be used by the painter.

philw
10th September 2014, 22:23
d_stranz, thanks for your reply!

I am INDEED having a QFont problem, right in the area your response focused on. This is my own application-level mistake ... the string is actually our own generated CSS Font Declaration thingy ... e.g. "font: italic medium 30px Verdana, Helvetica, sans-serif;" ... for which we, unfortunately, don't have a way to generate the original QFont. But the point here is that, nope, I cannot use that string in a call to QFont::fromString (const QString& descrip).

[Wouldn't in be great to have heuristic CSS font declaration encoding/decoding support in QFont !]

Sorry about the confusion of the initial disembodied statements. QFont _labelFont (and also QRectF _labelRect) are fields in my QGraphicsItem subclass, and the related statements appear elsewhere in that class.

To clarify, in fact, Calling QPainter::setFont() within a QGraphicsItem::paint() implementation — with a VALID QFont instance — IS WORKING for me. Thank you.

I had cross posted on the Qt Project Forum, on which there were also useful responses. SEE: https://qt-project.org/forums/viewthread/47233/

- Phil

d_stranz
10th September 2014, 22:43
It should not be difficult to parse out the fields in the CSS font declaration, using a combination of QString::split(), a lookup table of known tokens ("italic", "medium", etc.) and font names, and a QRegExp regular expression matcher for the variable bits ("30px"). There are QFont methods to turn each of those into a setting.

By the way, it is considered dangerous (or at best bad form) to begin variable names with an underscore. Many compiler name-mangling conventions add one or more underscore prefixes and you could unwittingly end up in conflict with an internal variable. Trailing and embedded underscores are fine.

The comment in the Qt Project post regarding setFont() and drawText() performance isn't valid, in my opinion. At some level, these calls (or the platform-specific equivalent) must be made whenever a font changes and text is painted, so it doesn't matter whether you call them or the internals do (e.g. when painting a QGraphicsTextItem). What will cost a slight performance hit is constructing QFont and other instances within the paint() methods. If these can be constructed once and then simply used within paint() then you avoid that overhead.

philw
16th September 2014, 20:45
Below is my generally successful attempt at basic QFont / CSS font specification encoding/decoding functions. (d_stranz, thanks for the nudge).

I did not find a way of providing a list of "fallback" font faces from a QFont or QFontInfo instance. (The QFont::substitutes() method apparently doesn’t do that). This is not a problem for my current need since I am just trying to round-trip a QFont created with QFontDialog through a CSS font spec serialization. But for more general use, it sure would be great to get fallback font faces into the CSS font spec. Any ideas?

Short of that, is there a general way to determine if a given QFont is a serif or sans-serif font?

Below are my header file and the two separate methods in a HtmlCssUtils namespace:


// File: HtmlCssUtils.hpp

#pragma once
#ifndef HtmlCssUtilsINCLUDED
#define HtmlCssUtilsINCLUDED

#include <QString>
#include <QFont>

namespace HtmlCssUtils
{
QString encodeCssFont (const QFont& refFont);
QFont decodeCssFontString (const QString& cssFontStr);
}

#endif

// File: HtmlCssUtils.cpp

#include "HtmlCssUtils.hpp"
#include <QList>
#include <QStringList>

QString HtmlCssUtils::encodeCssFont (const QFont& refFont)
{
//-----------------------------------------------------------------------
// This function assembles a CSS Font specification string from a QFont.
// This supports most of the QFont attributes settable in the Qt 4.8 and
// Qt 5.3 QFontDialog.
//
// (1) Font Family
// (2) Font Weight (just bold or not)
// (3) Font Style (possibly Italic or Oblique)
// (4) Font Size (in either pixels or points)
// (5) Decorations (possibly Underline or Strikeout)
//
// Not supported: Writing System (e.g. Latin).
//
// See the corresponding decode function, below.
// QFont decodeCssFontString (const QString cssFontStr)
//-----------------------------------------------------------------------

QStringList fields; // CSS font attribute fields

// ************************************************** *
// *** (1) Font Family: Primary plus Substitutes ***
// ************************************************** *

const QString family = refFont.family();

// NOTE [9-2014, Qt 4.8.6]: This isn't what I thought it was. It
// does not return a list of "fallback" font faces (e.g. Georgia,
// Serif for "Times New Roman"). In my testing, this is always
// returning an empty list.
//
QStringList famSubs = QFont::substitutes (family);

if (!famSubs.contains (family))
famSubs.prepend (family);

static const QChar DBL_QUOT ('"');
const int famCnt = famSubs.count();
QStringList famList;
for (int inx = 0; inx < famCnt; ++inx)
{
// Place double quotes around family names having space characters,
// but only if double quotes are not already there.
//
const QString fam = famSubs [inx];
if (fam.contains (' ') && !fam.startsWith (DBL_QUOT))
famList << (DBL_QUOT + fam + DBL_QUOT);
else
famList << fam;
}

const QString famStr = QString ("font-family: ") + famList.join (", ");
fields << famStr;

// **************************************
// *** (2) Font Weight: Bold or Not ***
// **************************************

const bool bold = refFont.bold();
if (bold)
fields << "font-weight: bold";

// ************************************************** **
// *** (3) Font Style: possibly Italic or Oblique ***
// ************************************************** **

const QFont::Style style = refFont.style();
switch (style)
{
case QFont::StyleNormal: break;
case QFont::StyleItalic: fields << "font-style: italic"; break;
case QFont::StyleOblique: fields << "font-style: oblique"; break;
}

// ************************************************
// *** (4) Font Size: either Pixels or Points ***
// ************************************************

const double sizeInPoints = refFont.pointSizeF(); // <= 0 if not defined.
const int sizeInPixels = refFont.pixelSize(); // <= 0 if not defined.
if (sizeInPoints > 0.0)
fields << QString ("font-size: %1pt") .arg (sizeInPoints);
else if (sizeInPixels > 0)
fields << QString ("font-size: %1px") .arg (sizeInPixels);

// ***********************************************
// *** (5) Decorations: Underline, Strikeout ***
// ***********************************************

const bool underline = refFont.underline();
const bool strikeOut = refFont.strikeOut();

if (underline && strikeOut)
fields << "text-decoration: underline line-through";
else if (underline)
fields << "text-decoration: underline";
else if (strikeOut)
fields << "text-decoration: line-through";

const QString cssFontStr = fields.join ("; ");
return cssFontStr;
}

QFont HtmlCssUtils::decodeCssFontString (const QString& cssFontStr)
{
//-----------------------------------------------------------------------
// This function creates a QFont from the provided CSS Font specification
// string. This supports most of the QFont attributes settable in the
// Qt 4.8 and Qt 5.3 QFontDialog.
//
// (1) Font Family
// (2) Font Weight (just bold or not)
// (3) Font Style (possibly Italic or Oblique)
// (4) Font Size (in either pixels or points)
// (5) Decorations (possibly Underline or Strikeout)
//
// Not supported (defaulted): Writing System (e.g. Latin).
//
// See the corresponding encode function, above.
// QString encodeCssFont (const QFont&)
//-----------------------------------------------------------------------

QFont retFont;

QStringList fields = cssFontStr .split (';');
const int fieldCnt = fields.count();

for (int inx = 0; inx < fieldCnt; ++inx)
{
const QString field = fields [inx] .trimmed();
if (field.isEmpty()) continue;
//----------------------------

const QStringList keyAndValue = field.split (':');
if (keyAndValue.count() != 2) continue;
//-------------------------------------

const QString key = keyAndValue [0] .trimmed() .toLower();
const QString val = keyAndValue [1] .trimmed();
const QString valLower = val .toLower();

// *************************
// *** (1) Font Family ***
// *************************

if (key .contains ("font-family"))
{
QStringList famList = val.split (',');
if (!famList.isEmpty())
{
// Use only the first family in a comma separated list
QString fam = famList [0] .trimmed();

// Remove leading and trailing double quotes
static const QChar DBL_QUOT ('"');
if (fam .startsWith (DBL_QUOT))
fam = fam .mid (1) .trimmed();
if (fam .endsWith (DBL_QUOT))
{ fam .chop (1); fam = fam.trimmed(); }

if (!fam.isEmpty())
retFont .setFamily (fam);
}
}

// ********************************
// *** (2) Font Weight (Bold) ***
// ********************************

else if (key .contains ("font-weight"))
{
if (valLower .contains ("bold"))
retFont .setBold (true);
}

// ********************************************
// *** (3) Font Style (Italic or Oblique) ***
// ********************************************

else if (key .contains ("font-style"))
{
if (valLower .contains ("italic"))
retFont .setStyle (QFont::StyleItalic);
else if (valLower .contains ("oblique"))
retFont .setStyle (QFont::StyleOblique);
}

// ************************************************
// *** (4) Font Size: either Pixels or Points ***
// ************************************************

else if (key .contains ("font-size"))
{
bool numOk (false);
QString numPart (valLower);
numPart.chop (2);
const double num = numPart.toDouble (&numOk);

if (numOk && (num > 0.0))
{
if (valLower.endsWith ("px"))
retFont .setPixelSize (int (num));
else if (valLower.endsWith ("pt"))
retFont .setPointSizeF (num);
}
}

// ***********************************************
// *** (5) Decorations: Underline, Strikeout ***
// ***********************************************

else if (key .contains ("text-decoration"))
{
if (valLower.contains ("underline"))
retFont .setUnderline (true);
if (valLower.contains ("line-through"))
retFont .setStrikeOut (true);
}

} // end for (int inx = 0; inx < fieldCnt; ++inx)

return retFont;
}

d_stranz
16th September 2014, 23:37
is there a general way to determine if a given QFont is a serif or sans-serif font?

QFontInfo and QFontDatabase might be able to tell you some of that.