Improved text layout handling

This commit is contained in:
Martin Tůma 2018-11-28 23:39:33 +01:00
parent 085a90e0e5
commit 8567ef44d1
9 changed files with 117 additions and 63 deletions

View File

@ -278,8 +278,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)
: _lineCap(Qt::FlatCap), _lineJoin(Qt::MiterJoin), _font("Open Sans"), : _lineCap(Qt::FlatCap), _lineJoin(Qt::MiterJoin), _font("Open Sans")
_viewportAlignment(false)
{ {
// line // line
_lineCap = FunctionS(json["line-cap"], "butt"); _lineCap = FunctionS(json["line-cap"], "butt");
@ -292,18 +291,17 @@ Style::Layer::Layout::Layout(const QJsonObject &json)
_textMaxWidth = FunctionF(json["text-max-width"], 10); _textMaxWidth = FunctionF(json["text-max-width"], 10);
_textMaxAngle = FunctionF(json["text-max-angle"], 45); _textMaxAngle = FunctionF(json["text-max-angle"], 45);
_textTransform = FunctionS(json["text-transform"], "none"); _textTransform = FunctionS(json["text-transform"], "none");
_textRotationAlignment = FunctionS(json["text-rotation-alignment"]);
_textAnchor = FunctionS(json["text-anchor"]);
if (json.contains("text-font") && json["text-font"].isArray()) if (json.contains("text-font") && json["text-font"].isArray())
_font = Font::fromJsonArray(json["text-font"].toArray()); _font = Font::fromJsonArray(json["text-font"].toArray());
if (json.contains("text-rotation-alignment")
&& json["text-rotation-alignment"].isString())
if (json["text-rotation-alignment"].toString() == "viewport")
_viewportAlignment = true;
_textAnchor = FunctionS(json["text-anchor"]);
// icon // icon
_icon = Template(FunctionS(json["icon-image"])); _icon = Template(FunctionS(json["icon-image"]));
// symbol
_symbolPlacement = FunctionS(json["symbol-placement"]);
} }
QFont Style::Layer::Layout::font(int zoom) const QFont Style::Layer::Layout::font(int zoom) const
@ -367,6 +365,31 @@ Qt::PenJoinStyle Style::Layer::Layout::lineJoin(int zoom) const
return Qt::MiterJoin; return Qt::MiterJoin;
} }
Text::SymbolPlacement Style::Layer::Layout::symbolPlacement(int zoom) const
{
QString placement(_symbolPlacement.value(zoom));
if (placement == "line")
return Text::Line;
else if (placement == "line-center")
return Text::LineCenter;
else
return Text::Point;
}
Text::RotationAlignment Style::Layer::Layout::textRotationAlignment(int zoom)
const
{
QString alignment(_textRotationAlignment.value(zoom));
if (alignment == "map")
return Text::Map;
else if (alignment == "viewport")
return Text::Viewport;
else
return Text::Auto;
}
Style::Layer::Layer(const QJsonObject &json) Style::Layer::Layer(const QJsonObject &json)
: _type(Unknown), _minZoom(-1), _maxZoom(-1) : _type(Unknown), _minZoom(-1), _maxZoom(-1)
{ {
@ -437,6 +460,8 @@ void Style::Layer::setTextProperties(Tile &tile) const
tile.text().setAnchor(_layout.textAnchor(tile.zoom())); tile.text().setAnchor(_layout.textAnchor(tile.zoom()));
tile.text().setPen(_paint.pen(_type, tile.zoom())); tile.text().setPen(_paint.pen(_type, tile.zoom()));
tile.text().setFont(_layout.font(tile.zoom())); tile.text().setFont(_layout.font(tile.zoom()));
tile.text().setSymbolPlacement(_layout.symbolPlacement(tile.zoom()));
tile.text().setRotationAlignment(_layout.textRotationAlignment(tile.zoom()));
} }
void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path, void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path,
@ -447,13 +472,7 @@ void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path,
return; return;
QString icon = _layout.icon(tile.zoom(), tags); QString icon = _layout.icon(tile.zoom(), tags);
tile.text().addLabel(text, sprites.icon(icon), path);
if (_layout.viewportAlignment())
tile.text().addLabel(text, path.elementAt(0), false, sprites.icon(icon));
else if (path.elementCount() == 1 && path.elementAt(0).isMoveTo())
tile.text().addLabel(text, path.elementAt(0), true, sprites.icon(icon));
else
tile.text().addLabel(text, path);
} }
bool Style::load(const QString &fileName) bool Style::load(const QString &fileName)

