62 Commits
1.1 ... 2.2

Author SHA1 Message Date
032c699e8b Code cleanup 2020-04-15 10:06:57 +02:00
a298cfc47d Only accept PBF gziped files in canRead() 2020-04-15 09:47:43 +02:00
402e1e3adc Consistently use mvt as the format 2020-04-13 19:47:41 +02:00
254e93e583 Added Tilezen examples 2020-04-11 11:49:37 +02:00
95d4fee453 Added examples data/style sources 2020-04-11 11:27:17 +02:00
a6547d27d3 Added style compatibility info 2020-04-10 13:13:20 +02:00
3e065f87fa Updated screenshots 2020-04-09 23:42:02 +02:00
61291bb202 Updated styles info 2020-04-09 22:47:21 +02:00
154114dd2a Added missing brew update to travis builds 2020-02-08 13:04:36 +01:00
c0a7d64a1d Silenced all the deprecated warnings 2020-01-31 22:20:57 +01:00
d8df5cb595 Added style expressions not supported info 2019-10-22 09:14:06 +02:00
543bf63017 Merge branch 'master' of https://github.com/tumic0/QtPBFImagePlugin 2019-10-06 13:27:10 +02:00
7ea19d260c Fixed broken interpolation of grayscale colors 2019-10-06 13:26:30 +02:00
0185cd904f Added change log link 2019-10-05 22:06:37 +02:00
ec6302d524 Updated scaling sample to fit v2.0 2019-10-05 18:49:34 +02:00
0916d6330c Added tile size info 2019-10-05 18:47:39 +02:00
cc0c16cfd6 Explicitly set the text format (optimization) 2019-10-05 18:38:24 +02:00
8da172c0ff Adjusted icon text alignment 2019-10-05 13:21:36 +02:00
148f79eaef Some more bounding rect computation tweaking 2019-10-04 20:23:34 +02:00
1c42f104d1 Merge branch 'master' of https://github.com/tumic0/QtPBFImagePlugin 2019-10-04 18:57:19 +02:00
536cad2d61 Use QStaticText instead of own text caching 2019-10-04 18:56:45 +02:00
ff1c29e078 Use QStaticText instead of own text caching 2019-10-04 18:54:59 +02:00
e8e9139740 Some more text layout tweeking 2019-10-04 09:26:15 +02:00
a58962ab93 Yet another text rendering improvement/optimization 2019-10-03 22:39:05 +02:00
aacc42c25d Fixed broken down-scaling 2019-10-03 08:38:48 +02:00
3f6b5faa2c Switched to 512x512px tile size 2019-10-03 01:01:40 +02:00
5b6a222999 Added correct maxzoom default value 2019-10-02 23:19:49 +02:00
e00c8feab6 Use the "official" min/max zoom defaults in zoom check 2019-10-02 23:14:20 +02:00
82308857ba Added missing include 2019-10-02 23:12:53 +02:00
044e95e061 Some more text layout fiddeling 2019-10-02 22:57:11 +02:00
f5c15ece52 Improved/optimized text rendering 2019-10-01 22:11:45 +02:00
df4134c03a Alittle bit more sane API... 2019-05-04 12:50:16 +02:00
c28d10eafc Proper path-text halo drawing 2019-04-28 09:10:19 +02:00
76fb129651 Halo drawing performance improvement 2019-04-28 08:25:57 +02:00
3dfcf35e53 Added support for text halo 2019-04-27 22:52:18 +02:00
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
27 changed files with 729 additions and 433 deletions

23
.travis.yml Normal file
View File

