29 Commits
1.0 ... 1.3

Author SHA1 Message Date
5ccb93bd59 Use the official IANA MIME-type and extension 2019-02-26 22:20:12 +01:00
c3df78a455 Added support for named (HTML) colors 2019-02-26 22:18:07 +01:00
f96b530fa3 Merge branch 'master' of https://github.com/tumic0/QtPBFImagePlugin 2019-02-22 08:47:29 +01:00
9fdfd35752 Removed obsolete code 2019-02-22 08:47:01 +01:00
20f2e7c7d2 Refer to PBF also as MVT 2019-01-03 01:44:57 +01:00
75e6da8903 Fixed build with older Google protocol buffers 2019-01-01 21:45:53 +01:00
c8e8f0013f Text rendering cosmetics 2019-01-01 21:27:54 +01:00
a7294b30f8 Improved performance 2019-01-01 21:27:07 +01:00
2a89ef8dfd Do not try to open empty filenames
(fixes warning)
2018-12-22 08:17:24 +01:00
54176640d0 cosmetics 2018-12-17 21:59:06 +01:00
4042b3ccdf New minimal Qt version is 5.4 2018-12-17 18:36:45 +01:00
c1af3d82a5 Added support for QT < 5.6 (and >= 5.4) 2018-12-17 18:31:26 +01:00
368c53307d Add protobuf path on OS X
Disable linux build until a host with QT >= 5.6 is available
2018-12-17 17:31:33 +01:00
75e61e956e 16.04 is not enaugh 2018-12-16 22:34:02 +01:00
094641eb4b Use Ubuntu 16.04 2018-12-16 22:29:50 +01:00
cdf40f774b Travis CI linux build fix 2018-12-16 22:22:25 +01:00
0950caae0d Merge branch 'master' of https://github.com/tumic0/QtPBFImagePlugin 2018-12-16 22:14:55 +01:00
0188273c29 Added Travis CI config 2018-12-16 22:14:10 +01:00
13d682bdf6 Added external layers info.
closes #3
2018-12-16 17:54:56 +01:00
738ada91c2 Added missing source file 2018-12-12 01:15:19 +01:00
6d02dafeb6 Improved fuzzy text bounding rect computation 2018-12-11 18:48:12 +01:00
a559b5237b Cosmetics 2018-12-09 11:25:27 +01:00
12b91c9c6f Added applications using the plugin 2018-12-09 11:22:33 +01:00
1677eb7a67 Added OBS link & info 2018-12-08 18:44:37 +01:00
03f4315a5b Removed obsolete code 2018-12-08 11:14:34 +01:00
a3d555bb6a Code cleanup 2018-12-07 00:50:11 +01:00
a83d4909ed Updated build requirements 2018-12-05 23:04:36 +01:00
e45e86e5d7 Some more missing includes 2018-12-05 22:34:26 +01:00
0476f5740e Added missing include 2018-12-05 22:20:29 +01:00
24 changed files with 440 additions and 306 deletions

22
.travis.yml Normal file
View File

@ -0,0 +1,22 @@
language: c++
dist: xenial
os:
- linux
- osx
env:
- QT_SELECT=qt5
before_install:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update; fi
install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install qt protobuf; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install qtbase5-dev qtbase5-dev-tools qt5-qmake libprotobuf-dev protobuf-compiler; fi
script:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then PATH=/usr/local/opt/qt/bin/:${PATH}; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then qmake PROTOBUF=/usr/local pbfplugin.pro; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then qmake pbfplugin.pro; fi
- make

View File

