Added support for symbol icons

This commit is contained in:
Martin Tůma 2018-11-22 21:57:21 +01:00
parent c48ea8c878
commit 2803f48a21
14 changed files with 2060 additions and 110 deletions

View File

@ -19,7 +19,8 @@ HEADERS += src/pbfhandler.h \
src/textpathitem.h \ src/textpathitem.h \
src/textpointitem.h \ src/textpointitem.h \
src/font.h \ src/font.h \
src/textitem.h src/textitem.h \
src/sprites.h
SOURCES += src/pbfplugin.cpp \ SOURCES += src/pbfplugin.cpp \
src/pbfhandler.cpp \ src/pbfhandler.cpp \
src/gzip.cpp \ src/gzip.cpp \
@ -30,7 +31,8 @@ SOURCES += src/pbfplugin.cpp \
src/function.cpp \ src/function.cpp \
src/textpathitem.cpp \ src/textpathitem.cpp \
src/textpointitem.cpp \ src/textpointitem.cpp \
src/font.cpp src/font.cpp \
src/sprites.cpp
RESOURCES += pbfplugin.qrc RESOURCES += pbfplugin.qrc
unix:!macx{ unix:!macx{

View File

@ -1,5 +1,7 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file>style/style.json</file> <file>style/style.json</file>
<file>style/sprite.json</file>
<file>style/sprite.png</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -116,7 +116,7 @@ static inline QPoint parameters(quint32 v1, quint32 v2)
static void drawFeature(const Feature &feature, Style *style, int styleLayer, static void drawFeature(const Feature &feature, Style *style, int styleLayer,
const QSizeF &factor, Tile &tile) const QSizeF &factor, Tile &tile)
{ {
if (!style->match(styleLayer, feature.tags())) if (!style->match(tile.zoom(), styleLayer, feature.tags()))
return; return;
QPoint cursor; QPoint cursor;
@ -151,7 +151,7 @@ static void drawFeature(const Feature &feature, Style *style, int styleLayer,
} }
} }
style->drawFeature(styleLayer, path, feature.tags(), tile); style->drawFeature(tile, styleLayer, path, feature.tags());
} }
static void drawLayer(const Layer &layer, Style *style, int styleLayer, static void drawLayer(const Layer &layer, Style *style, int styleLayer,
@ -163,7 +163,9 @@ static void drawLayer(const Layer &layer, Style *style, int styleLayer,
QSizeF factor(tile.size().width() / scale.x() / (qreal)layer.data()->extent(), QSizeF factor(tile.size().width() / scale.x() / (qreal)layer.data()->extent(),
tile.size().height() / scale.y() / (qreal)layer.data()->extent()); tile.size().height() / scale.y() / (qreal)layer.data()->extent());
style->setPainter(styleLayer, tile); style->setPainter(tile, styleLayer);
style->setTextProperties(tile, styleLayer);
for (int i = 0; i < layer.features().size(); i++) for (int i = 0; i < layer.features().size(); i++)
drawFeature(layer.features().at(i), style, styleLayer, factor, tile); drawFeature(layer.features().at(i), style, styleLayer, factor, tile);
} }
@ -177,9 +179,8 @@ bool PBF::render(const QByteArray &data, int zoom, Style *style,
return false; return false;
} }
Tile t(image, scale); Tile t(image, zoom, scale);
style->setZoom(zoom);
style->drawBackground(t); style->drawBackground(t);
// Prepare source layers // Prepare source layers

91
src/sprites.cpp Normal file
View File