@ -0,0 +1,23 @@
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
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew 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,20 +3,16 @@ 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.
The style is loaded from the A default fallback style (OSM-Liberty) for OpenMapTiles is part of the plugin.
[$AppDataLocation](http://doc.qt.io/qt-5/qstandardpaths.html)/style/style.json
file on plugin load. If the style uses a sprite, the sprite JSON file must
be named sprite.json and the sprite image sprite.png and both files must be
placed in the same directory as the style itself. A default fallback style
(OSM-Liberty) for OpenMapTiles is part of the plugin.
"Plain" PBF files as well as gzip compressed files (as used in MBTiles) are "Plain" PBF files as well as gzip compressed files (as used in MBTiles) are
supported by the plugin. supported by the plugin. The tile size is (since version 2.0 of the plugin) 512px
to fit the styles and available data (OpenMapTiles, Mapbox tiles).
## Usage ## Usage
Due to a major design flaw in the Mapbox vector tiles specification - the zoom Due to a major design flaw in the Mapbox vector tiles specification - the zoom
@ -33,14 +29,26 @@ The plugin supports vector scaling using QImageReader's setScaledSize() method,
so when used like in the following example: so when used like in the following example:
```cpp ```cpp
QImageReader reader(file, QString::number(zoom).toLatin1()); QImageReader reader(file, QString::number(zoom).toLatin1());
reader.setScaledSize(QSize(512, 512)); reader.setScaledSize(QSize(1024, 1024));
reader.read(&image); reader.read(&image);
``` ```
you will get 512x512px tiles with a pixel ratio of 2 (= HiDPI tiles). you will get 1024x1024px tiles with a pixel ratio of 2 (= HiDPI tiles).
## Styles
The map style is loaded from the
[$AppDataLocation](http://doc.qt.io/qt-5/qstandardpaths.html)/style/style.json
file on plugin load. If the style uses a sprite, the sprite JSON file must
be named `sprite.json` and the sprite image `sprite.png` and both files must be
placed in the same directory as the style itself. *A style compatible with the
tiles data schema (Mapbox, OpenMapTiles, Tilezen) must be used.*
For a list of "ready to use" styles see the
[QtPBFImagePlugin-styles](https://github.com/tumic0/QtPBFImagePlugin-styles)
repository.
## 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,25 +71,54 @@ 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.
* Expressions are not supported in the styles, only property functions are implemented.
## Changelog
[Changelog](https://build.opensuse.org/package/view_file/home:tumic:QtPBFImagePlugin/QtPBFImagePlugin/libqt5-qtpbfimageformat.changes)
## Status ## Status
A picture is worth a thousand words. Data and styles from https://openmaptiles.org. A picture is worth a thousand words.
#### OSM-liberty #### OpenMapTiles
![osm-liberty 2](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-2.png)
* Data: [MapTiler](https://github.com/tumic0/GPXSee-maps/blob/master/World/MapTiler.tpl)
* Style: [OSM-liberty](https://github.com/tumic0/QtPBFImagePlugin-styles/blob/master/OpenMapTiles/osm-liberty/style.json)
![osm-liberty 5](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-5.png) ![osm-liberty 5](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-5.png)
![osm-liberty 8](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-8.png) ![osm-liberty 8](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-8.png)
![osm-liberty 11](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-11.png) ![osm-liberty 12](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-12.png)
![osm-liberty 14](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-14.png) ![osm-liberty 14](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-14.png)
![osm-liberty 15](https://tumic0.github.io/QtPBFImagePlugin/images/osm-liberty-15.png)
#### Klokantech-basic #### Mapbox
![klokantech-basic 2](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-2.png)
![klokantech-basic 4](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-4.png)
![klokantech-basic 8](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-8.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)
* Data: [Mapbox](https://github.com/tumic0/GPXSee-maps/blob/master/World/Mapbox.tpl)
* Style: [Bright](https://github.com/tumic0/QtPBFImagePlugin-styles/blob/master/Mapbox/bright/style.json)
![bright 4](https://tumic0.github.io/QtPBFImagePlugin/images/bright-4.png)
![bright 6](https://tumic0.github.io/QtPBFImagePlugin/images/bright-6.png)
![bright 13](https://tumic0.github.io/QtPBFImagePlugin/images/bright-13.png)
![bright 15](https://tumic0.github.io/QtPBFImagePlugin/images/bright-15.png)
![bright 17](https://tumic0.github.io/QtPBFImagePlugin/images/bright-17.png)
#### Tilezen
* Data: [HERE](https://github.com/tumic0/GPXSee-maps/blob/master/World/here-vector.tpl)
* Style: [Apollo-Bright](https://github.com/tumic0/QtPBFImagePlugin-styles/blob/master/Tilezen/apollo-bright/style.json)
![apollo-bright 4](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-4.png)
![apollo-bright 6](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-6.png)
![apollo-bright 12](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-12.png)
![apollo-bright 15](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-15.png)
![apollo-bright 16](https://tumic0.github.io/QtPBFImagePlugin/images/apollo-bright-16.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,9 +33,12 @@ 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
DEFINES += QT_NO_DEPRECATED_WARNINGS
unix:!macx{ unix:!macx{
LIBS += -lprotobuf-lite \ LIBS += -lprotobuf-lite \
-lz -lz

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

@ -40,7 +40,9 @@ static QColor interpolate(const QPair<qreal, QColor> &p0,
qreal p1h, p1s, p1l, p1a; qreal p1h, p1s, p1l, p1a;
p1.second.getHslF(&p1h, &p1s, &p1l, &p1a); p1.second.getHslF(&p1h, &p1s, &p1l, &p1a);
return QColor::fromHslF(f(p0h, p1h, ratio), f(p0s, p1s, ratio), /* Qt returns a hue of -1 for achromatic colors. We convert it to a hue of 1
using qAbs() to enable interpolation of grayscale colors */
return QColor::fromHslF(f(qAbs(p0h), qAbs(p1h), ratio), f(p0s, p1s, ratio),
f(p0l, p1l, ratio), f(p0a, p1a, ratio)); f(p0l, p1l, ratio), f(p0a, p1a, ratio));
} }

View File

@ -1,35 +1,58 @@
#include <QtEndian> #include <QIODevice>
#include <QDebug>
#include <zlib.h> #include <zlib.h>
#include "gzip.h" #include "gzip.h"
#define CHUNK 16384
QByteArray Gzip::uncompress(const QByteArray &data) QByteArray Gzip::uncompress(QIODevice *device, int limit)
{ {
int ret;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];
qint64 rs;
QByteArray uba; QByteArray uba;
z_stream stream;
quint32 *size = (quint32*)(data.constData() + data.size() - sizeof(quint32));
uba.resize(qFromLittleEndian(*size));
stream.zalloc = Z_NULL; strm.zalloc = Z_NULL;
stream.zfree = Z_NULL; strm.zfree = Z_NULL;
stream.opaque = Z_NULL; strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
if (inflateInit2(&strm, MAX_WBITS + 16) != Z_OK)
return QByteArray();
stream.next_in = (Bytef*)data.constData(); do {
stream.avail_in = data.size(); rs = device->read((char*)in, CHUNK);
stream.next_out = (Bytef*)uba.data(); if (rs < 0) {
stream.avail_out = uba.size(); (void)inflateEnd(&strm);
return QByteArray();
} else if (rs == 0)
break;
else
strm.avail_in = (uInt)rs;
strm.next_in = in;
if (inflateInit2(&stream, MAX_WBITS + 16) != Z_OK) do {
return uba; strm.avail_out = CHUNK;
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
Q_ASSERT(ret != Z_STREAM_ERROR);
switch (ret) {
case Z_NEED_DICT:
case Z_DATA_ERROR:
case Z_MEM_ERROR:
(void)inflateEnd(&strm);
return QByteArray();
}
uba.append((char*)out, CHUNK - strm.avail_out);
if (limit && uba.size() >= limit) {
(void)inflateEnd(&strm);
return uba;
}
} while (!strm.avail_out);
} while (ret != Z_STREAM_END);
if (inflate(&stream, Z_NO_FLUSH) != Z_STREAM_END) { (void)inflateEnd(&strm);
qCritical() << "Invalid gzip data"; return (ret == Z_STREAM_END) ? uba : QByteArray();
uba = QByteArray();
}
inflateEnd(&stream);
return uba;
} }

View File

@ -3,9 +3,11 @@
#include <QByteArray> #include <QByteArray>
class QIODevice;
namespace Gzip namespace Gzip
{ {
QByteArray uncompress(const QByteArray &data); QByteArray uncompress(QIODevice *device, int limit = 0);
} }
#endif // GZIP_H #endif // GZIP_H

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,29 +1,31 @@
#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"
#define TILE_SIZE 256 #define TILE_SIZE 512
#define GZIP_MAGIC 0x1F8B0800 #define GZIP_MAGIC 0x1F8B
#define GZIP_MAGIC_MASK 0xFFFFFF00 #define GZIP_MAGIC_MASK 0xFFFF
#define PBF_MAGIC 0x1A000000 #define PBF_MAGIC 0x1A00
#define PBF_MAGIC_MASK 0xFF000000 #define PBF_MAGIC_MASK 0xFF00
static bool isMagic(quint32 magic, quint32 mask, quint32 value) static bool isMagic(quint16 magic, quint16 mask, quint16 value)
{ {
return ((qFromBigEndian(value) & mask) == magic); return ((qFromBigEndian(value) & mask) == magic);
} }
static bool isGZIPPBF(quint32 magic) static bool isGZIPPBF(quint16 magic)
{ {
return isMagic(GZIP_MAGIC, GZIP_MAGIC_MASK, magic); return isMagic(GZIP_MAGIC, GZIP_MAGIC_MASK, magic);
} }
static bool isPlainPBF(quint32 magic) static bool isPlainPBF(quint16 magic)
{ {
return isMagic(PBF_MAGIC, PBF_MAGIC_MASK, magic); return isMagic(PBF_MAGIC, PBF_MAGIC_MASK, magic);
} }
@ -32,7 +34,7 @@ static bool isPlainPBF(quint32 magic)
bool PBFHandler::canRead() const bool PBFHandler::canRead() const
{ {
if (canRead(device())) { if (canRead(device())) {
setFormat("pbf"); setFormat("mvt");
return true; return true;
} else } else
return false; return false;
@ -40,27 +42,42 @@ bool PBFHandler::canRead() const
bool PBFHandler::canRead(QIODevice *device) bool PBFHandler::canRead(QIODevice *device)
{ {
quint32 magic; quint16 magic;
qint64 size = device->peek((char*)&magic, sizeof(magic)); qint64 size = device->peek((char*)&magic, sizeof(magic));
if (size != sizeof(magic)) if (size != sizeof(magic))
return false; return false;
return (isGZIPPBF(magic) || isPlainPBF(magic)); if (isPlainPBF(magic))
return true;
else if (isGZIPPBF(magic)) {
QByteArray ba(Gzip::uncompress(device, sizeof(magic)));
if (ba.size() < (int)sizeof(magic))
return false;
return isPlainPBF(*((quint16*)ba.constData()));
} else
return false;
} }
bool PBFHandler::read(QImage *image) bool PBFHandler::read(QImage *image)
{ {
quint32 magic; quint16 magic;
if (device()->peek((char*)&magic, sizeof(magic)) != sizeof(magic)) if (device()->peek((char*)&magic, sizeof(magic)) != sizeof(magic))
return false; return false;
QByteArray ba; QByteArray ba;
if (isGZIPPBF(magic)) if (isGZIPPBF(magic)) {
ba = Gzip::uncompress(device()->readAll()); ba = Gzip::uncompress(device());
else if (isPlainPBF(magic)) if (ba.isNull()) {
qCritical() << "Invalid gzip data";
return false;
}
} else if (isPlainPBF(magic))
ba = device()->readAll(); ba = device()->readAll();
if (ba.isNull()) vector_tile::Tile data;
if (!data.ParseFromArray(ba.constData(), ba.size())) {
qCritical() << "Invalid PBF data";
return false; return false;
}
bool ok; bool ok;
int zoom = format().toInt(&ok); int zoom = format().toInt(&ok);
@ -68,11 +85,14 @@ bool PBFHandler::read(QImage *image)
QSize size = _scaledSize.isValid() QSize size = _scaledSize.isValid()
? _scaledSize : QSize(TILE_SIZE, TILE_SIZE); ? _scaledSize : QSize(TILE_SIZE, TILE_SIZE);
QPointF scale = _scaledSize.isValid() QPointF scale = _scaledSize.isValid()
? QPointF(_scaledSize.width() / TILE_SIZE, _scaledSize.height() / TILE_SIZE) ? QPointF((qreal)_scaledSize.width() / TILE_SIZE,
: QPointF(1.0, 1.0); (qreal)_scaledSize.height() / TILE_SIZE) : 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.toInt();
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;
@ -188,6 +230,9 @@ Style::Layer::Paint::Paint(const QJsonObject &json)
// text // text
_textColor = FunctionC(json["text-color"]); _textColor = FunctionC(json["text-color"]);
_textHaloColor = FunctionC(json["text-halo-color"], QColor());
_textHaloWidth = FunctionF(json["text-halo-width"]);
_textHaloBlur = FunctionF(json["text-halo-blur"]);
} }
QPen Style::Layer::Paint::pen(Type type, int zoom) const QPen Style::Layer::Paint::pen(Type type, int zoom) const
@ -392,7 +437,7 @@ Text::RotationAlignment Style::Layer::Layout::textRotationAlignment(int zoom)
} }
Style::Layer::Layer(const QJsonObject &json) Style::Layer::Layer(const QJsonObject &json)
: _type(Unknown), _minZoom(-1), _maxZoom(-1) : _type(Unknown), _minZoom(0), _maxZoom(24)
{ {
// type // type
QString type(json["type"].toString()); QString type(json["type"].toString());
@ -428,16 +473,12 @@ 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 && (zoom < _minZoom || zoom > _maxZoom))
if (_minZoom > 0 && zoom < _minZoom) return false;
return false;
if (_maxZoom > 0 && zoom > _maxZoom)
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
@ -469,16 +510,17 @@ void Style::Layer::setTextProperties(Tile &tile) const
t.setFont(_layout.font(zoom)); t.setFont(_layout.font(zoom));
t.setSymbolPlacement(_layout.symbolPlacement(zoom)); t.setSymbolPlacement(_layout.symbolPlacement(zoom));
t.setRotationAlignment(_layout.textRotationAlignment(zoom)); t.setRotationAlignment(_layout.textRotationAlignment(zoom));
t.setHalo(_paint.halo(zoom));
} }
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,44 +567,38 @@ 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
{ {
QRectF rect(QPointF(0, 0), tile.size()); QRectF rect(QPointF(0, 0), QSizeF(tile.size().width() / tile.scale().x(),
tile.size().height() / tile.scale().y()));
QPainterPath path; QPainterPath path;
path.addRect(rect); path.addRect(rect);
@ -578,3 +614,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,21 +30,12 @@ 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 {
public: public:
Layer() : _type(Unknown), _minZoom(-1), _maxZoom(-1) {} Layer() : _type(Unknown), _minZoom(0), _maxZoom(24) {}
Layer(const QJsonObject &json); Layer(const QJsonObject &json);
const QString &sourceLayer() const {return _sourceLayer;} const QString &sourceLayer() const {return _sourceLayer;}
@ -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,11 +137,13 @@ 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 Text::Halo halo(int zoom) const
{return _fillPattern.value(zoom);} {return Text::Halo(_textHaloColor.value(zoom),
_textHaloWidth.value(zoom), _textHaloBlur.value(zoom));}
private: private:
FunctionC _textColor; FunctionC _textColor;
FunctionC _textHaloColor;
FunctionC _lineColor; FunctionC _lineColor;
FunctionC _fillColor; FunctionC _fillColor;
FunctionC _fillOutlineColor; FunctionC _fillOutlineColor;
@ -152,6 +151,8 @@ private:
FunctionF _fillOpacity; FunctionF _fillOpacity;
FunctionF _lineOpacity; FunctionF _lineOpacity;
FunctionF _lineWidth; FunctionF _lineWidth;
FunctionF _textHaloWidth;
FunctionF _textHaloBlur;
FunctionB _fillAntialias; FunctionB _fillAntialias;
QVector<qreal> _lineDasharray; QVector<qreal> _lineDasharray;
FunctionS _fillPattern; FunctionS _fillPattern;
@ -165,9 +166,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,6 +1,7 @@
#include <QFontMetrics> #include <QFontMetrics>
#include <QPainter> #include <QPainter>
#include <QSet> #include <QSet>
#include <QMap>
#include "text.h" #include "text.h"
#include "textpointitem.h" #include "textpointitem.h"
#include "textpathitem.h" #include "textpathitem.h"
@ -31,22 +32,36 @@ void Text::addLabel(const QString &text, const QImage &icon,
{ {
TextItem *ti; TextItem *ti;
switch (_placement) { if (_alignment == Viewport) {
case Line: QMap<qreal, int> map;
if (_alignment == Viewport) for (int j = 0; j < path.elementCount(); j++) {
QLineF l(path.elementAt(j), _sceneRect.center());
map.insert(l.length(), j);
}
QMap<qreal, int>::const_iterator jt = map.constBegin();
ti = new TextPointItem(text, path.elementAt(jt.value()), _font,
_maxWidth, _anchor, icon);
while (true) {
if (_sceneRect.contains(ti->boundingRect()))
break;
if (++jt == map.constEnd())
break;
static_cast<TextPointItem*>(ti)->setPos(path.elementAt(
jt.value()));
}
} else {
switch (_placement) {
case Line:
ti = new TextPathItem(text, path, _font, _maxAngle, _sceneRect);
break;
case LineCenter:
ti = new TextPointItem(text, path.pointAtPercent(0.5), _font,
_maxWidth, _anchor, icon);
break;
default:
ti = new TextPointItem(text, path.elementAt(0), _font, ti = new TextPointItem(text, path.elementAt(0), _font,
_maxWidth, _anchor, icon); _maxWidth, _anchor, icon);
else }
ti = new TextPathItem(text, path, _font, _maxAngle, _sceneRect);
break;
case LineCenter:
ti = new TextPointItem(text, path.pointAtPercent(0.5), _font,
_maxWidth, _anchor, icon);
break;
default:
ti = new TextPointItem(text, path.elementAt(0), _font, _maxWidth,
_anchor, icon);
break;
} }
// Note: empty path == point geometry (single move) // Note: empty path == point geometry (single move)
@ -56,6 +71,7 @@ void Text::addLabel(const QString &text, const QImage &icon,
} }
ti->setPen(_pen); ti->setPen(_pen);
ti->setHalo(_halo);
addItem(ti); addItem(ti);
QList<TextItem*> ci = collidingItems(ti); QList<TextItem*> ci = collidingItems(ti);

View File

@ -34,6 +34,22 @@ public:
Auto Auto
}; };
class Halo {
public:
Halo() : _width(0), _blur(0) {}
Halo(const QColor &color, qreal width, qreal blur)
: _color(color), _width(width), _blur(blur) {}
const QColor &color() const {return _color;}
qreal width() const {return _width;}
qreal blur() const {return _blur;}
private:
QColor _color;
qreal _width;
qreal _blur;
};
Text(const QSize &size) : _sceneRect(QRectF(QPointF(0, 0), size)) {} Text(const QSize &size) : _sceneRect(QRectF(QPointF(0, 0), size)) {}
~Text(); ~Text();
@ -45,6 +61,7 @@ public:
void setSymbolPlacement(SymbolPlacement placement); void setSymbolPlacement(SymbolPlacement placement);
void setRotationAlignment(RotationAlignment alignment) void setRotationAlignment(RotationAlignment alignment)
{_alignment = alignment;} {_alignment = alignment;}
void setHalo(const Halo &halo) {_halo = halo;}
void addLabel(const QString &text, const QImage &icon, void addLabel(const QString &text, const QImage &icon,
const QPainterPath &path); const QPainterPath &path);
@ -65,6 +82,7 @@ private:
RotationAlignment _alignment; RotationAlignment _alignment;
QFont _font; QFont _font;
QPen _pen; QPen _pen;
Halo _halo;
}; };
#endif // TEXT_H #endif // TEXT_H

39
src/textitem.cpp Normal file
View File

@ -0,0 +1,39 @@
#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());
}
qreal 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.80 : 0.73;
if (_font.bold())
ratio *= 1.1;
if (_font.italic())
ratio *= 0.9;
// The rest (Latin scripts, Arabic, ...)
} else {
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.75 : 0.63;
if (_font.bold())
ratio *= 1.1;
if (_font.italic())
ratio *= 0.9;
}
return ratio * _font.pixelSize();
}

View File

@ -1,20 +1,28 @@
#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>
#include "text.h"
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;}
const Text::Halo &halo() const {return _halo;}
void setPen(const QPen &pen) {_pen = pen;} void setPen(const QPen &pen) {_pen = pen;}
void setHalo(const Text::Halo &halo) {_halo = halo;}
virtual QPainterPath shape() const = 0; virtual QPainterPath shape() const = 0;
virtual QRectF boundingRect() const = 0; virtual QRectF boundingRect() const = 0;
@ -23,38 +31,16 @@ 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) qreal 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;
QFont _font; QFont _font;
QPen _pen; QPen _pen;
Text::Halo _halo;
bool _visible; bool _visible;
}; };

View File

@ -3,23 +3,23 @@
#include "textpathitem.h" #include "textpathitem.h"
static QPointF intersection(const QLineF &line, const QRectF &rect) static bool intersection(const QLineF &line, const QRectF &rect,
QPointF *p)
{ {
QPointF p; if (line.intersect(QLineF(rect.topLeft(), rect.topRight()), p)
if (line.intersect(QLineF(rect.topLeft(), rect.topRight()), &p)
== QLineF::BoundedIntersection) == QLineF::BoundedIntersection)
return p; return true;
if (line.intersect(QLineF(rect.topLeft(), rect.bottomLeft()), &p) if (line.intersect(QLineF(rect.topLeft(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection) == QLineF::BoundedIntersection)
return p; return true;
if (line.intersect(QLineF(rect.bottomRight(), rect.bottomLeft()), &p) if (line.intersect(QLineF(rect.bottomRight(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection) == QLineF::BoundedIntersection)
return p; return true;
if (line.intersect(QLineF(rect.bottomRight(), rect.topRight()), &p) if (line.intersect(QLineF(rect.bottomRight(), rect.topRight()), p)
== QLineF::BoundedIntersection) == QLineF::BoundedIntersection)
return p; return true;
return rect.center(); return false;
} }
static QPainterPath subpath(const QList<QLineF> &lines, int start, int end, static QPainterPath subpath(const QList<QLineF> &lines, int start, int end,
@ -63,6 +63,7 @@ static QList<QLineF> lineString(const QPainterPath &path,
{ {
QList<QLineF> lines; QList<QLineF> lines;
int start = 0, end = path.elementCount() - 1; int start = 0, end = path.elementCount() - 1;
QPointF p;
for (int i = 0; i < path.elementCount(); i++) { for (int i = 0; i < path.elementCount(); i++) {
if (boundingRect.contains(path.elementAt(i))) { if (boundingRect.contains(path.elementAt(i))) {
@ -79,16 +80,14 @@ static QList<QLineF> lineString(const QPainterPath &path,
if (start > 0) { if (start > 0) {
QLineF l(path.elementAt(start-1), path.elementAt(start)); QLineF l(path.elementAt(start-1), path.elementAt(start));
QPointF p(intersection(l, boundingRect)); if (intersection(l, boundingRect, &p))
if (p != boundingRect.center())
lines.append(QLineF(p, path.elementAt(start))); lines.append(QLineF(p, path.elementAt(start)));
} }
for (int i = start + 1; i <= end; i++) for (int i = start + 1; i <= end; i++)
lines.append(QLineF(path.elementAt(i-1), path.elementAt(i))); lines.append(QLineF(path.elementAt(i-1), path.elementAt(i)));
if (end < path.elementCount() - 1) { if (end < path.elementCount() - 1) {
QLineF l(path.elementAt(end), path.elementAt(end+1)); QLineF l(path.elementAt(end), path.elementAt(end+1));
QPointF p(intersection(l, boundingRect)); if (intersection(l, boundingRect, &p))
if (p != boundingRect.center())
lines.append(QLineF(path.elementAt(end), p)); lines.append(QLineF(path.elementAt(end), p));
} }
@ -134,9 +133,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); qreal cw = avgCharWidth();
int textWidth = text.size() * cw; int textWidth = text.size() * cw;
if (textWidth > path.length()) if (textWidth > path.length())
return; return;
@ -146,7 +145,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());
@ -167,10 +165,35 @@ void TextPathItem::paint(QPainter *painter) const
qreal percent = (1.0 - factor) / 2.0; qreal percent = (1.0 - factor) / 2.0;
painter->setFont(font()); painter->setFont(font());
painter->setPen(pen());
QTransform t = painter->transform(); QTransform t = painter->transform();
if (halo().color().isValid() && halo().width() > 0) {
painter->setPen(halo().color());
for (int i = 0; i < text().size(); i++) {
QPointF point = _path.pointAtPercent(percent);
qreal angle = _path.angleAtPercent(percent);
painter->translate(point);
painter->rotate(-angle);
painter->drawText(QPoint(-1, fm.descent() - 1), text().at(i));
painter->drawText(QPoint(1, fm.descent() + 1), text().at(i));
painter->drawText(QPoint(-1, fm.descent() + 1), text().at(i));
painter->drawText(QPoint(1, fm.descent() -1), text().at(i));
painter->drawText(QPoint(0, fm.descent() - 1), text().at(i));
painter->drawText(QPoint(0, fm.descent() + 1), text().at(i));
painter->drawText(QPoint(-1, fm.descent()), text().at(i));
painter->drawText(QPoint(1, fm.descent()), text().at(i));
painter->setTransform(t);
int width = fm.charWidth(text(), i);
percent += ((qreal)width / (qreal)textWidth) * factor;
}
percent = (1.0 - factor) / 2.0;
}
painter->setPen(pen());
for (int i = 0; i < text().size(); i++) { for (int i = 0; i < text().size(); i++) {
QPointF point = _path.pointAtPercent(percent); QPointF point = _path.pointAtPercent(percent);
qreal angle = _path.angleAtPercent(percent); qreal angle = _path.angleAtPercent(percent);

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,23 @@
#include <QPainter> #include <QPainter>
#include <QtMath> #include <QtMath>
#include <QStaticText>
#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::fuzzyBoundingRect() const
const QFont &font, int maxWidth)
{ {
QFontMetrics fm(font); int fs = font().pixelSize();
int limit = font.pixelSize() * maxWidth; if (text().size() <= 3)
// Italic fonts overflow the computed bounding rect, so reduce it return QRectF(0, 0, text().size() * fs, fs * 1.6);
// a little bit.
if (font.italic())
limit -= font.pixelSize() / 2.0;
QRect br = fm.boundingRect(QRect(0, 0, limit, 0), FLAGS, str); int limit = fs * _maxWidth;
Q_ASSERT(br.isValid()); qreal cw = avgCharWidth();
// Expand the bounding rect back to the real content size
if (font.italic())
br.adjust(-font.pixelSize() / 4.0, 0, font.pixelSize() / 4.0, 0);
return br;
}
QRectF TextPointItem::fuzzyBoundingRect(const QString &str,
const QFont &font, int maxWidth)
{
int limit = font.pixelSize() * maxWidth;
qreal cw = avgCharWidth(str, font);
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) {
@ -48,7 +30,7 @@ QRectF TextPointItem::fuzzyBoundingRect(const QString &str,
if (wl + pl < limit) { if (wl + pl < limit) {
pl += wl + cw; pl += wl + cw;
} else { } else {
if (wl > limit) { if (wl >= limit) {
if (pl > 0) if (pl > 0)
lines++; lines++;
} else } else
@ -65,27 +47,32 @@ QRectF TextPointItem::fuzzyBoundingRect(const QString &str,
} }
} }
return QRectF(0, 0, width, lines * lh); return QRectF(0, 0, width, lines * fs * 1.6);
} }
QRectF TextPointItem::computeTextRect(BoundingRectFunction brf) const QRectF TextPointItem::moveTextRect(const QRectF &rect) 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(rect);
switch (_anchor) { switch (_anchor) {
case Text::Center: case Text::Center:
textRect.moveCenter(_pos); textRect.moveCenter(_pos);
break; break;
case Text::Left: case Text::Left:
textRect.moveTopLeft(_pos - QPointF(-iconRect.width() / 2, textRect.moveTopLeft(_pos - QPointF(-iconRect.width() / 2
textRect.height() / 2)); - font().pixelSize()/4.0, textRect.height() / 2));
break; break;
case Text::Right: case Text::Right:
textRect.moveTopRight(_pos - QPointF(iconRect.width() / 2, textRect.moveTopRight(_pos - QPointF(iconRect.width() / 2
textRect.height() / 2)); + font().pixelSize()/4.0, textRect.height() / 2));
break; break;
case Text::Bottom: case Text::Bottom:
textRect.moveTopLeft(_pos - QPointF(textRect.width() / 2, textRect.moveTopLeft(_pos - QPointF(textRect.width() / 2,
@ -102,15 +89,19 @@ 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); _textRect = fuzzyBoundingRect();
_boundingRect = computeTextRect(fuzzyBoundingRect); _boundingRect = moveTextRect(_textRect);
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;
} }
@ -118,22 +109,59 @@ TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
_shape.addRect(_boundingRect); _shape.addRect(_boundingRect);
} }
void TextPointItem::setPos(const QPointF &pos)
{
QPointF d(_boundingRect.left() - _pos.x(), _boundingRect.top() - _pos.y());
_boundingRect.moveTopLeft(pos + d);
_shape = QPainterPath();
_shape.addRect(_boundingRect);
_pos = pos;
}
void TextPointItem::paint(QPainter *painter) const void TextPointItem::paint(QPainter *painter) const
{ {
//painter->setPen(Qt::red);
//painter->drawRect(_boundingRect);
QRectF textRect; QRectF textRect;
if (!_icon.isNull()) {
textRect = computeTextRect(exactBoundingRect);
painter->drawImage(_pos - QPointF(_icon.width()
/ _icon.devicePixelRatioF() / 2, _icon.height()
/ _icon.devicePixelRatioF() / 2), _icon);
} else
textRect = computeTextRect(fuzzyBoundingRect);
painter->setFont(font()); painter->setFont(font());
painter->setPen(pen()); painter->setPen(pen());
painter->drawText(textRect, FLAGS, text());
if (!_icon.isNull()) {
textRect = moveTextRect(painter->boundingRect(_textRect, FLAGS, text()));
#ifdef ENABLE_HIDPI
painter->drawImage(_pos - QPointF(_icon.width()
/ _icon.devicePixelRatioF() / 2, _icon.height()
/ _icon.devicePixelRatioF() / 2), _icon);
#else // ENABLE_HIDPI
painter->drawImage(_pos - QPointF(_icon.width() / 2,
_icon.height() / 2), _icon);
#endif // ENABLE_HIDPI
} else
textRect = _boundingRect;
if (hasHalo()) {
QStaticText st(text());
st.setTextFormat(Qt::PlainText);
st.setTextWidth(textRect.width());
st.setTextOption(QTextOption(Qt::AlignHCenter));
st.setPerformanceHint(QStaticText::AggressiveCaching);
painter->setPen(halo().color());
painter->drawStaticText(textRect.topLeft() + QPointF(-1, -1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(+1, +1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(-1, +1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(+1, -1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(0, -1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(0, +1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(-1, 0), st);
painter->drawStaticText(textRect.topLeft() + QPointF(+1, 0), st);
painter->setPen(pen());
painter->drawStaticText(textRect.topLeft(), st);
} else
painter->drawText(textRect, FLAGS, text());
//painter->setBrush(Qt::NoBrush);
//painter->setPen(Qt::red);
//painter->drawRect(_boundingRect);
//painter->setPen(Qt::blue);
//painter->drawRect(textRect);
} }

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
{ {
@ -17,19 +15,17 @@ public:
QPainterPath shape() const {return _shape;} QPainterPath shape() const {return _shape;}
void paint(QPainter *painter) const; void paint(QPainter *painter) const;
void setPos(const QPointF &pos);
private: private:
typedef QRectF (*BoundingRectFunction)(const QString &, const QFont &, int); QRectF fuzzyBoundingRect() const;
QRectF moveTextRect(const QRectF &rect) const;
static QRectF exactBoundingRect(const QString &str, const QFont &font, bool hasHalo() const
int maxWidth); {return halo().color().isValid() && halo().width() > 0;}
static QRectF fuzzyBoundingRect(const QString &str, const QFont &font,
int maxWidth);
QRectF computeTextRect(BoundingRectFunction brf) const;
QPointF _pos; QPointF _pos;
QPainterPath _shape; QPainterPath _shape;
QRectF _boundingRect; QRectF _textRect, _boundingRect;
QImage _icon; QImage _icon;
int _maxWidth; int _maxWidth;
Text::Anchor _anchor; Text::Anchor _anchor;