forked from organicmaps/organicmaps
implemented beautiful kerning for drawPathText
This commit is contained in:
parent
2ddfc0a452
commit
60bc21b607
4 changed files with 208 additions and 49 deletions
|
@ -4,7 +4,11 @@
|
|||
#include "resource_manager.hpp"
|
||||
#include "font_desc.hpp"
|
||||
|
||||
#include "../base/logging.hpp"
|
||||
#include "../base/math.hpp"
|
||||
|
||||
#include "../geometry/angles.hpp"
|
||||
#include "../geometry/aa_rect2d.hpp"
|
||||
|
||||
namespace yg
|
||||
{
|
||||
|
@ -82,32 +86,31 @@ namespace yg
|
|||
for (size_t i = res.m_i; i < size() - 1; ++i)
|
||||
{
|
||||
double l = res.m_pt.Length(get(i + 1));
|
||||
res.m_pt = res.m_pt.Move(min(l, offset), ang::AngleTo(get(i), get(i + 1)));
|
||||
res.m_i = i;
|
||||
|
||||
if (offset < l)
|
||||
{
|
||||
res.m_pt = res.m_pt.Move(offset, ang::AngleTo(get(i), get(i + 1)));
|
||||
res.m_i = i;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset -= l;
|
||||
res.m_pt = get(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
PivotPoint findPivotPoint(PathPoint const & pp, GlyphMetrics const & sym)
|
||||
PivotPoint findPivotPoint(PathPoint const & pp, GlyphMetrics const & sym, double kern)
|
||||
{
|
||||
PathPoint startPt = offsetPoint(pp, kern);
|
||||
|
||||
PivotPoint res;
|
||||
res.m_pp.m_i = -1;
|
||||
m2::PointD pt1 = pp.m_pt;
|
||||
m2::PointD pt1 = startPt.m_pt;
|
||||
|
||||
double angle = 0;
|
||||
double advance = sym.m_xOffset + sym.m_width / 2.0;
|
||||
|
||||
int j = pp.m_i;
|
||||
int j = startPt.m_i;
|
||||
|
||||
while (advance > 0)
|
||||
{
|
||||
|
@ -130,8 +133,7 @@ namespace yg
|
|||
res.m_pp.m_pt = pt1.Move(advance, ang::AngleTo(get(j), get(j + 1)));
|
||||
advance = 0;
|
||||
|
||||
angle /= (res.m_pp.m_i - pp.m_i + 1);
|
||||
res.m_pp.m_pt = pt1;
|
||||
angle /= (res.m_pp.m_i - startPt.m_i + 1);
|
||||
res.m_angle = angle;
|
||||
|
||||
break;
|
||||
|
@ -142,6 +144,47 @@ namespace yg
|
|||
}
|
||||
};
|
||||
|
||||
double GlyphLayout::getKerning(GlyphLayoutElem const & prevElem, GlyphLayoutElem const & curElem)
|
||||
{
|
||||
double res = 0;
|
||||
/// check, whether we should offset this symbol slightly
|
||||
/// not to overlap the previous symbol
|
||||
|
||||
m2::AARectD prevSymRectAA(
|
||||
prevElem.m_pt.Move(prevElem.m_metrics.m_height, prevElem.m_angle - math::pi / 2),
|
||||
prevElem.m_angle,
|
||||
m2::RectD(prevElem.m_metrics.m_xOffset,
|
||||
prevElem.m_metrics.m_yOffset,
|
||||
prevElem.m_metrics.m_xOffset + prevElem.m_metrics.m_width,
|
||||
prevElem.m_metrics.m_yOffset + prevElem.m_metrics.m_height));
|
||||
|
||||
m2::AARectD curSymRectAA(
|
||||
curElem.m_pt.Move(curElem.m_metrics.m_height, curElem.m_angle - math::pi / 2),
|
||||
curElem.m_angle,
|
||||
m2::RectD(curElem.m_metrics.m_xOffset,
|
||||
curElem.m_metrics.m_yOffset,
|
||||
curElem.m_metrics.m_xOffset + curElem.m_metrics.m_width,
|
||||
curElem.m_metrics.m_yOffset + curElem.m_metrics.m_height)
|
||||
);
|
||||
|
||||
m2::RectD prevLocalRect = prevSymRectAA.GetLocalRect();
|
||||
m2::PointD pts[4];
|
||||
prevSymRectAA.GetGlobalPoints(pts);
|
||||
curSymRectAA.ConvertTo(pts, 4);
|
||||
|
||||
m2::RectD prevRectInCurSym(pts[0].x, pts[0].y, pts[0].x, pts[0].y);
|
||||
prevRectInCurSym.Add(pts[1]);
|
||||
prevRectInCurSym.Add(pts[2]);
|
||||
prevRectInCurSym.Add(pts[3]);
|
||||
|
||||
m2::RectD curSymRect = curSymRectAA.GetLocalRect();
|
||||
|
||||
if (curSymRect.minX() < prevRectInCurSym.maxX())
|
||||
res = prevRectInCurSym.maxX() - curSymRect.minX();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
GlyphLayout::GlyphLayout(shared_ptr<ResourceManager> const & resourceManager,
|
||||
FontDesc const & fontDesc,
|
||||
m2::PointD const * pts,
|
||||
|
@ -168,10 +211,27 @@ namespace yg
|
|||
strLength += m_entries[i].m_metrics.m_xAdvance;
|
||||
}
|
||||
|
||||
if (fullLength < strLength)
|
||||
return;
|
||||
|
||||
// offset of the text from path's start
|
||||
double offset = (fullLength - strLength) / 2.0;
|
||||
if (offset < 0.0)
|
||||
return;
|
||||
|
||||
if (pos & yg::EPosLeft)
|
||||
offset = 0;
|
||||
if (pos & yg::EPosRight)
|
||||
offset = (fullLength - strLength);
|
||||
|
||||
// calculate base line offset
|
||||
double blOffset = 2 - fontDesc.m_size / 2;
|
||||
// on-path kerning should be done for baseline-centered glyphs
|
||||
double kernOffset = blOffset;
|
||||
|
||||
if (pos & yg::EPosUnder)
|
||||
blOffset = 2 - fontDesc.m_size;
|
||||
if (pos & yg::EPosAbove)
|
||||
blOffset = 2;
|
||||
|
||||
offset -= pathOffset;
|
||||
if (-offset >= strLength)
|
||||
return;
|
||||
|
@ -181,31 +241,70 @@ namespace yg
|
|||
while (offset < 0 && symPos < count)
|
||||
offset += m_entries[symPos++].m_metrics.m_xAdvance;
|
||||
|
||||
PathPoint startPt = arrPath.offsetPoint(PathPoint(0, arrPath.get(0)), offset);
|
||||
PathPoint glyphStartPt = arrPath.offsetPoint(PathPoint(0, arrPath.get(0)), offset);
|
||||
|
||||
m_firstVisible = symPos;
|
||||
|
||||
GlyphLayoutElem prevElem;
|
||||
bool hasPrevElem = false;
|
||||
|
||||
for (; symPos < count; ++symPos)
|
||||
{
|
||||
/// full advance, including possible kerning.
|
||||
double fullGlyphAdvance = m_entries[symPos].m_metrics.m_xAdvance;
|
||||
|
||||
GlyphMetrics const & metrics = m_entries[symPos].m_metrics;
|
||||
|
||||
if (startPt.m_i == -1)
|
||||
if (glyphStartPt.m_i == -1)
|
||||
return;
|
||||
|
||||
if (metrics.m_width != 0)
|
||||
{
|
||||
PivotPoint pivotPt = arrPath.findPivotPoint(startPt, metrics);
|
||||
double fullKern = 0;
|
||||
double kern = 0;
|
||||
|
||||
if (pivotPt.m_pp.m_i == -1)
|
||||
return;
|
||||
int i = 0;
|
||||
for (; i < 100; ++i)
|
||||
{
|
||||
PivotPoint pivotPt = arrPath.findPivotPoint(glyphStartPt, metrics, fullKern);
|
||||
|
||||
m_entries[symPos].m_angle = pivotPt.m_angle;
|
||||
//double centerOffset = metrics.m_xOffset + metrics.m_width / 2.0;
|
||||
//m_entries[symPos].m_pt = pivotPt.m_pp.m_pt.Move(-centerOffset, pivotPt.m_angle);
|
||||
m_entries[symPos].m_pt = startPt.m_pt;
|
||||
if (pivotPt.m_pp.m_i == -1)
|
||||
return;
|
||||
|
||||
m_entries[symPos].m_angle = pivotPt.m_angle;
|
||||
double centerOffset = metrics.m_xOffset + metrics.m_width / 2.0;
|
||||
m_entries[symPos].m_pt = pivotPt.m_pp.m_pt.Move(-centerOffset, m_entries[symPos].m_angle);
|
||||
m_entries[symPos].m_pt = m_entries[symPos].m_pt.Move(blOffset, m_entries[symPos].m_angle - math::pi / 2);
|
||||
// m_entries[symPos].m_pt = m_entries[symPos].m_pt.Move(kernOffset, m_entries[symPos].m_angle - math::pi / 2);
|
||||
|
||||
// < check whether we should "kern"
|
||||
if (hasPrevElem)
|
||||
{
|
||||
kern = getKerning(prevElem, m_entries[symPos]);
|
||||
if (kern < 0.5)
|
||||
kern = 0;
|
||||
|
||||
fullGlyphAdvance += kern;
|
||||
fullKern += kern;
|
||||
}
|
||||
|
||||
if (kern == 0)
|
||||
break;
|
||||
}
|
||||
if (i == 100)
|
||||
LOG(LINFO, ("100 iteration on computing kerning exceeded. possibly infinite loop occured"));
|
||||
|
||||
|
||||
/// kerning should be computed for baseline centered glyph
|
||||
prevElem = m_entries[symPos];
|
||||
hasPrevElem = true;
|
||||
|
||||
// < align to baseline
|
||||
// m_entries[symPos].m_pt = m_entries[symPos].m_pt.Move(blOffset - kernOffset, m_entries[symPos].m_angle - math::pi / 2);
|
||||
}
|
||||
|
||||
startPt = arrPath.offsetPoint(startPt, metrics.m_xAdvance);
|
||||
glyphStartPt = arrPath.offsetPoint(glyphStartPt, fullGlyphAdvance);
|
||||
offset += fullGlyphAdvance;
|
||||
|
||||
m_lastVisible = symPos + 1;
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ namespace yg
|
|||
|
||||
vector<GlyphLayoutElem> m_entries;
|
||||
|
||||
double getKerning(GlyphLayoutElem const & prevElem, GlyphLayoutElem const & curElem);
|
||||
|
||||
public:
|
||||
|
||||
GlyphLayout(GlyphLayout const & layout, double shift);
|
||||
|
|
|
@ -355,42 +355,50 @@ namespace yg
|
|||
|
||||
GlyphLayout layout(resourceManager(), fontDesc, path, s, text, fullLength, pathOffset, pos);
|
||||
|
||||
// calculate base line offset
|
||||
float blOffset = 2;
|
||||
switch (pos)
|
||||
{
|
||||
case yg::EPosUnder: blOffset -= fontDesc.m_size; break;
|
||||
case yg::EPosCenter: blOffset -= fontDesc.m_size / 2; break;
|
||||
case yg::EPosAbove: blOffset -= 0; break;
|
||||
default: blOffset -= fontDesc.m_size / 2; break;
|
||||
}
|
||||
|
||||
vector<GlyphLayoutElem> const & glyphs = layout.entries();
|
||||
|
||||
if (layout.lastVisible() != text.size())
|
||||
return false;
|
||||
|
||||
/* for (size_t i = layout.firstVisible(); i < layout.lastVisible(); ++i)
|
||||
{
|
||||
uint32_t const colorID = skin()->mapColor(yg::Color(fontDesc.m_isMasked ? 255 : 0, 0, fontDesc.m_isMasked ? 0 : 255, 255));
|
||||
ResourceStyle const * colorStyle = skin()->fromID(colorID);
|
||||
|
||||
float x0 = glyphs[i].m_metrics.m_xOffset;
|
||||
float y1 = -glyphs[i].m_metrics.m_yOffset;
|
||||
float y0 = y1 - glyphs[i].m_metrics.m_height;
|
||||
float x1 = x0 + glyphs[i].m_metrics.m_width;
|
||||
|
||||
drawTexturedPolygon(glyphs[i].m_pt, glyphs[i].m_angle,
|
||||
colorStyle->m_texRect.minX() + 1,
|
||||
colorStyle->m_texRect.minY() + 1,
|
||||
colorStyle->m_texRect.maxX() - 1,
|
||||
colorStyle->m_texRect.maxY() - 1,
|
||||
x0, y0, x1, y1,
|
||||
depth - 1,
|
||||
colorStyle->m_pageID);
|
||||
|
||||
}
|
||||
*/
|
||||
for (size_t i = layout.firstVisible(); i < layout.lastVisible(); ++i)
|
||||
{
|
||||
uint32_t const glyphID = skin()->mapGlyph(GlyphKey(text[i], fontDesc.m_size, fontDesc.m_isMasked, fontDesc.m_isMasked ? fontDesc.m_maskColor : fontDesc.m_color), fontDesc.m_isStatic);
|
||||
CharStyle const * charStyle = static_cast<CharStyle const *>(skin()->fromID(glyphID));
|
||||
|
||||
drawGlyph(glyphs[i].m_pt, m2::PointD(0.0, 0.0), glyphs[i].m_angle, blOffset, charStyle, depth);
|
||||
drawGlyph(glyphs[i].m_pt, m2::PointD(0.0, 0.0), glyphs[i].m_angle, 0, charStyle, depth);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TextRenderer::drawGlyph(m2::PointD const & ptOrg, m2::PointD const & ptGlyph, float angle, float blOffset, CharStyle const * p, double depth)
|
||||
void TextRenderer::drawGlyph(m2::PointD const & ptOrg, m2::PointD const & ptGlyph, float angle, float /*blOffset*/, CharStyle const * p, double depth)
|
||||
{
|
||||
float x0 = ptGlyph.x + (p->m_xOffset - 1);
|
||||
float y1 = ptGlyph.y - (p->m_yOffset - 1) - blOffset;
|
||||
float y1 = ptGlyph.y - (p->m_yOffset - 1);
|
||||
float y0 = y1 - (p->m_texRect.SizeY() - 2);
|
||||
float x1 = x0 + (p->m_texRect.SizeX() - 2);
|
||||
|
||||
|
||||
drawTexturedPolygon(ptOrg, angle,
|
||||
p->m_texRect.minX() + 1,
|
||||
p->m_texRect.minY() + 1,
|
||||
|
|
|
@ -738,10 +738,10 @@ namespace
|
|||
void Init()
|
||||
{
|
||||
m_path.push_back(m2::PointD(40, 200));
|
||||
m_path.push_back(m2::PointD(90, 200));
|
||||
m_path.push_back(m2::PointD(190, 230));
|
||||
m_path.push_back(m2::PointD(80, 200));
|
||||
m_path.push_back(m2::PointD(180, 250));
|
||||
|
||||
m_text = "Sim";
|
||||
m_text = "Syp";
|
||||
|
||||
double pat[2] = {2, 2};
|
||||
m_penInfo = yg::PenInfo(yg::Color(0xFF, 0xFF, 0xFF, 0xFF), 2, &pat[0], ARRAY_SIZE(pat), 0);
|
||||
|
@ -750,9 +750,57 @@ namespace
|
|||
void DoDraw(shared_ptr<yg::gl::Screen> p)
|
||||
{
|
||||
p->drawPath(&m_path[0], m_path.size(), 0, p->skin()->mapPenInfo(m_penInfo), 1);
|
||||
yg::FontDesc fontDesc(false, 40);
|
||||
yg::FontDesc fontDesc(false, 30);
|
||||
|
||||
p->drawPathText(fontDesc, &m_path[0], m_path.size(), m_text, calc_length(m_path), 0.0, yg::EPosCenter, 0);
|
||||
p->drawPathText(fontDesc, &m_path[0], m_path.size(), m_text, calc_length(m_path), 0.0, yg::EPosLeft, 0);
|
||||
}
|
||||
};
|
||||
|
||||
struct TestDrawTextOnPathInteractive
|
||||
{
|
||||
double m_pathOffset;
|
||||
vector<m2::PointD> m_testPoints;
|
||||
string m_text;
|
||||
|
||||
bool OnKeyPress(QKeyEvent * kev)
|
||||
{
|
||||
if (kev->key() == Qt::Key_Left)
|
||||
{
|
||||
m_pathOffset += 1;
|
||||
return true;
|
||||
}
|
||||
if (kev->key() == Qt::Key_Right)
|
||||
{
|
||||
m_pathOffset -= 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Init()
|
||||
{
|
||||
m_pathOffset = -102;
|
||||
//m_pathOffset = 0;
|
||||
m_testPoints.push_back(m2::PointD(40, 200));
|
||||
m_testPoints.push_back(m2::PointD(100, 100));
|
||||
m_testPoints.push_back(m2::PointD(160, 200));
|
||||
m_testPoints.push_back(m2::PointD(200, 100));
|
||||
m_testPoints.push_back(m2::PointD(240, 200));
|
||||
m_testPoints.push_back(m2::PointD(280, 100));
|
||||
m_testPoints.push_back(m2::PointD(320, 200));
|
||||
m_testPoints.push_back(m2::PointD(360, 100));
|
||||
m_testPoints.push_back(m2::PointD(400, 200));
|
||||
|
||||
}
|
||||
|
||||
void DoDraw(shared_ptr<yg::gl::Screen> p)
|
||||
{
|
||||
p->drawPath(&m_testPoints[0], m_testPoints.size(), 0, p->skin()->mapPenInfo(yg::PenInfo(yg::Color(255, 255, 255, 255), 2, 0, 0, 0)), 0);
|
||||
yg::FontDesc fontDesc(false, 20, yg::Color(0, 0, 0, 255), false);
|
||||
//m_text = "Simplicity is the ultimate sophistication. Leonardo Da Vinci.";
|
||||
m_text = "Vinci";
|
||||
p->drawPathText(fontDesc, &m_testPoints[0], m_testPoints.size(), m_text.c_str(), calc_length(m_testPoints), m_pathOffset, yg::EPosLeft, 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -764,6 +812,7 @@ namespace
|
|||
|
||||
TestDrawTextOnPath()
|
||||
{
|
||||
|
||||
m_path.push_back(m2::PointD(40, 200));
|
||||
m_path.push_back(m2::PointD(100, 100));
|
||||
m_path.push_back(m2::PointD(600, 100));
|
||||
|
@ -1116,10 +1165,11 @@ namespace
|
|||
// UNIT_TEST_GL(TestDrawUnicodeSymbols);
|
||||
// UNIT_TEST_GL(TestDrawTextRectWithFixedFont);
|
||||
// UNIT_TEST_GL(TestDrawStringOnString);
|
||||
// UNIT_TEST_GL(TestDrawTextOnPathBigSymbols);
|
||||
UNIT_TEST_GL(TestDrawTextOnPath);
|
||||
UNIT_TEST_GL(TestDrawTextOnPathZigZag);
|
||||
UNIT_TEST_GL(TestDrawTextOnPathWithOffset);
|
||||
UNIT_TEST_GL(TestDrawTextOnPathInteractive);
|
||||
UNIT_TEST_GL(TestDrawTextOnPathBigSymbols);
|
||||
UNIT_TEST_GL(TestDrawTextOnPath);
|
||||
UNIT_TEST_GL(TestDrawTextOnPathZigZag);
|
||||
UNIT_TEST_GL(TestDrawTextOnPathWithOffset);
|
||||
// UNIT_TEST_GL(TestDrawTextOverflow);
|
||||
// UNIT_TEST_GL(TestDrawTextFiltering);
|
||||
// UNIT_TEST_GL(TestDrawRandomTextFiltering);
|
||||
|
|
Loading…
Add table
Reference in a new issue