98 Commits

Author SHA1 Message Date
c86eb7bac2 Increase major version due to the API change
The API should be backward compatible but the overzoom extension should rather
be promoted with a major version number change.
2023-12-10 18:14:54 +01:00
3ef6c55d20 Code samples cleanup 2023-12-10 17:46:39 +01:00
98933deb0e Added HiDPI and Overzoom sections 2023-12-10 15:53:19 +01:00
6e1bc09d62 Cosmetics 2023-12-10 15:19:35 +01:00
ea98da4a74 Added overzoom description 2023-12-10 15:16:23 +01:00
8261ee2e79 Keep the overzoom and scaled size separated 2023-12-10 15:12:13 +01:00
d5c315efbe Version++ 2023-12-10 14:44:36 +01:00
39ffdaf616 Added support for overzoom 2023-12-10 14:43:07 +01:00
38b6e2320b Example codes cleanup 2023-12-10 08:18:16 +01:00
b36fb5fa92 Fixed maximal lines angle check 2023-10-22 23:53:39 +02:00
a7b7d16f4f Version++ 2023-10-22 23:47:28 +02:00
8cf73b5bb5 Silenced compiler warning 2023-10-10 23:04:39 +02:00
038f4e7d64 Fixed line splitting algorithm 2023-10-10 08:16:20 +02:00
aaff1c0bdb Fixed/improved path label layout algorithm 2023-10-09 21:59:05 +02:00
37c6c36204 Improved Windows dll info 2023-10-08 11:02:57 +02:00
f901243baa Remove non-ASCII characters from project file
UTF-8 is not supported by qmake
2023-10-07 01:38:39 +02:00
19c6b9854f Fixed artifact path
With the introduction of the VERSION qmake variable, the dll name has changed
to pbf2.dll.
2023-10-07 00:25:47 +02:00
52d15c63bd Extended Windows executable info 2023-10-07 00:25:12 +02:00
71331b06f1 Version++ 2023-10-06 22:55:40 +02:00
d6a24a4498 Switch back to Homebrew-only based builds
Build all using Homebrew packages and do not provide artifacts as those are
unusable outside the particular build machine.
2023-10-06 22:27:44 +02:00
a26c86dbd8 Checkout action version++ 2023-10-06 22:26:58 +02:00
0db93fd5ee Use GPXSee compatible Qt versions 2023-09-26 20:14:58 +02:00
f8044efda6 Fixed triplet path 2023-09-26 20:14:18 +02:00
450a1458a3 Use the propper static/dynamic crt triplet 2023-09-26 19:43:01 +02:00
bff8c7229c Fixed artifacts path 2023-09-22 10:49:16 +02:00
ff569644b6 Back to non-static vcpkg 2023-09-22 09:57:43 +02:00
6a32f0a447 Rename zlib lib to fit the pro file 2023-09-22 09:19:37 +02:00
1051a1c5b2 Use the static vcpkg builds 2023-09-22 02:02:45 +02:00
11e360d9a4 Fixed protoc path + added cache 2023-09-22 01:18:25 +02:00
5577590f6b Added AppVeyor build config 2023-09-22 00:50:12 +02:00
9dba35082b Use protobuf@21 in OS X build 2023-09-21 01:00:14 +02:00
76f1a23f8d Switch to Qt6 in OS X build 2023-09-21 00:45:41 +02:00
f931b653c4 Yet anothe OS X build fix 2023-09-21 00:23:35 +02:00
b0885a4b05 Trying to fix the OS X build 2023-09-21 00:12:48 +02:00
7c34a2c4e1 Use protobuf@21 in OS X builds 2023-09-20 23:48:06 +02:00
0cb3edda05 Fixed compiler warning 2023-09-20 23:36:30 +02:00
871aad37da Fix MacOS CI build 2023-05-05 23:26:03 +02:00
12c878d8e2 Proprly initialize the tile with transparent background 2023-05-05 19:15:36 +02:00
44862114ab Added Ordnance Survey and Esri styles info 2022-06-08 01:02:04 +02:00
1065a27bfb Fixed cut&paste error 2022-01-04 08:36:18 +01:00
3c22cd7823 Added linux CI build 2022-01-04 08:34:24 +01:00
1afd242302 Get rid of TravisCI 2021-10-27 17:48:33 +02:00
ae8b2f00db Added GitHub actions OS X build 2021-10-27 17:42:47 +02:00
e22ea9aa12 Updated Maputnik editor URL 2021-07-16 22:23:58 +02:00
62a78fdaae protobufs are already installed 2021-05-28 08:00:28 +02:00
63f86a900e Trying to fix the OS X TravisCI build... 2021-05-28 07:29:40 +02:00
ea8819a381 Updated minimal Qt version
Qt 5.11 works as well
2020-12-29 10:50:49 +01:00
ee49c05eb9 Changed Travis build distro to focal 2020-12-22 23:51:30 +01:00
29d4008068 A better regexp handling 2020-12-22 22:30:05 +01:00
60691060ea Added support for Qt6
Minimal Qt version is now Qt 5.12
2020-12-22 22:17:00 +01:00
9b63d1d362 Trying to fix the travis-ci OS X build... 2020-09-02 22:28:36 +02:00
7822bdd42c Fixed uninitialized visibility property 2020-09-01 22:25:37 +02:00
fe97e9d238 Added link to pbf2png 2020-07-03 01:23:40 +02:00
fd02b83b47 Use a better scoped singleton 2020-07-01 21:11:09 +02:00
7684f4fdcf Code cleanup 2020-07-01 20:48:37 +02:00
9772c2f67f Added support for layout visibility style option 2020-06-13 17:15:49 +02:00
e3c940dd07 Added github FUNDING.yml file 2020-05-03 11:42:01 +02:00
e2bb9f5b14 Added gitignore file 2020-05-03 11:40:08 +02:00
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
30 changed files with 710 additions and 329 deletions

