implemented beautiful kerning for drawPathText

This commit is contained in:
rachytski 2011-05-08 21:28:48 +03:00 committed by Alex Zolotarev
parent 2ddfc0a452
commit 60bc21b607
4 changed files with 208 additions and 49 deletions

View file

@ -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;
}

View file

@ -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);

View file

@ -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,

View file

@ -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);