View File

@ -94,7 +94,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"), _viewportAlignment(false) {} _font("Open Sans") {}
Layout(const QJsonObject &json); Layout(const QJsonObject &json);
qreal maxTextWidth(int zoom) const qreal maxTextWidth(int zoom) const
@ -109,7 +109,8 @@ private:
Qt::PenCapStyle lineCap(int zoom) const; Qt::PenCapStyle lineCap(int zoom) const;
Qt::PenJoinStyle lineJoin(int zoom) const; Qt::PenJoinStyle lineJoin(int zoom) const;
Text::Anchor textAnchor(int zoom) const; Text::Anchor textAnchor(int zoom) const;
bool viewportAlignment() const {return _viewportAlignment;} Text::SymbolPlacement symbolPlacement(int zoom) const;
Text::RotationAlignment textRotationAlignment(int zoom) const;
private: private:
QFont::Capitalization textTransform(int zoom) const; QFont::Capitalization textTransform(int zoom) const;
@ -123,8 +124,9 @@ private:
FunctionS _lineJoin; FunctionS _lineJoin;
FunctionS _textAnchor; FunctionS _textAnchor;
FunctionS _textTransform; FunctionS _textTransform;
FunctionS _symbolPlacement;
FunctionS _textRotationAlignment;
QFont _font; QFont _font;
bool _viewportAlignment;
}; };
class Paint { class Paint {

View File

@ -1,4 +1,4 @@
#include <QFontMetrics> #include <QFontMetrics>
#include <QPainter> #include <QPainter>
#include "text.h" #include "text.h"
#include "textpointitem.h" #include "textpointitem.h"
@ -20,15 +20,36 @@ void Text::render(QPainter *painter) const
} }
} }
void Text::addLabel(const QString &text, const QPointF &pos, bool overlap, void Text::addLabel(const QString &text, const QImage &icon,
const QImage &icon) const QPainterPath &path)
{ {
TextPointItem *ti = new TextPointItem(text, pos, _font, _maxWidth, _anchor, TextItem *ti;
icon);
if (!overlap && !_sceneRect.contains(ti->boundingRect())) { switch (_placement) {
delete ti; case Line:
return; if (_alignment == Viewport)
ti = new TextPointItem(text, path.elementAt(0), _font,
_maxWidth, _anchor, icon);
else
ti = new TextPathItem(text, path, _font, _maxAngle, _sceneRect);
if (!_sceneRect.contains(ti->boundingRect()))
ti->setVisible(false);
break;
case LineCenter:
ti = new TextPointItem(text, path.pointAtPercent(0.5), _font,
_maxWidth, _anchor, icon);
if (!_sceneRect.contains(ti->boundingRect()))
ti->setVisible(false);
break;
default:
ti = new TextPointItem(text, path.elementAt(0), _font, _maxWidth,
_anchor, icon);
if (_alignment == Viewport
&& !_sceneRect.contains(ti->boundingRect()))
ti->setVisible(false);
break;
} }
ti->setPen(_pen); ti->setPen(_pen);
addItem(ti); addItem(ti);
@ -37,23 +58,6 @@ void Text::addLabel(const QString &text, const QPointF &pos, bool overlap,
ci[i]->setVisible(false); ci[i]->setVisible(false);
} }
void Text::addLabel(const QString &text, const QPainterPath &path)
{
TextPathItem *ti = new TextPathItem(text, path, _font, _maxAngle,
_sceneRect);
if (!_sceneRect.contains(ti->boundingRect())) {
delete ti;
return;
}
ti->setPen(_pen);
addItem(ti);
QList<TextItem*> ci = collidingItems(ti);
for (int i = 0; i < ci.size(); i++)
ci[i]->setVisible(false);
}
QList<TextItem*> Text::collidingItems(const TextItem *item) const QList<TextItem*> Text::collidingItems(const TextItem *item) const
{ {
QList<TextItem*> list; QList<TextItem*> list;
@ -69,3 +73,16 @@ QList<TextItem*> Text::collidingItems(const TextItem *item) const
return list; return list;
} }
void Text::setSymbolPlacement(SymbolPlacement placement)
{
_placement = placement;
if (_placement != Text::Point) {
for (int i = 0; i < _items.size(); i++) {
TextItem *ti = _items[i];
if (!_sceneRect.contains(ti->boundingRect()))
ti->setVisible(false);
}
}
}

View File

@ -22,6 +22,18 @@ public:
Bottom Bottom
}; };
enum SymbolPlacement {
Point,
Line,
LineCenter
};
enum RotationAlignment {
Map,
Viewport,
Auto
};
Text(const QSize &size) Text(const QSize &size)
: _sceneRect(QRectF(QPointF(0, 0), size)) {} : _sceneRect(QRectF(QPointF(0, 0), size)) {}
~Text(); ~Text();
@ -31,10 +43,12 @@ public:
void setAnchor(Anchor anchor) {_anchor = anchor;} void setAnchor(Anchor anchor) {_anchor = anchor;}
void setMaxWidth(int width) {_maxWidth = width;} void setMaxWidth(int width) {_maxWidth = width;}
void setMaxAngle(int angle) {_maxAngle = angle;} void setMaxAngle(int angle) {_maxAngle = angle;}
void setSymbolPlacement(SymbolPlacement placement);
void setRotationAlignment(RotationAlignment alignment)
{_alignment = alignment;}
void addLabel(const QString &text, const QPointF &pos, bool overlap, void addLabel(const QString &text, const QImage &icon,
const QImage &icon); const QPainterPath &path);
void addLabel(const QString &text, const QPainterPath &path);
void render(QPainter *painter) const; void render(QPainter *painter) const;
@ -48,6 +62,8 @@ private:
int _maxWidth; int _maxWidth;
int _maxAngle; int _maxAngle;
Anchor _anchor; Anchor _anchor;
SymbolPlacement _placement;
RotationAlignment _alignment;
QFont _font; QFont _font;
QPen _pen; QPen _pen;
}; };