32
.appveyor.yml Normal file
View File

@ -0,0 +1,32 @@
version: 3.0.{build}
configuration:
- Release
image:
- Visual Studio 2022
environment:
VCPKGDIR: C:\tools\vcpkg\installed\x64-windows-static-md
matrix:
- QTDIR: C:\Qt\5.15\msvc2019_64
- QTDIR: C:\Qt\6.5\msvc2019_64
install:
- cmd: |-
set PATH=%QTDIR%\bin;%VCPKGDIR%\tools\protobuf;%PATH%
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat
vcpkg install protobuf:x64-windows-static-md
vcpkg install zlib:x64-windows-static-md
copy /y %VCPKGDIR%\lib\zlib.lib %VCPKGDIR%\lib\zlibstatic.lib
build_script:
- cmd: |-
qmake PROTOBUF=%VCPKGDIR% ZLIB=%VCPKGDIR% pbfplugin.pro
nmake release
artifacts:
- path: release\pbf2.dll
cache:
- C:\tools\vcpkg\installed\

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: tumic0

22
.github/workflows/linux.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Linux
on:
push:
branches:
- master
jobs:
build:
name: QtPBFImagePlugin
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install qtbase5-dev qtbase5-dev-tools qt5-qmake libprotobuf-dev protobuf-compiler zlib1g-dev
- name: Configure build
run: qmake pbfplugin.pro
- name: Build project
run: make -j2

37
.github/workflows/osx.yml vendored Normal file
View File

@ -0,0 +1,37 @@
name: OS X
on:
push:
branches:
- master
jobs:
qt5:
name: QtPBFImagePlugin Qt5 build
runs-on: macos-latest
steps:
- name: Set environment variables
run: echo "PATH=/usr/local/opt/qt@5/bin:/usr/local/opt/protobuf@21/bin:$PATH" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
run: brew install qt@5 protobuf@21
- name: Configure build
run: qmake PROTOBUF=/usr/local/opt/protobuf@21 pbfplugin.pro
- name: Build project
run: make -j3
qt6:
name: QtPBFImagePlugin Qt6 build
runs-on: macos-latest
steps:
- name: Set environment variables
run: echo "PATH=/usr/local/opt/qt@6/bin:/usr/local/opt/protobuf@21/bin:$PATH" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v3
- name: Install dependencies
run: brew install qt@6 protobuf@21
- name: Configure build
run: qmake PROTOBUF=/usr/local/opt/protobuf@21 pbfplugin.pro
- name: Build project
run: make -j3

17
.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
# Object files
*.o
# Protobuf stuff
protobuf/vector_tile.pb.cc
protobuf/vector_tile.pb.h
# Qt stuff
/.qmake.stash
moc_*.cpp
moc_*.h
qrc_*.cpp
Makefile*
# lib
libpbf.so
pbf.dylib

View File

@ -1,22 +0,0 @@
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