@ -3,8 +3,8 @@ Qt image plugin for displaying Mapbox vector tiles
## Description ## Description
QtPBFImagePlugin is a Qt image plugin that enables applications capable of QtPBFImagePlugin is a Qt image plugin that enables applications capable of
displaying raster MBTiles maps or raster XYZ online maps to also display PBF displaying raster MBTiles maps or raster XYZ online maps to also display
vector tiles without (almost, see usage) any application modifications. PBF(MVT) vector tiles without (almost, see usage) any application modifications.
Standard Mapbox GL Styles are used for styling the maps. Most relevant style Standard Mapbox GL Styles are used for styling the maps. Most relevant style
features used by [Maputnik](http://editor.openmaptiles.org) are supported. features used by [Maputnik](http://editor.openmaptiles.org) are supported.
@ -40,7 +40,7 @@ you will get 512x512px tiles with a pixel ratio of 2 (= HiDPI tiles).
## Build ## Build
### Requirements ### Requirements
* Qt 5 * Qt >= 5.4 (5.6 for HiDPI support)
* Google Protocol Buffers (protobuf-lite) * Google Protocol Buffers (protobuf-lite)
* Zlib * Zlib
@ -63,10 +63,14 @@ make
## Install ## Install
Copy the plugin to the system Qt image plugins path to make it work. You may Copy the plugin to the system Qt image plugins path to make it work. You may
also set the QT_PLUGIN_PATH system variable before starting the application. also set the QT_PLUGIN_PATH system variable before starting the application. For
Linux, there are RPM and DEB [packages](https://build.opensuse.org/project/show/home:tumic:QtPBFImagePlugin)
for most common distros available on OBS.
## Limitations ## Limitations
Text PBF features must have a unique id (OpenMapTiles >= v3.7) for the text layout * Only data that is part of the PBF file is displayed. External layers defined in the
style are ignored.
* Text PBF features must have a unique id (OpenMapTiles >= v3.7) for the text layout
algorithm to work properly. algorithm to work properly.
## Status ## Status
@ -85,3 +89,5 @@ A picture is worth a thousand words. Data and styles from https://openmaptiles.o
![klokantech-basic 13](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-13.png) ![klokantech-basic 13](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-13.png)
![klokantech-basic 14](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-14.png) ![klokantech-basic 14](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-14.png)
## Applications using QtPBFImagePlugin
* [GPXSee](https://www.gpxsee.org)

View File

@ -1,4 +1,4 @@
{ {
"Keys": [ "pbf" ], "Keys": [ "mvt" ],
"MimeTypes": [ "image/pbf" ] "MimeTypes": [ "application/vnd.mapbox-vector-tile" ]
} }

View File

@ -20,7 +20,8 @@ HEADERS += src/pbfhandler.h \
src/textpointitem.h \ src/textpointitem.h \
src/font.h \ src/font.h \
src/textitem.h \ src/textitem.h \
src/sprites.h src/sprites.h \
src/config.h
SOURCES += src/pbfplugin.cpp \ SOURCES += src/pbfplugin.cpp \
src/pbfhandler.cpp \ src/pbfhandler.cpp \
src/gzip.cpp \ src/gzip.cpp \
@ -32,7 +33,8 @@ SOURCES += src/pbfplugin.cpp \
src/textpathitem.cpp \ src/textpathitem.cpp \
src/textpointitem.cpp \ src/textpointitem.cpp \
src/font.cpp \ src/font.cpp \
src/sprites.cpp src/sprites.cpp \
src/textitem.cpp
RESOURCES += pbfplugin.qrc RESOURCES += pbfplugin.qrc
unix:!macx{ unix:!macx{

9
pkg/pbfplugin.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/vnd.mapbox-vector-tile">
<comment>Mapbox Vector Tile</comment>
<sub-class-of type="application/octet-stream"/>
<generic-icon name="application/octet-stream"/>
<glob pattern="*.mvt"/>
</mime-type>
</mime-info>

View File

@ -13,9 +13,7 @@ QColor Color::fromJsonString(const QString &str)
{ {
QColor ret; QColor ret;
if (str.startsWith('#')) if (str.startsWith("rgb(")) {
return QColor(str);
else if (str.startsWith("rgb(")) {
QStringList comp(str.mid(4, str.size() - 5).split(',')); QStringList comp(str.mid(4, str.size() - 5).split(','));
if (comp.size() != 3) if (comp.size() != 3)
return QColor(); return QColor();
@ -39,7 +37,8 @@ QColor Color::fromJsonString(const QString &str)
return QColor(); return QColor();
ret = QColor::fromHslF(comp.at(0).toFloat() / 360.0, pval(comp.at(1)), ret = QColor::fromHslF(comp.at(0).toFloat() / 360.0, pval(comp.at(1)),
pval(comp.at(2)), comp.at(3).toFloat()); pval(comp.at(2)), comp.at(3).toFloat());
} } else
ret = QColor(str);
if (!ret.isValid()) if (!ret.isValid())
qWarning() << str << ": invalid color"; qWarning() << str << ": invalid color";

10
src/config.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <QtGlobal>
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
#define ENABLE_HIDPI
#endif // QT >= 5.6
#endif // CONFIG_H

View File

@ -4,6 +4,8 @@
#include <QList> #include <QList>
#include <QPointF> #include <QPointF>
#include <QColor> #include <QColor>
#include <QPair>
#include <QString>
class QJsonObject; class QJsonObject;

View File

@ -1,21 +1,20 @@
#include <QByteArray>
#include <QPainter>
#include <QDebug>
#include <QVariantHash>
#include "vector_tile.pb.h"
#include "style.h"
#include "tile.h"
#include "pbf.h" #include "pbf.h"
#define MOVE_TO 1 #define MOVE_TO 1
#define LINE_TO 2 #define LINE_TO 2
#define CLOSE_PATH 7 #define CLOSE_PATH 7
#define POLYGON vector_tile::Tile_GeomType_POLYGON
#define LINESTRING vector_tile::Tile_GeomType_LINESTRING
#define POINT vector_tile::Tile_GeomType_POINT
static inline qint32 zigzag32decode(quint32 value)
{
return static_cast<qint32>((value >> 1u) ^ static_cast<quint32>(
-static_cast<qint32>(value & 1u)));
}
static inline QPoint parameters(quint32 v1, quint32 v2)
{
return QPoint(zigzag32decode(v1), zigzag32decode(v2));
}
static QVariant value(const vector_tile::Tile_Value &val) static QVariant value(const vector_tile::Tile_Value &val)
{ {
@ -37,100 +36,35 @@ static QVariant value(const vector_tile::Tile_Value &val)
return QVariant(); return QVariant();
} }
class Feature const QVariant *PBF::Feature::value(const QString &key) const
{ {
public: const KeyHash &keys(_layer->keys());
Feature() : _data(0) {} KeyHash::const_iterator it(keys.find(key));
Feature(const vector_tile::Tile_Feature *data, const QVector<QString> *keys, if (it == keys.constEnd())
const QVector<QVariant> *values) : _data(data) return 0;
{
for (int i = 0; i < data->tags_size(); i = i + 2)
_tags.insert(keys->at(data->tags(i)), values->at(data->tags(i+1)));
switch (data->type()) { google::protobuf::uint32 index = *it;
case POLYGON: for (int i = 0; i < _data->tags_size(); i = i + 2)
_tags.insert("$type", QVariant("Polygon")); if (_data->tags(i) == index)
break; return &(_layer->values().at(_data->tags(i+1)));
case LINESTRING:
_tags.insert("$type", QVariant("LineString"));
break;
case POINT:
_tags.insert("$type", QVariant("Point"));
break;
default:
break;
}
}
const QVariantHash &tags() const {return _tags;} return 0;
const vector_tile::Tile_Feature *data() const {return _data;}
private:
QVariantHash _tags;
const vector_tile::Tile_Feature *_data;
};
bool cmp(const Feature &f1, const Feature &f2)
{
return f1.data()->id() < f2.data()->id();
} }
class Layer QPainterPath PBF::Feature::path(const QSizeF &factor) const
{ {
public:
Layer(const vector_tile::Tile_Layer *data) : _data(data)
{
QVector<QString> keys;
QVector<QVariant> values;
for (int i = 0; i < data->keys_size(); i++)
keys.append(QString::fromStdString(data->keys(i)));
for (int i = 0; i < data->values_size(); i++)
values.append(value(data->values(i)));
_features.reserve(data->features_size());
for (int i = 0; i < data->features_size(); i++)
_features.append(Feature(&(data->features(i)), &keys, &values));
qSort(_features.begin(), _features.end(), cmp);
}
const QVector<Feature> &features() const {return _features;}
const vector_tile::Tile_Layer *data() const {return _data;}
private:
const vector_tile::Tile_Layer *_data;
QVector<Feature> _features;
};
static inline qint32 zigzag32decode(quint32 value)
{
return static_cast<qint32>((value >> 1u) ^ static_cast<quint32>(
-static_cast<qint32>(value & 1u)));
}
static inline QPoint parameters(quint32 v1, quint32 v2)
{
return QPoint(zigzag32decode(v1), zigzag32decode(v2));
}
static void drawFeature(const Feature &feature, Style *style, int styleLayer,
const QSizeF &factor, Tile &tile)
{
if (!style->match(tile.zoom(), styleLayer, feature.tags()))
return;
QPoint cursor; QPoint cursor;
QPainterPath path; QPainterPath path;
for (int i = 0; i < feature.data()->geometry_size(); i++) { for (int i = 0; i < _data->geometry_size(); i++) {
quint32 g = feature.data()->geometry(i); quint32 g = _data->geometry(i);
unsigned cmdId = g & 0x7; unsigned cmdId = g & 0x7;
unsigned cmdCount = g >> 3; unsigned cmdCount = g >> 3;
if (cmdId == MOVE_TO) { if (cmdId == MOVE_TO) {
for (unsigned j = 0; j < cmdCount; j++) { for (unsigned j = 0; j < cmdCount; j++) {
QPoint offset = parameters(feature.data()->geometry(i+1), QPoint offset = parameters(_data->geometry(i+1),
feature.data()->geometry(i+2)); _data->geometry(i+2));
i += 2; i += 2;
cursor += offset; cursor += offset;
path.moveTo(QPointF(cursor.x() * factor.width(), path.moveTo(QPointF(cursor.x() * factor.width(),
@ -138,8 +72,8 @@ static void drawFeature(const Feature &feature, Style *style, int styleLayer,
} }
} else if (cmdId == LINE_TO) { } else if (cmdId == LINE_TO) {
for (unsigned j = 0; j < cmdCount; j++) { for (unsigned j = 0; j < cmdCount; j++) {
QPoint offset = parameters(feature.data()->geometry(i+1), QPoint offset = parameters(_data->geometry(i+1),
feature.data()->geometry(i+2)); _data->geometry(i+2));
i += 2; i += 2;
cursor += offset; cursor += offset;
path.lineTo(QPointF(cursor.x() * factor.width(), path.lineTo(QPointF(cursor.x() * factor.width(),
@ -151,60 +85,35 @@ static void drawFeature(const Feature &feature, Style *style, int styleLayer,
} }
} }
if (path.elementCount()) return path;
style->drawFeature(tile, styleLayer, path, feature.tags());
} }
static void drawLayer(const Layer &layer, Style *style, int styleLayer, PBF::Layer::Layer(const vector_tile::Tile_Layer *data) : _data(data)
Tile &tile)
{ {
if (layer.data()->version() > 2) _keys.reserve(data->keys_size());
return; for (int i = 0; i < data->keys_size(); i++)
_keys.insert(QString::fromStdString(data->keys(i)), i);
_values.reserve(data->values_size());
for (int i = 0; i < data->values_size(); i++)
_values.append(value(data->values(i)));
QSizeF factor(tile.size().width() / tile.scale().x() / _features.reserve(data->features_size());
(qreal)layer.data()->extent(), tile.size().height() / tile.scale().y() for (int i = 0; i < data->features_size(); i++)
/ (qreal)layer.data()->extent()); _features.append(Feature(&(data->features(i)), this));
qSort(_features.begin(), _features.end());
tile.painter().save();
style->setupLayer(tile, styleLayer);
for (int i = 0; i < layer.features().size(); i++)
drawFeature(layer.features().at(i), style, styleLayer, factor, tile);
tile.painter().restore();
} }
bool PBF::render(const QByteArray &data, int zoom, Style *style, PBF::PBF(const vector_tile::Tile &tile)
const QPointF &scale, QImage *image)
{ {
vector_tile::Tile tile;
if (!tile.ParseFromArray(data.constData(), data.size())) {
qCritical() << "Invalid tile protocol buffer data";
return false;
}
Tile t(image, zoom, scale);
style->drawBackground(t);
// Prepare source layers
QMap<QString, Layer> layers;
for (int i = 0; i < tile.layers_size(); i++) { for (int i = 0; i < tile.layers_size(); i++) {
const vector_tile::Tile_Layer &layer = tile.layers(i); const vector_tile::Tile_Layer &layer = tile.layers(i);
QString name(QString::fromStdString(layer.name())); _layers.insert(QString::fromStdString(layer.name()), new Layer(&layer));
if (style->sourceLayers().contains(name))
layers.insert(name, Layer(&layer));
} }
}
// Process source layers in order of style layers
for (int i = 0; i < style->sourceLayers().size(); i++) { PBF::~PBF()
QMap<QString, Layer>::const_iterator it = layers.find( {
style->sourceLayers().at(i)); for (QHash<QString, Layer*>::iterator it = _layers.begin();
if (it == layers.constEnd()) it != _layers.end(); it++)
continue; delete *it;
drawLayer(*it, style, i, t);
}
t.text().render(&t.painter());
return true;
} }

View File

@ -1,15 +1,67 @@
#ifndef PBF_H #ifndef PBF_H
#define PBF_H #define PBF_H
#include <QImage> #include <QVariant>
#include <QVector>
#include <QHash>
#include <QPainterPath>
#include "vector_tile.pb.h"
class QByteArray;
class Style;
namespace PBF typedef QHash<QString, google::protobuf::uint32> KeyHash;
class PBF
{ {
bool render(const QByteArray &data, int zoom, Style *style, public:
const QPointF &scale, QImage *render); class Layer;
}
class Feature
{
public:
Feature() : _data(0), _layer(0) {}
Feature(const vector_tile::Tile_Feature *data, const Layer *layer)
: _data(data), _layer(layer) {}
const QVariant *value(const QString &key) const;
vector_tile::Tile_GeomType type() const {return _data->type();}
QPainterPath path(const QSizeF &factor) const;
friend bool operator<(const Feature &f1, const Feature &f2);
private:
const vector_tile::Tile_Feature *_data;
const Layer *_layer;
};
class Layer
{
public:
Layer(const vector_tile::Tile_Layer *data);
const QVector<Feature> &features() const {return _features;}
const QVector<QVariant> &values() const {return _values;}
const KeyHash &keys() const {return _keys;}
const vector_tile::Tile_Layer *data() const {return _data;}
private:
const vector_tile::Tile_Layer *_data;
QVector<Feature> _features;
QVector<QVariant> _values;
KeyHash _keys;
};
PBF(const vector_tile::Tile &tile);
~PBF();
const QHash<QString, Layer*> &layers() const {return _layers;}
private:
QHash<QString, Layer*> _layers;
};
inline bool operator<(const PBF::Feature &f1, const PBF::Feature &f2)
{return f1._data->id() < f2._data->id();}
#endif // PBF_H #endif // PBF_H

View File

@ -1,8 +1,10 @@
#include <QImage> #include <QImage>
#include <QIODevice> #include <QIODevice>
#include <QtEndian> #include <QtEndian>
#include <QDebug>
#include "gzip.h" #include "gzip.h"
#include "pbf.h" #include "tile.h"
#include "style.h"
#include "pbfhandler.h" #include "pbfhandler.h"
@ -61,6 +63,11 @@ bool PBFHandler::read(QImage *image)
ba = device()->readAll(); ba = device()->readAll();
if (ba.isNull()) if (ba.isNull())
return false; return false;
vector_tile::Tile data;
if (!data.ParseFromArray(ba.constData(), ba.size())) {
qCritical() << "Invalid PBF data";
return false;
}
bool ok; bool ok;
int zoom = format().toInt(&ok); int zoom = format().toInt(&ok);
@ -71,8 +78,11 @@ bool PBFHandler::read(QImage *image)
? QPointF(_scaledSize.width() / TILE_SIZE, _scaledSize.height() / TILE_SIZE) ? QPointF(_scaledSize.width() / TILE_SIZE, _scaledSize.height() / TILE_SIZE)
: QPointF(1.0, 1.0); : QPointF(1.0, 1.0);
*image = QImage(size, QImage::Format_ARGB32_Premultiplied); *image = QImage(size, QImage::Format_ARGB32_Premultiplied);
Tile tile(image, ok ? zoom : -1, scale);
return PBF::render(ba, ok ? zoom : -1, _style, scale, image); _style->render(data, tile);
return true;
} }
bool PBFHandler::supportsOption(ImageOption option) const bool PBFHandler::supportsOption(ImageOption option) const

View File

@ -2,16 +2,16 @@
#define PBFHANDLER_H #define PBFHANDLER_H
#include <QImageIOHandler> #include <QImageIOHandler>
#include <QImage>
#include <QVariant> #include <QVariant>
#include <QSize> #include <QSize>
class QImage;
class Style; class Style;
class PBFHandler : public QImageIOHandler class PBFHandler : public QImageIOHandler
{ {
public: public:
PBFHandler(Style *style) : _style(style) {} PBFHandler(const Style *style) : _style(style) {}
~PBFHandler() {} ~PBFHandler() {}
bool canRead() const; bool canRead() const;
@ -19,12 +19,12 @@ public:
QVariant option(ImageOption option) const; QVariant option(ImageOption option) const;
bool supportsOption(ImageOption option) const; bool supportsOption(ImageOption option) const;
void setOption(QImageIOHandler::ImageOption option, const QVariant &value); void setOption(ImageOption option, const QVariant &value);
static bool canRead(QIODevice *device); static bool canRead(QIODevice *device);
private: private:
Style *_style; const Style *_style;
QSize _scaledSize; QSize _scaledSize;
}; };

View File

@ -21,7 +21,7 @@ QImageIOPlugin::Capabilities PBFPlugin::capabilities(QIODevice *device,
const QByteArray &format) const const QByteArray &format) const
{ {
if (device == 0) if (device == 0)
return (format == "pbf") ? Capabilities(CanRead) : Capabilities(); return (format == "mvt") ? Capabilities(CanRead) : Capabilities();
else else
return (device->isReadable() && PBFHandler::canRead(device)) return (device->isReadable() && PBFHandler::canRead(device))
? Capabilities(CanRead) : Capabilities(); ? Capabilities(CanRead) : Capabilities();

View File

@ -83,6 +83,9 @@ bool Sprites::load(const QString &jsonFile, const QString &imageFile)
QImage Sprites::icon(const QString &name) const QImage Sprites::icon(const QString &name) const
{ {
if (_imageFile.isEmpty())
return QImage();
const QImage *img = atlas(_imageFile); const QImage *img = atlas(_imageFile);
if (img->isNull()) if (img->isNull())
return QImage(); return QImage();

View File

@ -4,6 +4,9 @@
#include <QRect> #include <QRect>
#include <QMap> #include <QMap>
#include <QImage> #include <QImage>
#include <QString>
class QJsonObject;
class Sprites class Sprites
{ {

View File

@ -10,9 +10,22 @@
#include "color.h" #include "color.h"
#include "font.h" #include "font.h"
#include "tile.h" #include "tile.h"
#include "pbf.h"
#include "style.h" #include "style.h"
static vector_tile::Tile_GeomType geometryType(const QString &str)
{
if (str == "Point")
return vector_tile::Tile_GeomType_POINT;
else if (str == "LineString")
return vector_tile::Tile_GeomType_LINESTRING;
else if (str == "Polygon")
return vector_tile::Tile_GeomType_POLYGON;
else
return vector_tile::Tile_GeomType_UNKNOWN;
}
Style::Layer::Filter::Filter(const QJsonArray &json) Style::Layer::Filter::Filter(const QJsonArray &json)
: _type(Unknown), _not(false) : _type(Unknown), _not(false)
{ {
@ -27,9 +40,15 @@ Style::Layer::Filter::Filter(const QJsonArray &json)
if (type == "==") { if (type == "==") {
if (json.size() != 3) if (json.size() != 3)
INVALID_FILTER(json); INVALID_FILTER(json);
_type = EQ; if (json.at(1).toString() == "$type") {
_kv = QPair<QString, QVariant>(json.at(1).toString(), _type = GeometryType;
json.at(2).toVariant()); _kv = QPair<QString, QVariant>(QString(),
QVariant(geometryType(json.at(2).toString())));
} else {
_type = EQ;
_kv = QPair<QString, QVariant>(json.at(1).toString(),
json.at(2).toVariant());
}
} else if (type == "!=") { } else if (type == "!=") {
if (json.size() != 3) if (json.size() != 3)
INVALID_FILTER(json); INVALID_FILTER(json);
@ -98,45 +117,68 @@ Style::Layer::Filter::Filter(const QJsonArray &json)
INVALID_FILTER(json); INVALID_FILTER(json);
} }
bool Style::Layer::Filter::match(const QVariantHash &tags) const bool Style::Layer::Filter::match(const PBF::Feature &feature) const
{ {
const QVariant *v;
switch (_type) { switch (_type) {
case None: case None:
return true; return true;
case EQ: case EQ:
return tags.value(_kv.first) == _kv.second; if (!(v = feature.value(_kv.first)))
return false;
else
return *v == _kv.second;
case NE: case NE:
return tags.value(_kv.first) != _kv.second; if (!(v = feature.value(_kv.first)))
return true;
else
return *v != _kv.second;
case GT: case GT:
return tags.value(_kv.first) > _kv.second; if (!(v = feature.value(_kv.first)))
return false;
else
return *v > _kv.second;
case GE: case GE:
return tags.value(_kv.first) >= _kv.second; if (!(v = feature.value(_kv.first)))
return false;
else
return *v >= _kv.second;
case LT: case LT:
return tags.value(_kv.first) < _kv.second; if (!(v = feature.value(_kv.first)))
return false;
else
return *v < _kv.second;
case LE: case LE:
return tags.value(_kv.first) <= _kv.second; if (!(v = feature.value(_kv.first)))
return false;
else
return *v <= _kv.second;
case In: case In:
return _set.contains(tags.value(_kv.first).toString()) ^ _not; if (!(v = feature.value(_kv.first)))
return _not;
else
return _set.contains((*v).toString()) ^ _not;
case Has: case Has:
return tags.contains(_kv.first) ^ _not; return (feature.value(_kv.first) ? true : false) ^ _not;
case All: case All:
for (int i = 0; i < _filters.size(); i++) { for (int i = 0; i < _filters.size(); i++)
if (!_filters.at(i).match(tags)) if (!_filters.at(i).match(feature))
return false; return false;
}
return true; return true;
case Any: case Any:
for (int i = 0; i < _filters.size(); i++) { for (int i = 0; i < _filters.size(); i++)
if (_filters.at(i).match(tags)) if (_filters.at(i).match(feature))
return true; return true;
}
return false; return false;
case GeometryType:
return feature.type() == _kv.second.toUInt();
default: default:
return false; return false;
} }
} }
QString Style::Layer::Template::value(int zoom, const QVariantHash &tags) const QString Style::Layer::Template::value(int zoom, const PBF::Feature &feature) const
{ {
QRegExp rx = QRegExp("\\{[^\\}]*\\}"); QRegExp rx = QRegExp("\\{[^\\}]*\\}");
QString text(_field.value(zoom)); QString text(_field.value(zoom));
@ -150,8 +192,8 @@ QString Style::Layer::Template::value(int zoom, const QVariantHash &tags) const
} }
for (int i = 0; i < keys.size(); i++) { for (int i = 0; i < keys.size(); i++) {
const QString &key = keys.at(i); const QString &key = keys.at(i);
const QVariant val = tags.value(key); const QVariant *val = feature.value(key);
text.replace(QString("{%1}").arg(key), val.toString()); text.replace(QString("{%1}").arg(key), val ? val->toString() : "");
} }
return text; return text;
@ -428,7 +470,7 @@ Style::Layer::Layer(const QJsonObject &json)
_paint = Paint(json["paint"].toObject()); _paint = Paint(json["paint"].toObject());
} }
bool Style::Layer::match(int zoom, const QVariantHash &tags) const bool Style::Layer::match(int zoom, const PBF::Feature &feature) const
{ {
if (zoom >= 0) { if (zoom >= 0) {
if (_minZoom > 0 && zoom < _minZoom) if (_minZoom > 0 && zoom < _minZoom)
@ -437,7 +479,7 @@ bool Style::Layer::match(int zoom, const QVariantHash &tags) const
return false; return false;
} }
return _filter.match(tags); return _filter.match(feature);
} }
void Style::Layer::setPathPainter(Tile &tile, const Sprites &sprites) const void Style::Layer::setPathPainter(Tile &tile, const Sprites &sprites) const
@ -472,13 +514,13 @@ void Style::Layer::setTextProperties(Tile &tile) const
} }
void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path, void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path,
const QVariantHash &tags, const Sprites &sprites) const const PBF::Feature &feature, const Sprites &sprites) const
{ {
QString text = _layout.text(tile.zoom(), tags); QString text = _layout.text(tile.zoom(), feature);
if (text.isEmpty()) if (text.isEmpty())
return; return;
QString icon = _layout.icon(tile.zoom(), tags); QString icon = _layout.icon(tile.zoom(), feature);
tile.text().addLabel(text, sprites.icon(icon), path); tile.text().addLabel(text, sprites.icon(icon), path);
} }
@ -525,39 +567,32 @@ bool Style::load(const QString &fileName)
_layers.append(Layer(layers[i].toObject())); _layers.append(Layer(layers[i].toObject()));
} }
for (int i = 0; i < _layers.size(); i++)
_sourceLayers.append(_layers.at(i).sourceLayer());
QDir styleDir = QFileInfo(fileName).absoluteDir(); QDir styleDir = QFileInfo(fileName).absoluteDir();
loadSprites(styleDir, "sprite.json", "sprite.png", _sprites); loadSprites(styleDir, "sprite.json", "sprite.png", _sprites);
#ifdef ENABLE_HIDPI
loadSprites(styleDir, "sprite@2x.json", "sprite@2x.png", _sprites2x); loadSprites(styleDir, "sprite@2x.json", "sprite@2x.png", _sprites2x);
#endif // ENABLE_HIDPI
return true; return true;
} }
void Style::setupLayer(Tile &tile, int layer) const const Sprites &Style::sprites(const QPointF &scale) const
{ {
const Layer &sl = _layers.at(layer); #ifdef ENABLE_HIDPI
const Sprites &sprites = (tile.scale().x() > 1.0 || tile.scale().y() > 1.0) return (scale.x() > 1.0 || scale.y() > 1.0)
&& !_sprites2x.isNull() ? _sprites2x : _sprites; && !_sprites2x.isNull() ? _sprites2x : _sprites;
#else // ENABLE_HIDPI
if (sl.isSymbol()) Q_UNUSED(scale);
sl.setTextProperties(tile); return _sprites;
else if (sl.isPath()) #endif // ENABLE_HIDPI
sl.setPathPainter(tile, sprites);
} }
void Style::drawFeature(Tile &tile, int layer, const QPainterPath &path, void Style::setupLayer(Tile &tile, const Layer &layer) const
const QVariantHash &tags) const
{ {
const Layer &sl = _layers.at(layer); if (layer.isSymbol())
const Sprites &sprites = (tile.scale().x() > 1.0 || tile.scale().y() > 1.0) layer.setTextProperties(tile);
&& !_sprites2x.isNull() ? _sprites2x : _sprites; else if (layer.isPath())
layer.setPathPainter(tile, sprites(tile.scale()));
if (sl.isPath())
tile.painter().drawPath(path);
else if (sl.isSymbol())
sl.addSymbol(tile, path, tags, sprites);
} }
void Style::drawBackground(Tile &tile) const void Style::drawBackground(Tile &tile) const
@ -578,3 +613,52 @@ void Style::drawBackground(Tile &tile) const
//tile.painter().setPen(Qt::red); //tile.painter().setPen(Qt::red);
//tile.painter().drawRect(rect); //tile.painter().drawRect(rect);
} }
void Style::drawFeature(const PBF::Feature &feature, const Layer &layer,
Tile &tile, const QSizeF &factor) const
{
if (!layer.match(tile.zoom(), feature))
return;
QPainterPath path(feature.path(factor));
if (!path.elementCount())
return;
if (layer.isPath())
tile.painter().drawPath(path);
else if (layer.isSymbol())
layer.addSymbol(tile, path, feature, sprites(tile.scale()));
}
void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
Tile &tile) const
{
if (pbfLayer.data()->version() > 2)
return;
QSizeF factor(tile.size().width() / tile.scale().x() /
(qreal)pbfLayer.data()->extent(), tile.size().height() / tile.scale().y()
/ (qreal)pbfLayer.data()->extent());
tile.painter().save();
setupLayer(tile, styleLayer);
for (int i = 0; i < pbfLayer.features().size(); i++)
drawFeature(pbfLayer.features().at(i), styleLayer, tile, factor);
tile.painter().restore();
}
void Style::render(const PBF &data, Tile &tile) const
{
drawBackground(tile);
for (int i = 0; i < _layers.size(); i++) {
QHash<QString, PBF::Layer*>::const_iterator it = data.layers().find(
_layers.at(i).sourceLayer());
if (it == data.layers().constEnd())
continue;
drawLayer(**it, _layers.at(i), tile);
}
tile.text().render(&tile.painter());
}

View File

@ -3,12 +3,16 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QVector>
#include <QPair>
#include <QVariantHash> #include <QVariantHash>
#include <QStringList> #include <QStringList>
#include <QSet> #include <QSet>
#include <QPen> #include <QPen>
#include <QBrush> #include <QBrush>
#include <QFont> #include <QFont>
#include "pbf.h"
#include "config.h"
#include "text.h" #include "text.h"
#include "function.h" #include "function.h"
#include "sprites.h" #include "sprites.h"
@ -16,6 +20,8 @@
class QPainter; class QPainter;
class QPainterPath; class QPainterPath;
class QJsonArray;
class QJsonObject;
class Tile; class Tile;
class Style : public QObject class Style : public QObject
@ -24,16 +30,7 @@ public:
Style(QObject *parent = 0) : QObject(parent) {} Style(QObject *parent = 0) : QObject(parent) {}
bool load(const QString &fileName); bool load(const QString &fileName);
void render(const PBF &data, Tile &tile) const;
const QStringList &sourceLayers() const
{return _sourceLayers;}
bool match(int zoom, int layer, const QVariantHash &tags) const
{return _layers.at(layer).match(zoom, tags);}
void drawBackground(Tile &tile) const;
void setupLayer(Tile &tile, int layer) const;
void drawFeature(Tile &tile, int layer, const QPainterPath &path,
const QVariantHash &tags) const;
private: private:
class Layer { class Layer {
@ -46,11 +43,11 @@ private:
bool isBackground() const {return (_type == Background);} bool isBackground() const {return (_type == Background);}
bool isSymbol() const {return (_type == Symbol);} bool isSymbol() const {return (_type == Symbol);}
bool match(int zoom, const QVariantHash &tags) const; bool match(int zoom, const PBF::Feature &feature) const;
void setPathPainter(Tile &tile, const Sprites &sprites) const; void setPathPainter(Tile &tile, const Sprites &sprites) const;
void setTextProperties(Tile &tile) const; void setTextProperties(Tile &tile) const;
void addSymbol(Tile &tile, const QPainterPath &path, void addSymbol(Tile &tile, const QPainterPath &path,
const QVariantHash &tags, const Sprites &sprites) const; const PBF::Feature &feature, const Sprites &sprites) const;
private: private:
enum Type { enum Type {
@ -66,13 +63,13 @@ private:
Filter() : _type(None) {} Filter() : _type(None) {}
Filter(const QJsonArray &json); Filter(const QJsonArray &json);
bool match(const QVariantHash &tags) const; bool match(const PBF::Feature &feature) const;
private: private:
enum Type { enum Type {
None, Unknown, None, Unknown,
EQ, NE, GE, GT, LE, LT, EQ, NE, GE, GT, LE, LT,
All, Any, All, Any,
In, Has In, Has, GeometryType
}; };
Type _type; Type _type;
@ -86,7 +83,7 @@ private:
public: public:
Template() {} Template() {}
Template(const FunctionS &str) : _field(str) {} Template(const FunctionS &str) : _field(str) {}
QString value(int zoom, const QVariantHash &tags) const; QString value(int zoom, const PBF::Feature &feature) const;
private: private:
FunctionS _field; FunctionS _field;
@ -102,10 +99,10 @@ private:
{return _textMaxWidth.value(zoom);} {return _textMaxWidth.value(zoom);}
qreal maxTextAngle(int zoom) const qreal maxTextAngle(int zoom) const
{return _textMaxAngle.value(zoom);} {return _textMaxAngle.value(zoom);}
QString text(int zoom, const QVariantHash &tags) const QString text(int zoom, const PBF::Feature &feature) const
{return _text.value(zoom, tags).trimmed();} {return _text.value(zoom, feature).trimmed();}
QString icon(int zoom, const QVariantHash &tags) const QString icon(int zoom, const PBF::Feature &feature) const
{return _icon.value(zoom, tags);} {return _icon.value(zoom, feature);}
QFont font(int zoom) const; QFont font(int zoom) const;
Qt::PenCapStyle lineCap(int zoom) const; Qt::PenCapStyle lineCap(int zoom) const;
Qt::PenJoinStyle lineJoin(int zoom) const; Qt::PenJoinStyle lineJoin(int zoom) const;
@ -140,8 +137,6 @@ private:
const; const;
qreal opacity(Layer::Type type, int zoom) const; qreal opacity(Layer::Type type, int zoom) const;
bool antialias(Layer::Type type, int zoom) const; bool antialias(Layer::Type type, int zoom) const;
QString fillPattern(int zoom) const
{return _fillPattern.value(zoom);}
private: private:
FunctionC _textColor; FunctionC _textColor;
@ -165,9 +160,20 @@ private:
Paint _paint; Paint _paint;
}; };
const Sprites &sprites(const QPointF &scale) const;
void drawBackground(Tile &tile) const;
void setupLayer(Tile &tile, const Layer &layer) const;
void drawFeature(const PBF::Feature &feature, const Layer &layer,
Tile &tile, const QSizeF &factor) const;
void drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
Tile &tile) const;
QVector<Layer> _layers; QVector<Layer> _layers;
QStringList _sourceLayers; Sprites _sprites;
Sprites _sprites, _sprites2x; #ifdef ENABLE_HIDPI
Sprites _sprites2x;
#endif // QT >= 5.6
}; };
#endif // STYLE_H #endif // STYLE_H

View File

@ -1,5 +1,6 @@
#include <QFontMetrics> #include <QFontMetrics>
#include <QPainter> #include <QPainter>
#include <QSet>
#include "text.h" #include "text.h"
#include "textpointitem.h" #include "textpointitem.h"
#include "textpathitem.h" #include "textpathitem.h"

35
src/textitem.cpp Normal file
View File

@ -0,0 +1,35 @@
#include "textitem.h"
bool TextItem::collidesWithItem(const TextItem *other) const
{
QRectF r1(boundingRect());
QRectF r2(other->boundingRect());
if (r1.isEmpty() || r2.isEmpty() || !r1.intersects(r2))
return false;
return other->shape().intersects(shape());
}
int TextItem::avgCharWidth() const
{
qreal ratio;
ushort cp = _text.at(0).unicode();
// CJK & East Asian scripts
if (cp >= 0x2E80)
ratio = 1.0;
// Greek & Cyrilic
else if (cp >= 0x03FF && cp <= 0x04FF) {
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.75 : 0.67;
if (_font.bold())
ratio *= 1.1;
// The rest (Latin scripts, Arabic, ...)
} else {
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.7 : 0.58;
if (_font.bold())
ratio *= 1.1;
}
return ratio * _font.pixelSize();
}

View File

@ -1,18 +1,23 @@
#ifndef TEXTITEM_H #ifndef TEXTITEM_H
#define TEXTITEM_H #define TEXTITEM_H
#include <QString>
#include <QPainterPath> #include <QPainterPath>
#include <QPen>
#include <QFont> #include <QFont>
#include <QRectF>
class QPainter;
class TextItem class TextItem
{ {
public: public:
TextItem(const QString &text) : _text(text), _visible(true) {} TextItem(const QString &text, const QFont &font)
: _text(text), _font(font), _visible(true) {}
virtual ~TextItem() {} virtual ~TextItem() {}
const QString &text() const {return _text;} const QString &text() const {return _text;}
const QFont &font() const {return _font;} const QFont &font() const {return _font;}
void setFont(const QFont &font) {_font = font;}
const QPen &pen() const {return _pen;} const QPen &pen() const {return _pen;}
void setPen(const QPen &pen) {_pen = pen;} void setPen(const QPen &pen) {_pen = pen;}
@ -23,33 +28,10 @@ public:
bool isVisible() const {return _visible;} bool isVisible() const {return _visible;}
void setVisible(bool visible) {_visible = visible;} void setVisible(bool visible) {_visible = visible;}
bool collidesWithItem(const TextItem *other) const bool collidesWithItem(const TextItem *other) const;
{
QRectF r1(boundingRect());
QRectF r2(other->boundingRect());
if (r1.isEmpty() || r2.isEmpty() || !r1.intersects(r2))
return false;
return other->shape().intersects(shape());
}
protected: protected:
static int avgCharWidth(const QString &str, const QFont &font) int avgCharWidth() const;
{
qreal ratio;
if (str.at(0).unicode() >= 0x2E80)
ratio = 1.0;
else {
ratio = (font.capitalization() == QFont::AllUppercase)
? 0.66 : 0.55;
if (font.bold())
ratio *= 1.1;
}
return ratio * font.pixelSize();
}
private: private:
QString _text; QString _text;

View File

@ -134,9 +134,9 @@ static bool reverse(const QPainterPath &path)
TextPathItem::TextPathItem(const QString &text, const QPainterPath &path, TextPathItem::TextPathItem(const QString &text, const QPainterPath &path,
const QFont &font, int maxAngle, const QRectF &tileRect) const QFont &font, int maxAngle, const QRectF &tileRect)
: TextItem(text) : TextItem(text, font)
{ {
int cw = avgCharWidth(text, font); int cw = avgCharWidth();
int textWidth = text.size() * cw; int textWidth = text.size() * cw;
if (textWidth > path.length()) if (textWidth > path.length())
return; return;
@ -146,7 +146,6 @@ TextPathItem::TextPathItem(const QString &text, const QPainterPath &path,
return; return;
_path = reverse(tp) ? tp.toReversed() : tp; _path = reverse(tp) ? tp.toReversed() : tp;
setFont(font);
QPainterPathStroker s; QPainterPathStroker s;
s.setWidth(font.pixelSize()); s.setWidth(font.pixelSize());

View File

@ -1,10 +1,7 @@
#ifndef TEXTPATHITEM_H #ifndef TEXTPATHITEM_H
#define TEXTPATHITEM_H #define TEXTPATHITEM_H
#include <QFont>
#include <QString>
#include "textitem.h" #include "textitem.h"
#include "text.h"
class TextPathItem : public TextItem class TextPathItem : public TextItem
{ {

View File

@ -1,41 +1,37 @@
#include <QPainter> #include <QPainter>
#include <QtMath> #include <QtMath>
#include "config.h"
#include "textpointitem.h" #include "textpointitem.h"
#define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip) #define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip)
QRectF TextPointItem::exactBoundingRect(const QString &str, QRectF TextPointItem::exactBoundingRect() const
const QFont &font, int maxWidth)
{ {
QFontMetrics fm(font); QFontMetrics fm(font());
int limit = font.pixelSize() * maxWidth; int limit = font().pixelSize() * _maxWidth;
// Italic fonts overflow the computed bounding rect, so reduce it // Italic fonts overflow the computed bounding rect, so reduce it
// a little bit. // a little bit.
if (font.italic()) if (font().italic())
limit -= font.pixelSize() / 2.0; limit -= font().pixelSize() / 2.0;
QRect br = fm.boundingRect(QRect(0, 0, limit, 0), FLAGS, str); QRect br = fm.boundingRect(QRect(0, 0, limit, 0), FLAGS, text());
Q_ASSERT(br.isValid()); Q_ASSERT(br.isValid());
// Expand the bounding rect back to the real content size // Expand the bounding rect back to the real content size
if (font.italic()) if (font().italic())
br.adjust(-font.pixelSize() / 4.0, 0, font.pixelSize() / 4.0, 0); br.adjust(-font().pixelSize() / 4.0, 0, font().pixelSize() / 4.0, 0);
return br; return br;
} }
QRectF TextPointItem::fuzzyBoundingRect(const QString &str, QRectF TextPointItem::fuzzyBoundingRect() const
const QFont &font, int maxWidth)
{ {
int limit = font.pixelSize() * maxWidth; int limit = font().pixelSize() * _maxWidth;
qreal cw = avgCharWidth(str, font); qreal cw = avgCharWidth();
qreal lh = font.pixelSize() * 1.25; qreal lh = font().pixelSize() * 1.25;
int width = 0, lines = 0; int width = 0, lines = 0;
if (font.italic()) QStringList l(text().split('\n'));
limit -= font.pixelSize() / 2.0;
QStringList l(str.split('\n'));
for (int i = 0; i < l.size(); i++) { for (int i = 0; i < l.size(); i++) {
int lw = (int)(l.at(i).length() * cw); int lw = (int)(l.at(i).length() * cw);
if (lw > limit) { if (lw > limit) {
@ -69,11 +65,16 @@ QRectF TextPointItem::fuzzyBoundingRect(const QString &str,
} }
QRectF TextPointItem::computeTextRect(BoundingRectFunction brf) const QRectF TextPointItem::computeTextRect(bool exact) const
{ {
#ifdef ENABLE_HIDPI
QRectF iconRect = _icon.isNull() ? QRectF() QRectF iconRect = _icon.isNull() ? QRectF()
: QRectF(QPointF(0, 0), QSizeF(_icon.size()) / _icon.devicePixelRatioF()); : QRectF(QPointF(0, 0), QSizeF(_icon.size()) / _icon.devicePixelRatioF());
QRectF textRect = brf(text(), font(), _maxWidth); #else // ENABLE_HIDPI
QRectF iconRect = _icon.isNull() ? QRectF() : QRectF(QPointF(0, 0),
QSizeF(_icon.size()));
#endif // ENABLE_HIDPI
QRectF textRect = exact ? exactBoundingRect() : fuzzyBoundingRect();
switch (_anchor) { switch (_anchor) {
case Text::Center: case Text::Center:
@ -102,15 +103,18 @@ QRectF TextPointItem::computeTextRect(BoundingRectFunction brf) const
TextPointItem::TextPointItem(const QString &text, const QPointF &pos, TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
const QFont &font, int maxWidth, Text::Anchor anchor, const QImage &icon) const QFont &font, int maxWidth, Text::Anchor anchor, const QImage &icon)
: TextItem(text), _pos(pos), _icon(icon), _maxWidth(maxWidth), : TextItem(text, font), _pos(pos), _icon(icon), _maxWidth(maxWidth),
_anchor(anchor) _anchor(anchor)
{ {
setFont(font); _boundingRect = computeTextRect(false);
_boundingRect = computeTextRect(fuzzyBoundingRect);
if (!_icon.isNull()) { if (!_icon.isNull()) {
#ifdef ENABLE_HIDPI
QRectF iconRect(QPointF(0, 0), QSizeF(_icon.size()) QRectF iconRect(QPointF(0, 0), QSizeF(_icon.size())
/ _icon.devicePixelRatioF()); / _icon.devicePixelRatioF());
#else // ENABLE_HIDPI
QRectF iconRect(QPointF(0, 0), QSizeF(_icon.size()));
#endif // ENABLE_HIDPI
iconRect.moveCenter(pos); iconRect.moveCenter(pos);
_boundingRect |= iconRect; _boundingRect |= iconRect;
} }
@ -126,12 +130,18 @@ void TextPointItem::paint(QPainter *painter) const
QRectF textRect; QRectF textRect;
if (!_icon.isNull()) { if (!_icon.isNull()) {
textRect = computeTextRect(exactBoundingRect); textRect = (_anchor != Text::Center)
? computeTextRect(true) : _boundingRect;
#ifdef ENABLE_HIDPI
painter->drawImage(_pos - QPointF(_icon.width() painter->drawImage(_pos - QPointF(_icon.width()
/ _icon.devicePixelRatioF() / 2, _icon.height() / _icon.devicePixelRatioF() / 2, _icon.height()
/ _icon.devicePixelRatioF() / 2), _icon); / _icon.devicePixelRatioF() / 2), _icon);
#else // ENABLE_HIDPI
painter->drawImage(_pos - QPointF(_icon.width() / 2,
_icon.height() / 2), _icon);
#endif // ENABLE_HIDPI
} else } else
textRect = computeTextRect(fuzzyBoundingRect); textRect = _boundingRect;
painter->setFont(font()); painter->setFont(font());
painter->setPen(pen()); painter->setPen(pen());

View File

@ -1,11 +1,9 @@
#ifndef TEXTPOINTITEM_H #ifndef TEXTPOINTITEM_H
#define TEXTPOINTITEM_H #define TEXTPOINTITEM_H
#include <QPen> #include <QImage>
#include <QFont>
#include <QString>
#include "textitem.h"
#include "text.h" #include "text.h"
#include "textitem.h"
class TextPointItem : public TextItem class TextPointItem : public TextItem
{ {
@ -18,14 +16,9 @@ public:
void paint(QPainter *painter) const; void paint(QPainter *painter) const;
private: private:
typedef QRectF (*BoundingRectFunction)(const QString &, const QFont &, int); QRectF exactBoundingRect() const;
QRectF fuzzyBoundingRect() const;
static QRectF exactBoundingRect(const QString &str, const QFont &font, QRectF computeTextRect(bool exact) const;
int maxWidth);
static QRectF fuzzyBoundingRect(const QString &str, const QFont &font,
int maxWidth);
QRectF computeTextRect(BoundingRectFunction brf) const;
QPointF _pos; QPointF _pos;
QPainterPath _shape; QPainterPath _shape;