View File

@ -11,6 +11,10 @@ public:
virtual ~TextItem() {} virtual ~TextItem() {}
const QString &text() const {return _text;} const QString &text() const {return _text;}
const QFont &font() const {return _font;}
void setFont(const QFont &font) {_font = font;}
const QPen &pen() const {return _pen;}
void setPen(const QPen &pen) {_pen = pen;}
virtual QPainterPath shape() const = 0; virtual QPainterPath shape() const = 0;
virtual QRectF boundingRect() const = 0; virtual QRectF boundingRect() const = 0;
@ -49,6 +53,8 @@ protected:
private: private:
QString _text; QString _text;
QFont _font;
QPen _pen;
bool _visible; bool _visible;
}; };

View File

@ -134,7 +134,7 @@ static bool reverse(const QPainterPath &path)
TextPathItem::TextPathItem(const QString &text, const QPainterPath &path, TextPathItem::TextPathItem(const QString &text, const QPainterPath &path,
const QFont &font, int maxAngle, const QRectF &tileRect) const QFont &font, int maxAngle, const QRectF &tileRect)
: TextItem(text), _font(font) : TextItem(text)
{ {
int cw = avgCharWidth(text, font); int cw = avgCharWidth(text, font);
int textWidth = text.size() * cw; int textWidth = text.size() * cw;
@ -146,6 +146,7 @@ TextPathItem::TextPathItem(const QString &text, const QPainterPath &path,
return; return;
_path = reverse(tp) ? tp.toReversed() : tp; _path = reverse(tp) ? tp.toReversed() : tp;
setFont(font);
QPainterPathStroker s; QPainterPathStroker s;
s.setWidth(font.pixelSize()); s.setWidth(font.pixelSize());
@ -159,14 +160,14 @@ void TextPathItem::paint(QPainter *painter) const
//painter->setPen(Qt::red); //painter->setPen(Qt::red);
//painter->drawPath(_shape); //painter->drawPath(_shape);
QFontMetrics fm(_font); QFontMetrics fm(font());
int textWidth = fm.width(text()); int textWidth = fm.width(text());
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); painter->setPen(pen());
QTransform t = painter->transform(); QTransform t = painter->transform();

View File

@ -16,14 +16,10 @@ public:
QRectF boundingRect() const {return _boundingRect;} QRectF boundingRect() const {return _boundingRect;}
void paint(QPainter *painter) const; void paint(QPainter *painter) const;
void setPen(const QPen &pen) {_pen = pen;}
private: private:
QPainterPath _path; QPainterPath _path;
QPainterPath _shape; QPainterPath _shape;
QRectF _boundingRect; QRectF _boundingRect;
QFont _font;
QPen _pen;
}; };
#endif // TEXTPATHITEM_H #endif // TEXTPATHITEM_H