102
README.md
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](https://maputnik.github.io/editor) 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
@ -27,20 +23,51 @@ QPixmap::loadFromData() functions. The zoom number is passed as ASCII string
to the functions: to the functions:
```cpp ```cpp
QPixmap pm; QPixmap pm;
pm.loadFromData(tileData, QString::number(zoom).toLatin1()); pm.loadFromData(data, QByteArray::number(zoom));
``` ```
For a complete code sample see the [pbf2png](https://github.com/tumic0/pbf2png)
conversion utility.
### HiDPI
The plugin supports vector scaling using QImageReader's setScaledSize() method, 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()); QImage img;
reader.setScaledSize(QSize(512, 512)); QImageReader reader(file, QByteArray::number(zoom));
reader.read(&image); reader.setScaledSize(QSize(1024, 1024));
reader.read(&img);
``` ```
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).
### Overzoom
Since version 3 of the plugin tile overzoom is supported. If you set *format*
to `$zoom;$overzoom`:
```cpp
QPixmap pm;
QByteArray fmt(QByteArray::number(zoom) + ';' + QByteArray::number(overzoom));
pm.loadFromData(data, fmt);
```
you will get (512<<overzoom)x(512<<overzoom)px tiles with a pixel ratio of 1.
When overzoom is combined with setScaledSize(), the base size is the overzoomed
tile size.
## 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, Ordnance Survey, Esri, ...)
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.4 (5.6 for HiDPI support) * Qt5 >= 5.11 or Qt6
* Google Protocol Buffers (protobuf-lite) * Google Protocol Buffers (protobuf-lite)
* Zlib * Zlib
@ -72,22 +99,45 @@ for most common distros available on OBS.
style are ignored. style are ignored.
* Text PBF features must have a unique id (OpenMapTiles >= v3.7) for the text layout * 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) * Data: [Mapbox](https://github.com/tumic0/GPXSee-maps/blob/master/World/Mapbox.tpl)
![klokantech-basic 8](https://tumic0.github.io/QtPBFImagePlugin/images/klokantech-basic-8.png) * Style: [Bright](https://github.com/tumic0/QtPBFImagePlugin-styles/blob/master/Mapbox/bright/style.json)
![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) ![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 ## Applications using QtPBFImagePlugin
* [GPXSee](https://www.gpxsee.org) * [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

@ -2,6 +2,7 @@ TARGET = pbf
TEMPLATE = lib TEMPLATE = lib
CONFIG += plugin CONFIG += plugin
QT += gui QT += gui
VERSION = 3.0
PROTOS = protobuf/vector_tile.proto PROTOS = protobuf/vector_tile.proto
include(protobuf/vector_tile.pri) include(protobuf/vector_tile.pri)
@ -20,8 +21,7 @@ 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 \
@ -37,6 +37,8 @@ SOURCES += src/pbfplugin.cpp \
src/textitem.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
@ -46,6 +48,10 @@ win32 {
$$ZLIB/include $$ZLIB/include
LIBS += $$PROTOBUF/lib/libprotobuf-lite.lib \ LIBS += $$PROTOBUF/lib/libprotobuf-lite.lib \
$$ZLIB/lib/zlibstatic.lib $$ZLIB/lib/zlibstatic.lib
QMAKE_TARGET_PRODUCT = QtPBFImagePlugin
QMAKE_TARGET_DESCRIPTION = Qt $$QT_VERSION MVT/PBF image plugin
QMAKE_TARGET_COPYRIGHT = Copyright (c) 2023 Martin Tuma
} }
macx { macx {
INCLUDEPATH += $$PROTOBUF/include INCLUDEPATH += $$PROTOBUF/include

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";

View File

@ -1,10 +0,0 @@
#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

@ -34,13 +34,19 @@ static QColor interpolate(const QPair<qreal, QColor> &p0,
? progress / difference ? progress / difference
: (pow(base, progress) - 1) / (pow(base, difference) - 1); : (pow(base, progress) - 1) / (pow(base, difference) - 1);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
qreal p0h, p0s, p0l, p0a; qreal p0h, p0s, p0l, p0a;
p0.second.getHslF(&p0h, &p0s, &p0l, &p0a);
qreal p1h, p1s, p1l, p1a; qreal p1h, p1s, p1l, p1a;
#else // QT6
float p0h, p0s, p0l, p0a;
float p1h, p1s, p1l, p1a;
#endif // QT6
p0.second.getHslF(&p0h, &p0s, &p0l, &p0a);
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_END;
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,9 +1,5 @@
#include "pbf.h" #include "pbf.h"
#define POLYGON vector_tile::Tile_GeomType_POLYGON
#define LINESTRING vector_tile::Tile_GeomType_LINESTRING
#define POINT vector_tile::Tile_GeomType_POINT
#define MOVE_TO 1 #define MOVE_TO 1
#define LINE_TO 2 #define LINE_TO 2
#define CLOSE_PATH 7 #define CLOSE_PATH 7
@ -104,7 +100,7 @@ PBF::Layer::Layer(const vector_tile::Tile_Layer *data) : _data(data)
_features.reserve(data->features_size()); _features.reserve(data->features_size());
for (int i = 0; i < data->features_size(); i++) for (int i = 0; i < data->features_size(); i++)
_features.append(Feature(&(data->features(i)), this)); _features.append(Feature(&(data->features(i)), this));
qSort(_features.begin(), _features.end()); std::sort(_features.begin(), _features.end());
} }
PBF::PBF(const vector_tile::Tile &tile) PBF::PBF(const vector_tile::Tile &tile)
@ -117,7 +113,5 @@ PBF::PBF(const vector_tile::Tile &tile)
PBF::~PBF() PBF::~PBF()
{ {
for (QHash<QString, Layer*>::iterator it = _layers.begin(); qDeleteAll(_layers);
it != _layers.end(); it++)
delete *it;
} }

View File

@ -8,24 +8,24 @@
#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);
} }
@ -34,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;
@ -42,43 +42,56 @@ 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())
return false;
vector_tile::Tile data; vector_tile::Tile data;
if (!data.ParseFromArray(ba.constData(), ba.size())) { if (!data.ParseFromArray(ba.constData(), ba.size())) {
qCritical() << "Invalid PBF data"; qCritical() << "Invalid PBF data";
return false; return false;
} }
bool ok; QList<QByteArray> list(format().split(';'));
int zoom = format().toInt(&ok); int zoom = list.size() ? list.first().toInt() : 0;
int overzoom = (list.size() > 1) ? list.at(1).toInt() : 0;
QSize scaledSize(_scaledSize.isValid()
? _scaledSize : QSize(TILE_SIZE, TILE_SIZE));
QSize size(scaledSize.width()<<overzoom,
scaledSize.height()<<overzoom);
QPointF scale((qreal)scaledSize.width() / TILE_SIZE,
(qreal)scaledSize.height() / TILE_SIZE);
QSize size = _scaledSize.isValid()
? _scaledSize : QSize(TILE_SIZE, TILE_SIZE);
QPointF scale = _scaledSize.isValid()
? QPointF(_scaledSize.width() / TILE_SIZE, _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); Tile tile(image, zoom, scale);
_style->render(data, tile); _style->render(data, tile);

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

@ -9,9 +9,9 @@
Loading the sprites atlas image must be deferred until all image plugins Loading the sprites atlas image must be deferred until all image plugins
are loaded, otherwise reading the image will cause a deadlock! are loaded, otherwise reading the image will cause a deadlock!
*/ */
static const QImage *atlas(const QString &fileName) static const QImage &atlas(const QString &fileName)
{ {
static QImage *img = new QImage(fileName); static QImage img(fileName);
return img; return img;
} }
@ -86,18 +86,18 @@ QImage Sprites::icon(const QString &name) const
if (_imageFile.isEmpty()) if (_imageFile.isEmpty())
return QImage(); return QImage();
const QImage *img = atlas(_imageFile); const QImage &img = atlas(_imageFile);
if (img->isNull()) if (img.isNull())
return QImage(); return QImage();
QMap<QString, Sprite>::const_iterator it = _sprites.find(name); QMap<QString, Sprite>::const_iterator it = _sprites.find(name);
if (it == _sprites.constEnd()) if (it == _sprites.constEnd())
return QImage(); return QImage();
if (!img->rect().contains(it->rect())) if (!img.rect().contains(it->rect()))
return QImage(); return QImage();
QImage ret(img->copy(it->rect())); QImage ret(img.copy(it->rect()));
ret.setDevicePixelRatio(it->pixelRatio()); ret.setDevicePixelRatio(it->pixelRatio());
return ret; return ret;

View File

@ -5,6 +5,7 @@
#include <QJsonArray> #include <QJsonArray>
#include <QFileInfo> #include <QFileInfo>
#include <QDir> #include <QDir>
#include <QRegularExpression>
#include <QDebug> #include <QDebug>
#include "text.h" #include "text.h"
#include "color.h" #include "color.h"
@ -138,22 +139,46 @@ bool Style::Layer::Filter::match(const PBF::Feature &feature) const
if (!(v = feature.value(_kv.first))) if (!(v = feature.value(_kv.first)))
return false; return false;
else else
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return *v > _kv.second; return *v > _kv.second;
#else // QT6
return (QVariant::compare(*v, _kv.second)
== QPartialOrdering::Greater);
#endif // QT6
case GE: case GE:
if (!(v = feature.value(_kv.first))) {if (!(v = feature.value(_kv.first)))
return false; return false;
else else {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return *v >= _kv.second; return *v >= _kv.second;
#else // QT6
QPartialOrdering res = QVariant::compare(*v, _kv.second);
return (res == QPartialOrdering::Greater
|| res == QPartialOrdering::Equivalent);
#endif // QT6
}}
case LT: case LT:
if (!(v = feature.value(_kv.first))) if (!(v = feature.value(_kv.first)))
return false; return false;
else else
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return *v < _kv.second; return *v < _kv.second;
#else // QT6
return (QVariant::compare(*v, _kv.second)
== QPartialOrdering::Less);
#endif // QT6
case LE: case LE:
if (!(v = feature.value(_kv.first))) {if (!(v = feature.value(_kv.first)))
return false; return false;
else else {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return *v <= _kv.second; return *v <= _kv.second;
#else // QT6
QPartialOrdering res = QVariant::compare(*v, _kv.second);
return (res == QPartialOrdering::Less
|| res == QPartialOrdering::Equivalent);
#endif // QT6
}}
case In: case In:
if (!(v = feature.value(_kv.first))) if (!(v = feature.value(_kv.first)))
return _not; return _not;
@ -172,7 +197,7 @@ bool Style::Layer::Filter::match(const PBF::Feature &feature) const
return true; return true;
return false; return false;
case GeometryType: case GeometryType:
return feature.type() == _kv.second.toUInt(); return feature.type() == _kv.second.toInt();
default: default:
return false; return false;
} }
@ -180,15 +205,15 @@ bool Style::Layer::Filter::match(const PBF::Feature &feature) const
QString Style::Layer::Template::value(int zoom, const PBF::Feature &feature) const QString Style::Layer::Template::value(int zoom, const PBF::Feature &feature) const
{ {
QRegExp rx = QRegExp("\\{[^\\}]*\\}"); QRegularExpression rx("\\{[^\\}]*\\}");
QString text(_field.value(zoom)); QString text(_field.value(zoom));
QRegularExpressionMatchIterator it = rx.globalMatch(text);
QStringList keys; QStringList keys;
int pos = 0;
while ((pos = rx.indexIn(text, pos)) != -1) { while (it.hasNext()) {
QString match = rx.capturedTexts().first(); QRegularExpressionMatch match = it.next();
keys.append(match.mid(1, match.size() - 2)); QString val = match.captured(0);
pos += rx.matchedLength(); keys.append(val.mid(1, val.size() - 2));
} }
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);
@ -230,6 +255,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
@ -345,6 +373,12 @@ Style::Layer::Layout::Layout(const QJsonObject &json)
// symbol // symbol
_symbolPlacement = FunctionS(json["symbol-placement"]); _symbolPlacement = FunctionS(json["symbol-placement"]);
// visibility
if (json.contains("visibility") && json["visibility"].isString())
_visible = !(json["visibility"].toString() == "none");
else
_visible = true;
} }
QFont Style::Layer::Layout::font(int zoom) const QFont Style::Layer::Layout::font(int zoom) const
@ -434,7 +468,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());
@ -472,12 +506,8 @@ Style::Layer::Layer(const QJsonObject &json)
bool Style::Layer::match(int zoom, const PBF::Feature &feature) 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(feature); return _filter.match(feature);
} }
@ -511,6 +541,7 @@ 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,
@ -569,22 +600,15 @@ bool Style::load(const QString &fileName)
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;
} }
const Sprites &Style::sprites(const QPointF &scale) const const Sprites &Style::sprites(const QPointF &scale) const
{ {
#ifdef ENABLE_HIDPI
return (scale.x() > 1.0 || scale.y() > 1.0) return (scale.x() > 1.0 || scale.y() > 1.0)
&& !_sprites2x.isNull() ? _sprites2x : _sprites; && !_sprites2x.isNull() ? _sprites2x : _sprites;
#else // ENABLE_HIDPI
Q_UNUSED(scale);
return _sprites;
#endif // ENABLE_HIDPI
} }
void Style::setupLayer(Tile &tile, const Layer &layer) const void Style::setupLayer(Tile &tile, const Layer &layer) const
@ -597,7 +621,8 @@ void Style::setupLayer(Tile &tile, const Layer &layer) const
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);
@ -636,6 +661,9 @@ void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer,
if (pbfLayer.data()->version() > 2) if (pbfLayer.data()->version() > 2)
return; return;
if (!styleLayer.isVisible())
return;
QSizeF factor(tile.size().width() / tile.scale().x() / QSizeF factor(tile.size().width() / tile.scale().x() /
(qreal)pbfLayer.data()->extent(), tile.size().height() / tile.scale().y() (qreal)pbfLayer.data()->extent(), tile.size().height() / tile.scale().y()
/ (qreal)pbfLayer.data()->extent()); / (qreal)pbfLayer.data()->extent());

