Reputation: 1049
I`m trying to reproduce an icon from FontAwesome.
First I load the font and get the points:
int fa = QFontDatabase::addApplicationFont(":/fonts/fa-solid-900.ttf");
auto fas = QFontDatabase::applicationFontFamilies(fa).at(0);
QRawFont rfont = QRawFont::fromFont(fas);
qDebug() << rfont.pathForGlyph(0);
Result being:
QPainterPath: Element count=45
-> MoveTo(x=10.5, y=-14)
-> LineTo(x=1.5, y=-14)
-> CurveTo(x=1.08333, y=-13.9792)
-> CurveToData(x=0.729167, y=-13.8333)
-> CurveToData(x=0.4375, y=-13.5625)
-> CurveTo(x=0.166667, y=-13.2708)
-> CurveToData(x=0.0208333, y=-12.9167)
-> CurveToData(x=0, y=-12.5)
-> LineTo(x=0, y=0.5)
-> CurveTo(x=0.0208333, y=0.916667)
-> CurveToData(x=0.166667, y=1.27083)
-> CurveToData(x=0.4375, y=1.5625)
-> CurveTo(x=0.729167, y=1.83333)
-> CurveToData(x=1.08333, y=1.97917)
-> CurveToData(x=1.5, y=2)
-> LineTo(x=10.5, y=2)
-> CurveTo(x=10.9167, y=1.97917)
-> CurveToData(x=11.2708, y=1.83333)
-> CurveToData(x=11.5625, y=1.5625)
-> CurveTo(x=11.8333, y=1.27083)
-> CurveToData(x=11.9792, y=0.916667)
-> CurveToData(x=12, y=0.5)
-> LineTo(x=12, y=-12.5)
-> CurveTo(x=11.9792, y=-12.9167)
-> CurveToData(x=11.8333, y=-13.2708)
-> CurveToData(x=11.5625, y=-13.5625)
-> CurveTo(x=11.2708, y=-13.8333)
-> CurveToData(x=10.9167, y=-13.9792)
-> CurveToData(x=10.5, y=-14)
-> MoveTo(x=8.8125, y=-12)
-> LineTo(x=6, y=-7.8125)
-> LineTo(x=3.21875, y=-12)
-> LineTo(x=8.8125, y=-12)
-> MoveTo(x=2, y=-10.1875)
-> LineTo(x=4.8125, y=-6)
-> LineTo(x=2, y=-1.8125)
-> LineTo(x=2, y=-10.1875)
-> MoveTo(x=3.21875, y=0)
-> LineTo(x=6, y=-4.1875)
-> LineTo(x=8.8125, y=0)
-> LineTo(x=3.21875, y=0)
-> MoveTo(x=10, y=-1.8125)
-> LineTo(x=7.21875, y=-6)
-> LineTo(x=10, y=-10.1875)
-> LineTo(x=10, y=-1.8125)
Then I am trying to reproduce it:
QPainterPath* polygonPath = new QPainterPath();
polygonPath->moveTo(10.5, -14);
polygonPath->lineTo(1.5, -14);
polygonPath->CurveTo(1.08333, -13.9792);
polygonPath->CurveToData(0.729167, -13.8333);
polygonPath->CurveToData(0.4375, -13.5625);
polygonPath->CurveTo(0.166667, -13.2708);
polygonPath->CurveToData(0.0208333, -12.9167);
polygonPath->CurveToData(0, -12.5);
polygonPath->lineTo(0, 0.5);
polygonPath->CurveTo(0.0208333, 0.916667);
polygonPath->CurveToData(0.166667, 1.27083);
polygonPath->CurveToData(0.4375, 1.5625);
polygonPath->CurveTo(0.729167, 1.83333);
polygonPath->CurveToData(1.08333, 1.97917);
polygonPath->CurveToData(1.5, 2);
polygonPath->lineTo(10.5, 2);
polygonPath->CurveTo(10.9167, 1.97917);
polygonPath->CurveToData(11.2708, 1.83333);
polygonPath->CurveToData(11.5625, 1.5625);
polygonPath->CurveTo(11.8333, 1.27083);
polygonPath->CurveToData(11.9792, 0.916667);
polygonPath->CurveToData(12, 0.5);
polygonPath->lineTo(12, -12.5);
polygonPath->CurveTo(11.9792, -12.9167);
polygonPath->CurveToData(11.8333, -13.2708);
polygonPath->CurveToData(11.5625, -13.5625);
polygonPath->CurveTo(11.2708, -13.8333);
polygonPath->CurveToData(10.9167, -13.9792);
polygonPath->CurveToData(10.5, -14);
polygonPath->moveTo(8.8125, -12);
polygonPath->lineTo(6, -7.8125);
polygonPath->lineTo(3.21875, -12);
polygonPath->lineTo(8.8125, -12);
polygonPath->moveTo(2, -10.1875);
polygonPath->lineTo(4.8125, -6);
polygonPath->lineTo(2, -1.8125);
polygonPath->lineTo(2, -10.1875);
polygonPath->moveTo(3.21875, 0);
polygonPath->lineTo(6, -4.1875);
polygonPath->lineTo(8.8125, 0);
polygonPath->lineTo(3.21875, 0);
polygonPath->moveTo(10, -1.8125);
polygonPath->lineTo(7.21875, -6);
polygonPath->lineTo(10, -10.1875);
polygonPath->lineTo(10, -1.8125);
polygonPath->closeSubpath();
Unfortunately I have no idea what to use instead of CurveTo and CurveToData. I have been reading the docs but just cannot understand it.
What I want to achieve is to obtain the icon points so I can draw it as a polygon. Similar to this:
QPolygon symbol;
symbol << QPoint(0.65, -9.75)
<< QPoint(5.85, -9.75)
<< QPoint(5.85, 0)
<< QPoint(0.65, 0);
painter.drawPolygon(symbol);
If someone would be so kind to provide a solution that would be amazing.
Upvotes: 1
Views: 254
Reputation: 20141
For the other answer about QPainterPath, I got the feedback that the actual issue of the OP seems to be to retrieve the painter path for the intended glyph.
Thus, I modified the MCVE to draw the painter path of a selected character.
First, I used the MS Character Table to chose a nice character:
The relevant info is the Unicode code point shown in the bottom status line: U+2620
.
Then I modified the makeSample()
to use that character for retrieval of the corresponding painter path:
QPainterPath makeSample()
{
// get a font which should be able to provide most of the Unicode code points
QFont qFont("Arial Unicode MS");
// transform font into a raw font
QRawFont qRawFont = QRawFont::fromFont(qFont);
// build a Qt string with the intended character
QString qText(1, QChar(0x2620)); // 0x2620 is the Unicode code point for Skull
// retrieve the glyph index for the character
QVector<quint32> qGlyphIndices = qRawFont.glyphIndexesForString(qText);
// build the Qt painter path
QPainterPath qPainterPath;
for (quint32 qGlyphIndex : qGlyphIndices) {
qPainterPath += qRawFont.pathForGlyph(qGlyphIndex);
}
// done
return qPainterPath;
}
The first result was a Chinese glyph until I realized that the glyph index has nothing to do with the Unicode code point. (I had used 0x2620
(the Unicode code point) as glyph index.) Thus, I introduced the conversion with QRawFont::glyphIndexesForString().
Similar to what OP described, the next thing I got were some lines in the upper left corner. With a short glance onto the dumped lists, I realized that there are y coordinates in the dump with negative numbers i.e. rendered beyond the top border of my Canvas
widget.
Hence, I tweaked the rendering a bit until I found it acceptable somehow.
The whole MCVE:
#include <QtWidgets>
class Canvas: public QWidget {
private:
QPainterPath *_pQPainterPath;
public:
explicit Canvas(QPainterPath* pQPainterPath, QWidget* pQParent = nullptr):
QWidget(pQParent),
_pQPainterPath(pQPainterPath)
{ }
protected:
virtual void paintEvent(QPaintEvent* pQEvent) override;
};
void Canvas::paintEvent(QPaintEvent* pQEvent)
{
if (!_pQPainterPath) return;
const QRectF qRect = _pQPainterPath->boundingRect();
const qreal scale
= std::min(width() / qRect.width(), height() / qRect.height());
const QPointF center(0.5 * width(), 0.5 * height());
QPainter qPainter(this);
QPen qPen(Qt::blue);
qPen.setCosmetic(true);
qPainter.setPen(qPen);
qPainter.translate(center);
qPainter.scale(scale, scale);
qPainter.translate(-qRect.center());
qPainter.drawPath(*_pQPainterPath);
}
const char* toText(QPainterPath::ElementType type)
{
static const char* const texts[] = {
"MoveTo", "LineTo", "CurveTo", "CurveToData"
};
const size_t n = std::size(texts);
return type < n ? texts[type] : "???";
}
QString list(const QPainterPath& qPainterPath)
{
QString qText;
QTextStream qOut(&qText);
for (int i = 0, n = qPainterPath.elementCount(); i < n; ++i) {
const QPainterPath::Element& qPPElem = qPainterPath.elementAt(i);
qOut << toText(qPPElem.type) << "(x=" << qPPElem.x << ", y=" << qPPElem.y << ")\n";
}
return qText;
}
QString listSrc(const QPainterPath& qPainterPath)
{
QString qText;
QTextStream qOut(&qText);
qOut << "QPainterPath qPainterPath;\n";
for (int i = 0, n = qPainterPath.elementCount(); i < n; ++i) {
const QPainterPath::Element& qPPElem = qPainterPath.elementAt(i);
switch (qPPElem.type) {
case QPainterPath::MoveToElement:
qOut << "qPainterPath.moveTo(" << qPPElem.x << ", " << qPPElem.y << ");\n";
break;
case QPainterPath::LineToElement:
qOut << "qPainterPath.lineTo(" << qPPElem.x << ", " << qPPElem.y << ");\n";
break;
case QPainterPath::CurveToElement: {
if (i + 2 >= n
|| qPainterPath.elementAt(i + 1).type != QPainterPath::CurveToDataElement
|| qPainterPath.elementAt(i + 2).type != QPainterPath::CurveToDataElement) {
qOut << "// BROKEN qPainterPath.cubicTo();\n";
break; // ERROR!
}
const QPainterPath::Element& qPPElem1 = qPainterPath.elementAt(++i);
const QPainterPath::Element& qPPElem2 = qPainterPath.elementAt(++i);
qOut << "qPainterPath.cubicTo(" << qPPElem.x << ", " << qPPElem.y << ", "
<< qPPElem1.x << ", " << qPPElem1.y << ", "
<< qPPElem2.x << ", " << qPPElem2.y << ");\n";
} break;
}
}
return qText;
}
QPainterPath makeSample()
{
QFont qFont("Arial Unicode MS");
QRawFont qRawFont = QRawFont::fromFont(qFont);
QString qText(1, QChar(0x2620)); // 0x2620 is the Unicode code point for Skull
QVector<quint32> qGlyphIndices = qRawFont.glyphIndexesForString(qText);
QPainterPath qPainterPath;
for (quint32 qGlyphIndex : qGlyphIndices) {
qPainterPath += qRawFont.pathForGlyph(qGlyphIndex);
}
return qPainterPath;
}
int main(int argc, char** argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup data
QPainterPath qPainterPath = makeSample();
qDebug() << qPainterPath;
// setup GUI
QMainWindow qWinMain;
qWinMain.setWindowTitle("Test QRawFont - QPainterPath");
qWinMain.resize(480, 280);
QSplitter qVSplit(Qt::Vertical);
Canvas canvas(&qPainterPath);
qVSplit.addWidget(&canvas);
QSplitter qHSplit(Qt::Horizontal);
QPlainTextEdit qEditCode;
qEditCode.setPlainText(listSrc(qPainterPath));
qHSplit.addWidget(&qEditCode);
QPlainTextEdit qEditPath;
qEditPath.setPlainText(list(qPainterPath));
qHSplit.addWidget(&qEditPath);
qVSplit.addWidget(&qHSplit);
qVSplit.setSizes({ 100, 180 });
qWinMain.setCentralWidget(&qVSplit);
qWinMain.show();
// runtime loop
return app.exec();
}
Output:
There is for sure a similarity though the rendering might not be perfect.
This is how it looks if a yellow brush is used with the blue pen.
(I had removed the brush while fiddling as the result didn't match my expectation until I added the qPen.setCosmetic(true);
.)
Upvotes: 1
Reputation: 20141
I must admit that all the guessing in my comments could have been prevented as the doc. about QPainterPath
mentions explicitly how it works:
enum QPainterPath::ElementType
This enum describes the types of elements used to connect vertices in subpaths.
Note that elements added as closed subpaths using the addEllipse(), addPath(), addPolygon(), addRect(), addRegion() and addText() convenience functions, is actually added to the path as a collection of separate elements using the moveTo(), lineTo() and cubicTo() functions.
Constant Value Description QPainterPath::MoveToElement
0 A new subpath. See also moveTo(). QPainterPath::LineToElement
1 A line. See also lineTo(). QPainterPath::CurveToElement
2 A curve. See also cubicTo() and quadTo(). QPainterPath::CurveToDataElement
3 The extra data required to describe a curve in a CurveToElement element.
I made my own MCVE to illustrate this:
#include <QtWidgets>
class Canvas: public QWidget {
private:
QPainterPath *_pQPainterPath;
public:
explicit Canvas(QPainterPath* pQPainterPath, QWidget* pQParent = nullptr):
QWidget(pQParent),
_pQPainterPath(pQPainterPath)
{ }
protected:
virtual void paintEvent(QPaintEvent* pQEvent) override;
};
void Canvas::paintEvent(QPaintEvent* pQEvent)
{
QPainter qPainter(this);
qPainter.setPen(
QPen(
QColor(79, 106, 25), 1,
Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
qPainter.setBrush(QColor(122, 163, 39));
if (_pQPainterPath) qPainter.drawPath(*_pQPainterPath);
}
const char* toText(QPainterPath::ElementType type)
{
static const char* const texts[] = {
"MoveTo", "LineTo", "CurveTo", "CurveToData"
};
const size_t n = std::size(texts);
return type < n ? texts[type] : "???";
}
QString list(const QPainterPath& qPainterPath)
{
QString qText;
QTextStream qOut(&qText);
for (int i = 0, n = qPainterPath.elementCount(); i < n; ++i) {
const QPainterPath::Element& qPPElem = qPainterPath.elementAt(i);
qOut << toText(qPPElem.type) << "(x=" << qPPElem.x << ", y=" << qPPElem.y << ")\n";
}
return qText;
}
QString listSrc(const QPainterPath& qPainterPath)
{
QString qText;
QTextStream qOut(&qText);
qOut << "QPainterPath qPainterPath;\n";
for (int i = 0, n = qPainterPath.elementCount(); i < n; ++i) {
const QPainterPath::Element& qPPElem = qPainterPath.elementAt(i);
switch (qPPElem.type) {
case QPainterPath::MoveToElement:
qOut << "qPainterPath.moveTo(" << qPPElem.x << ", " << qPPElem.y << ");\n";
break;
case QPainterPath::LineToElement:
qOut << "qPainterPath.lineTo(" << qPPElem.x << ", " << qPPElem.y << ");\n";
break;
case QPainterPath::CurveToElement: {
if (i + 2 >= n
|| qPainterPath.elementAt(i + 1).type != QPainterPath::CurveToDataElement
|| qPainterPath.elementAt(i + 2).type != QPainterPath::CurveToDataElement) {
qOut << "// BROKEN qPainterPath.cubicTo();\n";
break; // ERROR!
}
const QPainterPath::Element& qPPElem1 = qPainterPath.elementAt(++i);
const QPainterPath::Element& qPPElem2 = qPainterPath.elementAt(++i);
qOut << "qPainterPath.cubicTo(" << qPPElem.x << ", " << qPPElem.y << ", "
<< qPPElem1.x << ", " << qPPElem1.y << ", "
<< qPPElem2.x << ", " << qPPElem2.y << ");\n";
} break;
}
}
return qText;
}
QPainterPath makeSample()
{
#if 0 // ORIGINAL:
// taken from https://doc.qt.io/qt-5/qpainterpath.html#details
QPainterPath qPainterPath;
qPainterPath.addRect(20, 20, 60, 60);
qPainterPath.moveTo(0, 0);
qPainterPath.cubicTo(99, 0, 50, 50, 99, 99);
qPainterPath.cubicTo(0, 99, 50, 50, 0, 0);
#else // DUMPED in listSrc:
QPainterPath qPainterPath;
qPainterPath.moveTo(20, 20);
qPainterPath.lineTo(80, 20);
qPainterPath.lineTo(80, 80);
qPainterPath.lineTo(20, 80);
qPainterPath.lineTo(20, 20);
qPainterPath.moveTo(0, 0);
qPainterPath.cubicTo(99, 0, 50, 50, 99, 99);
qPainterPath.cubicTo(0, 99, 50, 50, 0, 0);
#endif // 0
return qPainterPath;
}
int main(int argc, char** argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup data
QPainterPath qPainterPath = makeSample();
// setup GUI
QMainWindow qWinMain;
qWinMain.setWindowTitle("Test QPainterPath");
qWinMain.resize(480, 280);
QSplitter qVSplit(Qt::Vertical);
Canvas canvas(&qPainterPath);
qVSplit.addWidget(&canvas);
QSplitter qHSplit(Qt::Horizontal);
QPlainTextEdit qEditCode;
qEditCode.setPlainText(listSrc(qPainterPath));
qHSplit.addWidget(&qEditCode);
QPlainTextEdit qEditPath;
qEditPath.setPlainText(list(qPainterPath));
qHSplit.addWidget(&qEditPath);
qVSplit.addWidget(&qHSplit);
qVSplit.setSizes({ 100, 180 });
qWinMain.setCentralWidget(&qVSplit);
qWinMain.show();
// runtime loop
return app.exec();
}
Output:
Qt Version: 5.15.1
QPainterPath: Element count=12
-> MoveTo(x=20, y=20)
-> LineTo(x=80, y=20)
-> LineTo(x=80, y=80)
-> LineTo(x=20, y=80)
-> LineTo(x=20, y=20)
-> MoveTo(x=0, y=0)
-> CurveTo(x=99, y=0)
-> CurveToData(x=50, y=50)
-> CurveToData(x=99, y=99)
-> CurveTo(x=0, y=99)
-> CurveToData(x=50, y=50)
-> CurveToData(x=0, y=0)
This is the output for the original sample (taken from Qt doc.).
This is the output for the source code dumped in the lower left text view and copied into the source.
(Some kind of bootstrap? ;-))
Upvotes: 1