View File

@ -72,7 +72,7 @@ QRectF TextPointItem::fuzzyBoundingRect(const QString &str,
QRectF TextPointItem::computeTextRect(BoundingRectFunction brf) const QRectF TextPointItem::computeTextRect(BoundingRectFunction brf) const
{ {
QRectF iconRect = _icon.isNull() ? QRectF() : _icon.rect(); QRectF iconRect = _icon.isNull() ? QRectF() : _icon.rect();
QRectF textRect = brf(text(), _font, _maxWidth); QRectF textRect = brf(text(), font(), _maxWidth);
switch (_anchor) { switch (_anchor) {
case Text::Center: case Text::Center:
@ -101,9 +101,10 @@ QRectF TextPointItem::computeTextRect(BoundingRectFunction brf) const
TextPointItem::TextPointItem(const QString &text, const QPointF &pos, TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
const QFont &font, int maxWidth, Text::Anchor anchor, const QImage &icon) const QFont &font, int maxWidth, Text::Anchor anchor, const QImage &icon)
: TextItem(text), _pos(pos), _font(font), _icon(icon), _maxWidth(maxWidth), : TextItem(text), _pos(pos), _icon(icon), _maxWidth(maxWidth),
_anchor(anchor) _anchor(anchor)
{ {
setFont(font);
_boundingRect = computeTextRect(fuzzyBoundingRect); _boundingRect = computeTextRect(fuzzyBoundingRect);
if (!_icon.isNull()) { if (!_icon.isNull()) {
@ -129,7 +130,7 @@ void TextPointItem::paint(QPainter *painter) const
} else } else
textRect = computeTextRect(fuzzyBoundingRect); textRect = computeTextRect(fuzzyBoundingRect);
painter->setFont(_font); painter->setFont(font());
painter->setPen(_pen); painter->setPen(pen());
painter->drawText(textRect, FLAGS, text()); painter->drawText(textRect, FLAGS, text());
} }

View File

@ -17,8 +17,6 @@ public:
QPainterPath shape() const {return _shape;} QPainterPath shape() const {return _shape;}
void paint(QPainter *painter) const; void paint(QPainter *painter) const;
void setPen(const QPen &pen) {_pen = pen;}
private: private:
typedef QRectF (*BoundingRectFunction)(const QString &, const QFont &, int); typedef QRectF (*BoundingRectFunction)(const QString &, const QFont &, int);
@ -32,8 +30,6 @@ private:
QPointF _pos; QPointF _pos;
QPainterPath _shape; QPainterPath _shape;
QRectF _boundingRect; QRectF _boundingRect;
QFont _font;
QPen _pen;
QImage _icon; QImage _icon;
int _maxWidth; int _maxWidth;
Text::Anchor _anchor; Text::Anchor _anchor;