@ -0,0 +1,91 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QFile>
#include <QDebug>
#include "sprites.h"
/*
Loading the sprites atlas image must be deferred until all image plugins
are loaded, otherwise reading the image will cause a deadlock!
*/
static QImage &atlas(const QString &fileName)
{
static QImage *img = new QImage(fileName);
return *img;
}
Sprites::Sprite::Sprite(const QJsonObject &json)
{
int x, y, width, height;
if (json.contains("x") && json["x"].isDouble())
x = json["x"].toInt();
else
return;
if (json.contains("y") && json["y"].isDouble())
y = json["y"].toInt();
else
return;
if (json.contains("width") && json["width"].isDouble())
width = json["width"].toInt();
else
return;
if (json.contains("height") && json["height"].isDouble())
height = json["height"].toInt();
else
return;
_rect = QRect(x, y, width, height);
}
bool Sprites::load(const QString &jsonFile, const QString &imageFile)
{
_imageFile = imageFile;
QFile file(jsonFile);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCritical() << jsonFile << ": error opening file";
return false;
}
QByteArray ba(file.readAll());
QJsonParseError error;
QJsonDocument doc(QJsonDocument::fromJson(ba, &error));
if (doc.isNull()) {
qCritical() << jsonFile << ":" << error.errorString();
return false;
}
QJsonObject json(doc.object());
for (QJsonObject::const_iterator it = json.constBegin();
it != json.constEnd(); it++) {
QJsonValue val(*it);
if (val.isObject())
_sprites.insert(it.key(), Sprite(val.toObject()));
else
qWarning() << it.key() << ": invalid sprite definition";
}
return true;
}
QImage Sprites::icon(const QString &name) const
{
if (_imageFile.isNull())
return QImage();
const QImage &img = atlas(_imageFile);
if (img.isNull())
return QImage();
QMap<QString, Sprite>::const_iterator it = _sprites.find(name);
if (it == _sprites.constEnd())
return QImage();
if (!img.rect().contains(it->rect())) {
qWarning() << it->rect() << ": invalid sprite rect";
return QImage();
}
return img.copy(it->rect());
}

29
src/sprites.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef SPRITES_H
#define SPRITES_H
#include <QRect>
#include <QMap>
#include <QImage>
class Sprites
{
public:
bool load(const QString &jsonFile, const QString &imageFile);
QImage icon(const QString &name) const;
private:
class Sprite {
public:
Sprite(const QJsonObject &json);
const QRect &rect() const {return _rect;}
private:
QRect _rect;
};
QMap<QString, Sprite> _sprites;
QString _imageFile;
};
#endif // SPRITES_H

View File