View File

@ -12,7 +12,6 @@
#include <QBrush> #include <QBrush>
#include <QFont> #include <QFont>
#include "pbf.h" #include "pbf.h"
#include "config.h"
#include "text.h" #include "text.h"
#include "function.h" #include "function.h"
#include "sprites.h" #include "sprites.h"
@ -35,13 +34,14 @@ public:
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;}
bool isPath() const {return (_type == Line || _type == Fill);} bool isPath() const {return (_type == Line || _type == Fill);}
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 isVisible() const {return (_layout.visible());}
bool match(int zoom, const PBF::Feature &feature) 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;
@ -92,7 +92,7 @@ private:
class Layout { class Layout {
public: public:
Layout() : _textSize(16), _textMaxWidth(10), _textMaxAngle(45), Layout() : _textSize(16), _textMaxWidth(10), _textMaxAngle(45),
_font("Open Sans") {} _font("Open Sans"), _visible(true) {}
Layout(const QJsonObject &json); Layout(const QJsonObject &json);
qreal maxTextWidth(int zoom) const qreal maxTextWidth(int zoom) const
@ -109,6 +109,7 @@ private:
Text::Anchor textAnchor(int zoom) const; Text::Anchor textAnchor(int zoom) const;
Text::SymbolPlacement symbolPlacement(int zoom) const; Text::SymbolPlacement symbolPlacement(int zoom) const;
Text::RotationAlignment textRotationAlignment(int zoom) const; Text::RotationAlignment textRotationAlignment(int zoom) const;
bool visible() const {return _visible;}
private: private:
QFont::Capitalization textTransform(int zoom) const; QFont::Capitalization textTransform(int zoom) const;
@ -125,6 +126,7 @@ private:
FunctionS _symbolPlacement; FunctionS _symbolPlacement;
FunctionS _textRotationAlignment; FunctionS _textRotationAlignment;
QFont _font; QFont _font;
bool _visible;
}; };
class Paint { class Paint {
@ -137,9 +139,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;
Text::Halo halo(int zoom) const
{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;
@ -147,6 +153,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;
@ -170,10 +178,7 @@ private:
Tile &tile) const; Tile &tile) const;
QVector<Layer> _layers; QVector<Layer> _layers;
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"
@ -8,8 +9,7 @@
Text::~Text() Text::~Text()
{ {
for (int i = 0; i < _items.size(); i++) qDeleteAll(_items);
delete _items[i];
} }
void Text::render(QPainter *painter) const void Text::render(QPainter *painter) const
@ -31,22 +31,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 +70,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

View File

@ -11,7 +11,7 @@ bool TextItem::collidesWithItem(const TextItem *other) const
return other->shape().intersects(shape()); return other->shape().intersects(shape());
} }
int TextItem::avgCharWidth() const qreal TextItem::avgCharWidth() const
{ {
qreal ratio; qreal ratio;
ushort cp = _text.at(0).unicode(); ushort cp = _text.at(0).unicode();
@ -21,14 +21,18 @@ int TextItem::avgCharWidth() const
ratio = 1.0; ratio = 1.0;
// Greek & Cyrilic // Greek & Cyrilic
else if (cp >= 0x03FF && cp <= 0x04FF) { else if (cp >= 0x03FF && cp <= 0x04FF) {
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.75 : 0.67; ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.80 : 0.70;
if (_font.bold()) if (_font.bold())
ratio *= 1.1; ratio *= 1.1;
if (_font.italic())
ratio *= 0.9;
// The rest (Latin scripts, Arabic, ...) // The rest (Latin scripts, Arabic, ...)
} else { } else {
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.7 : 0.58; ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.75 : 0.60;
if (_font.bold()) if (_font.bold())
ratio *= 1.1; ratio *= 1.1;
if (_font.italic())
ratio *= 0.9;
} }
return ratio * _font.pixelSize(); return ratio * _font.pixelSize();

View File

@ -6,6 +6,7 @@
#include <QPen> #include <QPen>
#include <QFont> #include <QFont>
#include <QRectF> #include <QRectF>
#include "text.h"
class QPainter; class QPainter;
@ -19,7 +20,9 @@ public:
const QString &text() const {return _text;} const QString &text() const {return _text;}
const QFont &font() const {return _font;} const QFont &font() const {return _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;
@ -31,12 +34,13 @@ public:
bool collidesWithItem(const TextItem *other) const; bool collidesWithItem(const TextItem *other) const;
protected: protected:
int avgCharWidth() const; qreal avgCharWidth() const;
private: private:
QString _text; QString _text;
QFont _font; QFont _font;
QPen _pen; QPen _pen;
Text::Halo _halo;
bool _visible; bool _visible;
}; };

View File

@ -3,41 +3,101 @@
#include "textpathitem.h" #include "textpathitem.h"
static QPointF intersection(const QLineF &line, const QRectF &rect) #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
{ #define INTERSECTS intersect
QPointF p; #else // QT 5.15
if (line.intersect(QLineF(rect.topLeft(), rect.topRight()), &p) #define INTERSECTS intersects
== QLineF::BoundedIntersection) #endif // QT 5.15
return p;
if (line.intersect(QLineF(rect.topLeft(), rect.bottomLeft()), &p)
== QLineF::BoundedIntersection)
return p;
if (line.intersect(QLineF(rect.bottomRight(), rect.bottomLeft()), &p)
== QLineF::BoundedIntersection)
return p;
if (line.intersect(QLineF(rect.bottomRight(), rect.topRight()), &p)
== QLineF::BoundedIntersection)
return p;
return rect.center(); static void swap(const QLineF &line, QPointF *p1, QPointF *p2)
{
QPointF lp1(line.p1());
QPointF lp2(line.p2());
if ((lp1.rx() < lp2.rx() && p1->rx() > p2->rx())
|| (lp1.ry() < lp2.ry() && p1->ry() > p2->ry())
|| (lp1.rx() > lp2.rx() && p1->rx() < p2->rx())
|| (lp1.ry() > lp2.ry() && p1->ry() < p2->ry())) {
QPointF tmp(*p2);
*p2 = *p1;
*p1 = tmp;
}
} }
static QPainterPath subpath(const QList<QLineF> &lines, int start, int end, static bool intersection(const QLineF &line, const QRectF &rect, QPointF *p1,
QPointF *p2)
{
QPointF *p = p1;
if (line.INTERSECTS(QLineF(rect.topLeft(), rect.topRight()), p)
== QLineF::BoundedIntersection)
p = p2;
if (line.INTERSECTS(QLineF(rect.topLeft(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection) {
if (p == p2) {
swap(line, p1, p2);
return true;
}
p = p2;
}
if (line.INTERSECTS(QLineF(rect.bottomRight(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection) {
if (p == p2) {
swap(line, p1, p2);
return true;
}
p = p2;
}
if (line.INTERSECTS(QLineF(rect.bottomRight(), rect.topRight()), p)
== QLineF::BoundedIntersection) {
if (p == p2) {
swap(line, p1, p2);
return true;
}
}
Q_ASSERT(p != p2);
return false;
}
static bool intersection(const QLineF &line, const QRectF &rect, QPointF *p)
{
if (line.INTERSECTS(QLineF(rect.topLeft(), rect.topRight()), p)
== QLineF::BoundedIntersection)
return true;
if (line.INTERSECTS(QLineF(rect.topLeft(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection)
return true;
if (line.INTERSECTS(QLineF(rect.bottomRight(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection)
return true;
if (line.INTERSECTS(QLineF(rect.bottomRight(), rect.topRight()), p)
== QLineF::BoundedIntersection)
return true;
return false;
}
static QPainterPath subpath(const QPolygonF &path, int start, int end,
qreal cut) qreal cut)
{ {
qreal ss = 0, es = 0; qreal ss = 0, es = 0;
int si = start, ei = end; int si = start, ei = end;
for (int i = start; i <= end; i++) { for (int i = start; i < end; i++) {
qreal len = lines.at(i).length(); QLineF l(path.at(i), path.at(i+1));
qreal len = l.length();
if (ss + len < cut / 2) { if (ss + len < cut / 2) {
ss += len; ss += len;
si++; si++;
} else } else
break; break;
} }
for (int i = end; i >= start; i--) { for (int i = end; i > start; i--) {
qreal len = lines.at(i).length(); QLineF l(path.at(i), path.at(i-1));
qreal len = l.length();
if (es + len < cut / 2) { if (es + len < cut / 2) {
es += len; es += len;
ei--; ei--;
@ -45,83 +105,117 @@ static QPainterPath subpath(const QList<QLineF> &lines, int start, int end,
break; break;
} }
QLineF sl(lines.at(si).p2(), lines.at(si).p1()); QLineF sl(path.at(si+1), path.at(si));
sl.setLength(sl.length() - (cut / 2 - ss)); sl.setLength(sl.length() - (cut / 2 - ss));
QLineF el(lines.at(ei)); QLineF el(path.at(ei-1), path.at(ei));
el.setLength(el.length() - (cut / 2 - es)); el.setLength(el.length() - (cut / 2 - es));
QPainterPath p(sl.p2()); QPainterPath p(sl.p2());
for (int i = si; i <= ei; i++) for (int i = si + 1; i < ei; i++)
p.lineTo(lines.at(i).p2()); p.lineTo(path.at(i));
p.setElementPositionAt(p.elementCount() - 1, el.p2().x(), el.p2().y()); p.lineTo(el.p2());
return p; return p;
} }
static QList<QLineF> lineString(const QPainterPath &path, static QList<QPolygonF> polyLines(const QPainterPath &path, const QRectF &rect)
const QRectF &boundingRect)
{ {
QList<QLineF> lines; QList<QPolygonF> lines;
int start = 0, end = path.elementCount() - 1; QPolygonF line;
bool lastIn = rect.contains(path.elementAt(0));
for (int i = 0; i < path.elementCount(); i++) { for (int i = 1; i < path.elementCount(); i++) {
if (boundingRect.contains(path.elementAt(i))) { if (rect.contains(path.elementAt(i))) {
start = i; if (lastIn) {
break; if (line.isEmpty())
} line.append(path.elementAt(i-1));
} line.append(path.elementAt(i));
for (int i = path.elementCount() - 1; i >= 0; i--) { } else {
if (boundingRect.contains(path.elementAt(i))) { QPointF p;
end = i; QLineF l(path.elementAt(i-1), path.elementAt(i));
break;
if (intersection(l, rect, &p))
line.append(p);
line.append(path.elementAt(i));
}
lastIn = true;
} else {
QLineF l(path.elementAt(i-1), path.elementAt(i));
if (lastIn) {
QPointF p;
if (line.isEmpty())
line.append(path.elementAt(i-1));
if (intersection(l, rect, &p))
line.append(p);
lines.append(line);
line.clear();
} else {
QPointF p1, p2;
if (intersection(l, rect, &p1, &p2)) {
line.append(p1);
line.append(p2);
lines.append(line);
line.clear();
}
}
lastIn = false;
} }
} }
if (start > 0) { if (!line.isEmpty())
QLineF l(path.elementAt(start-1), path.elementAt(start)); lines.append(line);
QPointF p(intersection(l, boundingRect));
if (p != boundingRect.center())
lines.append(QLineF(p, path.elementAt(start)));
}
for (int i = start + 1; i <= end; i++)
lines.append(QLineF(path.elementAt(i-1), path.elementAt(i)));
if (end < path.elementCount() - 1) {
QLineF l(path.elementAt(end), path.elementAt(end+1));
QPointF p(intersection(l, boundingRect));
if (p != boundingRect.center())
lines.append(QLineF(path.elementAt(end), p));
}
return lines; return lines;
} }
static qreal diff(qreal a1, qreal a2)
{
qreal d = qAbs(a1 - a2);
return (d > 180) ? 360 - d : d;
}
static QPainterPath textPath(const QPainterPath &path, qreal textWidth, static QPainterPath textPath(const QPainterPath &path, qreal textWidth,
qreal maxAngle, qreal charWidth, const QRectF &tileRect) qreal maxAngle, qreal charWidth, const QRectF &tileRect)
{ {
QList<QLineF> lines(lineString(path, tileRect)); if (path.isEmpty())
qreal length = 0; return QPainterPath();
qreal angle = lines.first().angle();
int last = 0; QList<QPolygonF> lines(polyLines(path, tileRect));
for (int i = 0; i < lines.size(); i++) { for (int i = 0; i < lines.size(); i++) {
qreal sl = lines.at(i).length(); const QPolygonF &pl = lines.at(i);
qreal a = lines.at(i).angle(); qreal angle = 0, length = 0;
int last = 0;
if (!tileRect.contains(lines.at(i).p2()) || sl < charWidth for (int j = 1; j < pl.size(); j ++) {
|| qAbs(angle - a) > maxAngle) { QLineF l(pl.at(j-1), pl.at(j));
if (length > textWidth) qreal sl = l.length();
return subpath(lines, last, i - 1, length - textWidth); qreal a = l.angle();
last = i;
length = 0;
} else
length += sl;
angle = a; if (sl < charWidth) {
if (length > textWidth)
return subpath(pl, last, j - 1, length - textWidth);
last = j;
length = 0;
} else if (j > 1 && diff(angle, a) > maxAngle) {
if (length > textWidth)
return subpath(pl, last, j - 1, length - textWidth);
last = j - 1;
length = sl;
} else
length += sl;
angle = a;
}
if (length > textWidth)
return subpath(pl, last, pl.size() - 1, length - textWidth);
} }
return (length > textWidth) return QPainterPath();
? subpath(lines, last, lines.size() - 1, length - textWidth)
: QPainterPath();
} }
static bool reverse(const QPainterPath &path) static bool reverse(const QPainterPath &path)
@ -136,7 +230,7 @@ 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, font) : TextItem(text, font)
{ {
int cw = avgCharWidth(); qreal cw = avgCharWidth();
int textWidth = text.size() * cw; int textWidth = text.size() * cw;
if (textWidth > path.length()) if (textWidth > path.length())
return; return;
@ -156,20 +250,42 @@ TextPathItem::TextPathItem(const QString &text, const QPainterPath &path,
void TextPathItem::paint(QPainter *painter) const void TextPathItem::paint(QPainter *painter) const
{ {
//painter->setPen(Qt::red);
//painter->drawPath(_shape);
QFontMetrics fm(font()); QFontMetrics fm(font());
int textWidth = fm.width(text()); int textWidth = fm.boundingRect(text()).width();
qreal factor = (textWidth) / qMax(_path.length(), (qreal)textWidth); qreal factor = (textWidth) / qMax(_path.length(), (qreal)textWidth);
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.horizontalAdvance(text().at(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);
@ -179,7 +295,12 @@ void TextPathItem::paint(QPainter *painter) const
painter->drawText(QPoint(0, fm.descent()), text().at(i)); painter->drawText(QPoint(0, fm.descent()), text().at(i));
painter->setTransform(t); painter->setTransform(t);
int width = fm.charWidth(text(), i); int width = fm.horizontalAdvance(text().at(i));
percent += ((qreal)width / (qreal)textWidth) * factor; percent += ((qreal)width / (qreal)textWidth) * factor;
} }
//painter->setBrush(Qt::NoBrush);
//painter->setPen(Qt::red);
//painter->setRenderHint(QPainter::Antialiasing, false);
//painter->drawPath(_shape);
} }

View File

@ -1,34 +1,19 @@
#include <QPainter> #include <QPainter>
#include <QtMath> #include <QtMath>
#include "config.h" #include <QStaticText>
#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
{
QFontMetrics fm(font());
int limit = font().pixelSize() * _maxWidth;
// Italic fonts overflow the computed bounding rect, so reduce it
// a little bit.
if (font().italic())
limit -= font().pixelSize() / 2.0;
QRect br = fm.boundingRect(QRect(0, 0, limit, 0), FLAGS, text());
Q_ASSERT(br.isValid());
// 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 QRectF TextPointItem::fuzzyBoundingRect() const
{ {
int limit = font().pixelSize() * _maxWidth; int fs = font().pixelSize();
if (text().size() <= 3)
return QRectF(0, 0, text().size() * fs, fs * 1.6);
int limit = fs * _maxWidth;
qreal cw = avgCharWidth(); qreal cw = avgCharWidth();
qreal lh = font().pixelSize() * 1.25;
int width = 0, lines = 0; int width = 0, lines = 0;
QStringList l(text().split('\n')); QStringList l(text().split('\n'));
@ -44,7 +29,7 @@ QRectF TextPointItem::fuzzyBoundingRect() const
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
@ -61,32 +46,26 @@ QRectF TextPointItem::fuzzyBoundingRect() const
} }
} }
return QRectF(0, 0, width, lines * lh); return QRectF(0, 0, width, lines * fs * 1.6);
} }
QRectF TextPointItem::moveTextRect(const QRectF &rect) 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());
#else // ENABLE_HIDPI QRectF textRect(rect);
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:
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,
@ -106,15 +85,12 @@ TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
: TextItem(text, font), _pos(pos), _icon(icon), _maxWidth(maxWidth), : TextItem(text, font), _pos(pos), _icon(icon), _maxWidth(maxWidth),
_anchor(anchor) _anchor(anchor)
{ {
_boundingRect = computeTextRect(false); _textRect = 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;
} }
@ -122,28 +98,54 @@ 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 = (_anchor != Text::Center)
? computeTextRect(true) : _boundingRect;
#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;
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()));
painter->drawImage(_pos - QPointF(_icon.width()
/ _icon.devicePixelRatioF() / 2, _icon.height()
/ _icon.devicePixelRatioF() / 2), _icon);
} 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

@ -15,14 +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:
QRectF exactBoundingRect() const;
QRectF fuzzyBoundingRect() const; QRectF fuzzyBoundingRect() const;
QRectF computeTextRect(bool exact) const; QRectF moveTextRect(const QRectF &rect) const;
bool hasHalo() const
{return halo().color().isValid() && halo().width() > 0;}
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;

View File

@ -11,7 +11,10 @@ public:
: _zoom(zoom), _size(img->size()), _scale(scale), : _zoom(zoom), _size(img->size()), _scale(scale),
_text(QSize(img->size().width() / scale.x(), _text(QSize(img->size().width() / scale.x(),
img->size().height() / scale.y())), _painter(img) img->size().height() / scale.y())), _painter(img)
{_painter.scale(scale.x(), scale.y());} {
img->fill(Qt::transparent);
_painter.scale(scale.x(), scale.y());
}
int zoom() const {return _zoom;} int zoom() const {return _zoom;}
const QSize &size() const {return _size;} const QSize &size() const {return _size;}

View File

@ -2265,7 +2265,6 @@
"source": "openmaptiles", "source": "openmaptiles",
"source-layer": "building", "source-layer": "building",
"minzoom": 13, "minzoom": 13,
"maxzoom": 14,
"layout": { "layout": {
"visibility": "visible" "visibility": "visible"
}, },
@ -4059,4 +4058,4 @@
} }
], ],
"id": "osm-liberty" "id": "osm-liberty"
} }