@ -3,6 +3,8 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonArray> #include <QJsonArray>
#include <QFileInfo>
#include <QDir>
#include <QDebug> #include <QDebug>
#include "text.h" #include "text.h"
#include "color.h" #include "color.h"
@ -168,6 +170,32 @@ bool Style::Layer::Filter::match(const QVariantHash &tags) const
} }
} }
Style::Layer::Template::Template(const QString &str) : _field(str)
{
int pos = 0;
while ((pos = _rx.indexIn(_field, pos)) != -1) {
QString match = _rx.capturedTexts().first();
_keys.append(match.mid(1, match.size() - 2));
pos += _rx.matchedLength();
}
}
QString Style::Layer::Template::value(const QVariantHash &tags) const
{
QString text(_field);
for (int i = 0; i < _keys.size(); i++) {
const QString &key = _keys.at(i);
const QVariant val = tags.value(key);
text.replace(QString("{%1}").arg(key), val.toString());
}
return text;
}
QRegExp Style::Layer::Template::_rx = QRegExp("\\{[^\\}]*\\}");
Style::Layer::Paint::Paint(const QJsonObject &json) Style::Layer::Paint::Paint(const QJsonObject &json)
: _fillOpacity(1.0), _lineOpacity(1.0), _lineWidth(1.0) : _fillOpacity(1.0), _lineOpacity(1.0), _lineWidth(1.0)
{ {
@ -275,7 +303,7 @@ bool Style::Layer::Paint::antialias(Layer::Type type, int zoom) const
Style::Layer::Layout::Layout(const QJsonObject &json) Style::Layer::Layout::Layout(const QJsonObject &json)
: _textSize(16), _textMaxWidth(10), _textMaxAngle(45), _lineCap(Qt::FlatCap), : _textSize(16), _textMaxWidth(10), _textMaxAngle(45), _lineCap(Qt::FlatCap),
_lineJoin(Qt::MiterJoin), _font("Open Sans"), _capitalize(false), _lineJoin(Qt::MiterJoin), _font("Open Sans"), _capitalize(false),
_viewportAlignment(false) _viewportAlignment(false), _textAnchor(Text::Center)
{ {
// line // line
if (json.contains("line-cap") && json["line-cap"].isString()) { if (json.contains("line-cap") && json["line-cap"].isString()) {
@ -292,17 +320,8 @@ Style::Layer::Layout::Layout(const QJsonObject &json)
} }
// text // text
if (!(json.contains("text-field") && json["text-field"].isString())) if (json.contains("text-field") && json["text-field"].isString())
return; _text = Template(json["text-field"].toString());
_textField = json["text-field"].toString();
QRegExp rx("\\{[^\\}]*\\}");
int pos = 0;
while ((pos = rx.indexIn(_textField, pos)) != -1) {
QString match = rx.capturedTexts().first();
_keys.append(match.mid(1, match.size() - 2));
pos += rx.matchedLength();
}
jsonFloat(json, "text-size", _textSize); jsonFloat(json, "text-size", _textSize);
jsonFloat(json, "text-max-width", _textMaxWidth); jsonFloat(json, "text-max-width", _textMaxWidth);
@ -311,11 +330,27 @@ Style::Layer::Layout::Layout(const QJsonObject &json)
_font = Font::fromJsonArray(json["text-font"].toArray()); _font = Font::fromJsonArray(json["text-font"].toArray());
if (json.contains("text-transform") && json["text-transform"].isString()) if (json.contains("text-transform") && json["text-transform"].isString())
_capitalize = json["text-transform"].toString() == "uppercase"; _capitalize = json["text-transform"].toString() == "uppercase";
if (json.contains("text-rotation-alignment") if (json.contains("text-rotation-alignment")
&& json["text-rotation-alignment"].isString()) && json["text-rotation-alignment"].isString())
if (json["text-rotation-alignment"].toString() == "viewport") if (json["text-rotation-alignment"].toString() == "viewport")
_viewportAlignment = true; _viewportAlignment = true;
if (json.contains("text-anchor") && json["text-anchor"].isString()) {
QString anchor(json["text-anchor"].toString());
if (anchor == "center")
_textAnchor = Text::Center;
else if (anchor == "left")
_textAnchor = Text::Left;
else if (anchor == "right")
_textAnchor = Text::Right;
else if (anchor == "top")
_textAnchor = Text::Top;
else if (anchor == "bottom")
_textAnchor = Text::Bottom;
}
// icon
if (json.contains("icon-image") && json["icon-image"].isString())
_icon = Template(json["icon-image"].toString());
} }
QFont Style::Layer::Layout::font(int zoom) const QFont Style::Layer::Layout::font(int zoom) const
@ -374,58 +409,61 @@ bool Style::Layer::match(int zoom, const QVariantHash &tags) const
return _filter.match(tags); return _filter.match(tags);
} }
void Style::Layer::setPathPainter(int zoom, Tile &tile) const void Style::Layer::setPathPainter(Tile &tile) const
{ {
QPen pen(_paint.pen(_type, zoom)); QPen pen(_paint.pen(_type, tile.zoom()));
QBrush brush(_paint.brush(_type, zoom)); QBrush brush(_paint.brush(_type, tile.zoom()));
pen.setJoinStyle(_layout.lineJoin()); pen.setJoinStyle(_layout.lineJoin());
pen.setCapStyle(_layout.lineCap()); pen.setCapStyle(_layout.lineCap());
QPainter &p = tile.painter(); QPainter &p = tile.painter();
p.setRenderHint(QPainter::Antialiasing, _paint.antialias(_type, zoom)); p.setRenderHint(QPainter::Antialiasing, _paint.antialias(_type, tile.zoom()));
p.setPen(pen); p.setPen(pen);
p.setBrush(brush); p.setBrush(brush);
p.setOpacity(_paint.opacity(_type, zoom)); p.setOpacity(_paint.opacity(_type, tile.zoom()));
} }
void Style::Layer::setSymbolPainter(int zoom, Tile &tile) const void Style::Layer::setSymbolPainter(Tile &tile) const
{ {
QPen pen(_paint.pen(_type, zoom)); QPen pen(_paint.pen(_type, tile.zoom()));
QFont font(_layout.font(zoom)); QFont font(_layout.font(tile.zoom()));
QPainter &p = tile.painter(); QPainter &p = tile.painter();
p.setPen(pen); p.setPen(pen);
p.setFont(font); p.setFont(font);
} }
void Style::Layer::addSymbol(int zoom, const QPainterPath &path, void Style::Layer::setTextProperties(Tile &tile) const
const QVariantHash &tags, Tile &tile) const
{ {
if (_layout.keys().isEmpty()) Text::Properties prop;
return; prop.maxWidth = _layout.maxTextWidth(tile.zoom());
prop.maxAngle = _layout.maxTextAngle(tile.zoom());
prop.anchor = _layout.textAnchor();
QString text(_layout.field()); tile.text().setProperties(prop);
for (int i = 0; i < _layout.keys().size(); i++) { }
const QString &key = _layout.keys().at(i);
const QVariant val = tags.value(key);
text.replace(QString("{%1}").arg(key), _layout.capitalize()
? val.toString().toUpper() : val.toString());
}
void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path,
const QVariantHash &tags, const Sprites &sprites) const
{
QString text = _layout.text().value(tags);
QString tt(text.trimmed()); QString tt(text.trimmed());
if (tt.isEmpty()) if (tt.isEmpty())
return; return;
if (_layout.capitalize())
tt = tt.toUpper();
QString icon = _layout.icon().value(tags);
if (_layout.viewportAlignment()) if (_layout.viewportAlignment())
tile.text().addLabel(tt, path.elementAt(0), tile.painter(), tile.text().addLabel(tt, path.elementAt(0), tile.painter(), false,
_layout.maxTextWidth(zoom), false); sprites.icon(icon));
else if (path.elementCount() == 1 && path.elementAt(0).isMoveTo()) else if (path.elementCount() == 1 && path.elementAt(0).isMoveTo())
tile.text().addLabel(tt, path.elementAt(0), tile.painter(), tile.text().addLabel(tt, path.elementAt(0), tile.painter(), true,
_layout.maxTextWidth(zoom), true); sprites.icon(icon));
else else
tile.text().addLabel(tt, path, tile.painter(), tile.text().addLabel(tt, path, tile.painter());
_layout.maxTextAngle(zoom));
} }
bool Style::load(const QString &fileName) bool Style::load(const QString &fileName)
@ -450,53 +488,69 @@ bool Style::load(const QString &fileName)
QJsonArray layers = json["layers"].toArray(); QJsonArray layers = json["layers"].toArray();
for (int i = 0; i < layers.size(); i++) for (int i = 0; i < layers.size(); i++)
if (layers[i].isObject()) if (layers[i].isObject())
_styles.append(Layer(layers[i].toObject())); _layers.append(Layer(layers[i].toObject()));
} }
for (int i = 0; i < _styles.size(); i++) for (int i = 0; i < _layers.size(); i++)
_sourceLayers.append(_styles.at(i).sourceLayer()); _sourceLayers.append(_layers.at(i).sourceLayer());
QDir styleDir = QFileInfo(fileName).absoluteDir();
QString spritesJSON(styleDir.filePath("sprite.json"));
if (QFileInfo::exists(spritesJSON)) {
QString spritesImg(styleDir.filePath("sprite.png"));
if (QFileInfo::exists(spritesImg))
_sprites.load(spritesJSON, spritesImg);
else
qCritical() << spritesImg << ": no such file";
}
return true; return true;
} }
bool Style::match(int layer, const QVariantHash &tags) bool Style::match(int zoom, int layer, const QVariantHash &tags) const
{ {
return _styles.at(layer).match(_zoom, tags); return _layers.at(layer).match(zoom, tags);
} }
void Style::setPainter(int layer, Tile &tile) void Style::setTextProperties(Tile &tile, int layer) const
{ {
const Layer &sl = _styles.at(layer); const Layer &sl = _layers.at(layer);
sl.setTextProperties(tile);
}
void Style::setPainter(Tile &tile, int layer) const
{
const Layer &sl = _layers.at(layer);
if (sl.isPath()) if (sl.isPath())
sl.setPathPainter(_zoom, tile); sl.setPathPainter(tile);
else if (sl.isSymbol()) else if (sl.isSymbol())
sl.setSymbolPainter(_zoom, tile); sl.setSymbolPainter(tile);
} }
void Style::drawFeature(int layer, const QPainterPath &path, void Style::drawFeature(Tile &tile, int layer, const QPainterPath &path,
const QVariantHash &tags, Tile &tile) const QVariantHash &tags) const
{ {
const Layer &sl = _styles.at(layer); const Layer &sl = _layers.at(layer);
if (sl.isPath()) if (sl.isPath())
tile.painter().drawPath(path); tile.painter().drawPath(path);
else if (sl.isSymbol()) else if (sl.isSymbol())
sl.addSymbol(_zoom, path, tags, tile); sl.addSymbol(tile, path, tags, _sprites);
} }
void Style::drawBackground(Tile &tile) void Style::drawBackground(Tile &tile) const
{ {
QRectF rect(QPointF(0, 0), tile.size()); QRectF rect(QPointF(0, 0), tile.size());
QPainterPath path; QPainterPath path;
path.addRect(rect); path.addRect(rect);
if (_styles.isEmpty()) { if (_layers.isEmpty()) {
tile.painter().setBrush(Qt::lightGray); tile.painter().setBrush(Qt::lightGray);
tile.painter().setPen(Qt::NoPen); tile.painter().setPen(Qt::NoPen);
tile.painter().drawRect(rect); tile.painter().drawRect(rect);
} else if (_styles.first().isBackground()) { } else if (_layers.first().isBackground()) {
_styles.first().setPathPainter(_zoom, tile); _layers.first().setPathPainter(tile);
tile.painter().drawPath(path); tile.painter().drawPath(path);
} }

View File

@ -9,7 +9,9 @@
#include <QPen> #include <QPen>
#include <QBrush> #include <QBrush>
#include <QFont> #include <QFont>
#include "text.h"
#include "function.h" #include "function.h"
#include "sprites.h"
class QPainter; class QPainter;
@ -23,15 +25,15 @@ public:
bool load(const QString &fileName); bool load(const QString &fileName);
void setZoom(int zoom) {_zoom = zoom;}
const QStringList &sourceLayers() const {return _sourceLayers;} const QStringList &sourceLayers() const {return _sourceLayers;}
bool match(int layer, const QVariantHash &tags);
void drawBackground(Tile &tile); bool match(int zoom, int layer, const QVariantHash &tags) const;
void setPainter(int layer, Tile &tile);
void drawFeature(int layer, const QPainterPath &path, void drawBackground(Tile &tile) const;
const QVariantHash &tags, Tile &tile); void setPainter(Tile &tile, int layer) const;
void setTextProperties(Tile &tile, int layer) const;
void drawFeature(Tile &tile, int layer, const QPainterPath &path,
const QVariantHash &tags) const;
private: private:
class Layer { class Layer {
@ -45,10 +47,11 @@ private:
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 QVariantHash &tags) const;
void setPathPainter(int zoom, Tile &tile) const; void setPathPainter(Tile &tile) const;
void setSymbolPainter(int zoom, Tile &tile) const; void setSymbolPainter(Tile &tile) const;
void addSymbol(int zoom, const QPainterPath &path, void setTextProperties(Tile &tile) const;
const QVariantHash &tags, Tile &tile) const; void addSymbol(Tile &tile, const QPainterPath &path,
const QVariantHash &tags, const Sprites &sprites) const;
private: private:
enum Type { enum Type {
@ -80,12 +83,25 @@ private:
QVector<Filter> _filters; QVector<Filter> _filters;
}; };
class Template {
public:
Template() {}
Template(const QString &str);
QString value(const QVariantHash &tags) const;
private:
static QRegExp _rx;
QStringList _keys;
QString _field;
};
class Layout { class Layout {
public: public:
Layout() : _textSize(16), _textMaxWidth(10), _textMaxAngle(45), Layout() : _textSize(16), _textMaxWidth(10), _textMaxAngle(45),
_lineCap(Qt::FlatCap), _lineJoin(Qt::MiterJoin), _lineCap(Qt::FlatCap), _lineJoin(Qt::MiterJoin),
_font("Open Sans"), _capitalize(false), _viewportAlignment(false) _font("Open Sans"), _capitalize(false), _viewportAlignment(false),
{} _textAnchor(Text::Center) {}
Layout(const QJsonObject &json); Layout(const QJsonObject &json);
bool capitalize() const {return _capitalize;} bool capitalize() const {return _capitalize;}
@ -93,16 +109,17 @@ 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);}
const QString &field() const {return _textField;} const Template &text() const {return _text;}
const QStringList &keys() const {return _keys;} const Template &icon() const {return _icon;}
QFont font(int zoom) const; QFont font(int zoom) const;
Qt::PenCapStyle lineCap() const {return _lineCap;} Qt::PenCapStyle lineCap() const {return _lineCap;}
Qt::PenJoinStyle lineJoin() const {return _lineJoin;} Qt::PenJoinStyle lineJoin() const {return _lineJoin;}
bool viewportAlignment() const {return _viewportAlignment;} bool viewportAlignment() const {return _viewportAlignment;}
Text::Anchor textAnchor() const {return _textAnchor;}
private: private:
QStringList _keys; Template _text;
QString _textField; Template _icon;
FunctionF _textSize; FunctionF _textSize;
FunctionF _textMaxWidth; FunctionF _textMaxWidth;
FunctionF _textMaxAngle; FunctionF _textMaxAngle;
@ -111,6 +128,7 @@ private:
QFont _font; QFont _font;
bool _capitalize; bool _capitalize;
bool _viewportAlignment; bool _viewportAlignment;
Text::Anchor _textAnchor;
}; };
class Paint { class Paint {
@ -144,9 +162,9 @@ private:
Paint _paint; Paint _paint;
}; };
int _zoom; QVector<Layer> _layers;
QVector<Layer> _styles;
QStringList _sourceLayers; QStringList _sourceLayers;
Sprites _sprites;
}; };
#endif // STYLE_H #endif // STYLE_H

View File

@ -150,28 +150,27 @@ void Text::render(QPainter *painter) const
} }
void Text::addLabel(const QString &text, const QPointF &pos, void Text::addLabel(const QString &text, const QPointF &pos,
const QPainter &painter, qreal maxTextWidth, bool overlap) const QPainter &painter, bool overlap, const QImage &icon)
{ {
if (text.isEmpty()) if (text.isEmpty())
return; return;
TextPointItem *ti; TextPointItem *ti = new TextPointItem(text, pos, painter.font(),
_properties, icon);
ti = new TextPointItem(text, pos, painter.font(), maxTextWidth);
if (!overlap && !_sceneRect.contains(ti->boundingRect())) { if (!overlap && !_sceneRect.contains(ti->boundingRect())) {
delete ti; delete ti;
return; return;
} }
ti->setPen(painter.pen()); ti->setPen(painter.pen());
addItem(ti); addItem(ti);
QList<TextItem*> ci = collidingItems(ti); QList<TextItem*> ci = collidingItems(ti);
for (int i = 0; i < ci.size(); i++) for (int i = 0; i < ci.size(); i++)
ci[i]->setVisible(false); ci[i]->setVisible(false);
} }
void Text::addLabel(const QString &text, const QPainterPath &path, void Text::addLabel(const QString &text, const QPainterPath &path,
const QPainter &painter, qreal maxAngle) const QPainter &painter)
{ {
if (path.isEmpty()) if (path.isEmpty())
return; return;
@ -182,7 +181,7 @@ void Text::addLabel(const QString &text, const QPainterPath &path,
if (textWidth > path.length()) if (textWidth > path.length())
return; return;
QPainterPath tp(textPath(path, textWidth, maxAngle, QPainterPath tp(textPath(path, textWidth, _properties.maxAngle,
painter.font().pixelSize() / 2, _sceneRect)); painter.font().pixelSize() / 2, _sceneRect));
if (tp.isEmpty()) if (tp.isEmpty())
return; return;

View File

@ -1,20 +1,40 @@
#ifndef TEXT_H #ifndef TEXT_H
#define TEXT_H #define TEXT_H
#include "textitem.h" #include <QImage>
class TextItem;
class Text class Text
{ {
public: public:
enum Anchor {
Center,
Left,
Right,
Top,
Bottom
};
struct Properties {
int maxWidth;
int maxAngle;
Anchor anchor;
};
Text(const QSize &size) : _sceneRect(QRectF(QPointF(0, 0), size)) {} Text(const QSize &size) : _sceneRect(QRectF(QPointF(0, 0), size)) {}
~Text(); ~Text();
void render(QPainter *painter) const; void setProperties(const Properties &prop)
{_properties = prop;}
void addLabel(const QString &text, const QPointF &pos, void addLabel(const QString &text, const QPointF &pos,
const QPainter &painter, qreal maxTextWidth, bool overlap); const QPainter &painter, bool overlap, const QImage &icon);
void addLabel(const QString &text, const QPainterPath &path, void addLabel(const QString &text, const QPainterPath &path,
const QPainter &painter, qreal maxAngle); const QPainter &painter);
void render(QPainter *painter) const;
private: private:
void addItem(TextItem *item) {_items.append(item);} void addItem(TextItem *item) {_items.append(item);}
@ -22,6 +42,7 @@ private:
QRectF _sceneRect; QRectF _sceneRect;
QList<TextItem *> _items; QList<TextItem *> _items;
Properties _properties;
}; };
#endif // TEXT_H #endif // TEXT_H

View File

@ -1,13 +1,11 @@
#include <QPainter> #include <QPainter>
#include <QtMath> #include <QtMath>
#include "textpointitem.h" #include "textpointitem.h"
#define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip) #define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip)
#ifdef USE_EXACT_TEXT_RECT static QRectF exactBoundingRect(const QString &str, const QFont &font,
static QRectF textBoundingRect(const QString &str, const QFont &font,
int maxTextWidth) int maxTextWidth)
{ {
QFontMetrics fm(font); QFontMetrics fm(font);
@ -26,8 +24,7 @@ static QRectF textBoundingRect(const QString &str, const QFont &font,
return br; return br;
} }
#else // USE_EXACT_TEXT_RECT static QRectF fuzzyBoundingRect(const QString &str, const QFont &font,
static QRectF textBoundingRect(const QString &str, const QFont &font,
int maxTextWidth) int maxTextWidth)
{ {
int limit = font.pixelSize() * maxTextWidth; int limit = font.pixelSize() * maxTextWidth;
@ -67,23 +64,68 @@ static QRectF textBoundingRect(const QString &str, const QFont &font,
return QRectF(0, 0, width, lines * lh); return QRectF(0, 0, width, lines * lh);
} }
#endif // USE_EXACT_TEXT_RECT
QRectF TextPointItem::computeTextRect(BoundingRectFunction brf) const
{
QRectF iconRect = _icon.isNull() ? QRectF() : _icon.rect();
QRectF textRect = brf(text(), _font, _properties.maxWidth);
switch (_properties.anchor) {
case Text::Center:
textRect.moveCenter(_pos);
break;
case Text::Left:
textRect.moveTopLeft(_pos - QPointF(-iconRect.width() / 2,
textRect.height() / 2));
break;
case Text::Right:
textRect.moveTopRight(_pos - QPointF(iconRect.width() / 2,
textRect.height() / 2));
break;
case Text::Bottom:
textRect.moveTopLeft(_pos - QPointF(textRect.width() / 2,
iconRect.height() / 2));
break;
case Text::Top:
textRect.moveTopLeft(_pos - QPointF(textRect.width() / 2,
-iconRect.height() / 2));
break;
}
return textRect;
}
TextPointItem::TextPointItem(const QString &text, const QPointF &pos, TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
const QFont &font, int maxTextWidth) : TextItem(text), _font(font) const QFont &font, const Text::Properties &prop, const QImage &icon)
: TextItem(text), _pos(pos), _font(font), _icon(icon), _properties(prop)
{ {
_boundingRect = textBoundingRect(text, font, maxTextWidth); _boundingRect = computeTextRect(fuzzyBoundingRect);
if (!_icon.isNull()) {
QRectF iconRect(_icon.rect());
iconRect.moveCenter(pos);
_boundingRect |= iconRect;
}
_boundingRect.moveCenter(pos);
_shape.addRect(_boundingRect); _shape.addRect(_boundingRect);
} }
void TextPointItem::paint(QPainter *painter) const void TextPointItem::paint(QPainter *painter) const
{ {
painter->setFont(_font);
painter->setPen(_pen);
painter->drawText(_boundingRect, FLAGS, text());
//painter->setPen(Qt::red); //painter->setPen(Qt::red);
//painter->drawRect(_boundingRect); //painter->drawRect(_boundingRect);
QRectF textRect;
if (!_icon.isNull()) {
textRect = computeTextRect(exactBoundingRect);
painter->drawImage(_pos - QPointF(_icon.width() / 2,
_icon.height() / 2), _icon);
} else
textRect = computeTextRect(fuzzyBoundingRect);
painter->setFont(_font);
painter->setPen(_pen);
painter->drawText(textRect, FLAGS, text());
} }

View File

@ -5,12 +5,13 @@
#include <QFont> #include <QFont>
#include <QString> #include <QString>
#include "textitem.h" #include "textitem.h"
#include "text.h"
class TextPointItem : public TextItem class TextPointItem : public TextItem
{ {
public: public:
TextPointItem(const QString &text, const QPointF &pos, const QFont &font, TextPointItem(const QString &text, const QPointF &pos, const QFont &font,
int maxTextWidth); const Text::Properties &prop, const QImage &icon);
QRectF boundingRect() const {return _boundingRect;} QRectF boundingRect() const {return _boundingRect;}
QPainterPath shape() const {return _shape;} QPainterPath shape() const {return _shape;}
@ -19,10 +20,16 @@ public:
void setPen(const QPen &pen) {_pen = pen;} void setPen(const QPen &pen) {_pen = pen;}
private: private:
typedef QRectF (*BoundingRectFunction)(const QString &, const QFont &, int);
QRectF computeTextRect(BoundingRectFunction brf) const;
QPointF _pos;
QPainterPath _shape; QPainterPath _shape;
QRectF _boundingRect; QRectF _boundingRect;
QFont _font; QFont _font;
QPen _pen; QPen _pen;
QImage _icon;
Text::Properties _properties;
}; };
#endif // TEXTPOINTITEM_H #endif // TEXTPOINTITEM_H

View File

@ -7,16 +7,18 @@
class Tile { class Tile {
public: public:
Tile(QImage *img, const QPointF &scale) Tile(QImage *img, int zoom, const QPointF &scale)
: _size(img->size()), _text(QSize(img->size().width() / scale.x(), : _zoom(zoom), _size(img->size()), _text(QSize(img->size().width()
img->size().height() / scale.y())), _painter(img) / scale.x(), img->size().height() / scale.y())), _painter(img)
{_painter.scale(scale.x(), scale.y());} {_painter.scale(scale.x(), scale.y());}
int zoom() const {return _zoom;}
QSize size() const {return _size;} QSize size() const {return _size;}
Text &text() {return _text;} Text &text() {return _text;}
QPainter &painter() {return _painter;} QPainter &painter() {return _painter;}
private: private:
int _zoom;
QSize _size; QSize _size;
Text _text; Text _text;
QPainter _painter; QPainter _painter;

1682
style/sprite.json Normal file

File diff suppressed because it is too large Load Diff

BIN
style/sprite.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB