correct PathTextElement transformation. closes #298

This commit is contained in:
rachytski 2011-09-21 00:03:50 +03:00 committed by Alex Zolotarev
parent 2212d9cd66
commit d82ea4ad7b
4 changed files with 128 additions and 78 deletions

View file

@ -1,7 +1,6 @@
#include "glyph_layout.hpp"
#include "font_desc.hpp"
#include "resource_style.hpp"
#include "text_path.hpp"
#include "../base/logging.hpp"
#include "../base/math.hpp"
@ -13,32 +12,27 @@
namespace yg
{
void GlyphLayoutElem::transform(math::Matrix<double, 3, 3> const & m)
{
m_pt = m_pt * m;
}
double GlyphLayout::getKerning(GlyphLayoutElem const & prevElem, GlyphLayoutElem const & curElem)
double GlyphLayout::getKerning(GlyphLayoutElem const & prevElem, GlyphMetrics const & prevMetrics, GlyphLayoutElem const & curElem, GlyphMetrics const & curMetrics)
{
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.cos(), prevElem.m_angle.sin()), //< moving by angle = prevElem.m_angle - math::pi / 2
prevElem.m_pt.Move(prevMetrics.m_height, -prevElem.m_angle.cos(), prevElem.m_angle.sin()), //< moving by angle = 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::RectD(prevMetrics.m_xOffset,
prevMetrics.m_yOffset,
prevMetrics.m_xOffset + prevMetrics.m_width,
prevMetrics.m_yOffset + prevMetrics.m_height));
m2::AARectD curSymRectAA(
curElem.m_pt.Move(curElem.m_metrics.m_height, -curElem.m_angle.cos(), curElem.m_angle.sin()), //< moving by angle = curElem.m_angle - math::pi / 2
curElem.m_pt.Move(curMetrics.m_height, -curElem.m_angle.cos(), curElem.m_angle.sin()), //< moving by angle = 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(curMetrics.m_xOffset,
curMetrics.m_yOffset,
curMetrics.m_xOffset + curMetrics.m_width,
curMetrics.m_yOffset + curMetrics.m_height)
);
if (prevElem.m_angle.val() == curElem.m_angle.val())
@ -65,16 +59,6 @@ namespace yg
GlyphLayout::GlyphLayout()
{}
GlyphLayout::GlyphLayout(GlyphLayout const & src, math::Matrix<double, 3, 3> const & m)
: m_firstVisible(src.m_firstVisible),
m_lastVisible(src.m_lastVisible),
m_pivot(src.m_pivot * m)
{
m_entries = src.m_entries;
/// TODO: it's better and faster to transform AARect, not calculate it once again.
computeMinLimitRect();
}
GlyphLayout::GlyphLayout(GlyphCache * glyphCache,
FontDesc const & fontDesc,
m2::PointD const & pt,
@ -113,8 +97,8 @@ namespace yg
elem.m_sym = visText[i];
elem.m_angle = 0;
elem.m_pt = curPt;
elem.m_metrics = m;
m_entries.push_back(elem);
m_metrics.push_back(m);
curPt += m2::PointD(m.m_xAdvance, m.m_yAdvance);
}
@ -144,6 +128,23 @@ namespace yg
m_limitRect.Offset(ptOffs);
}
GlyphLayout::GlyphLayout(GlyphLayout const & src,
math::Matrix<double, 3, 3> const & m)
: m_firstVisible(0),
m_lastVisible(0),
m_path(src.m_path, m),
m_visText(src.m_visText),
m_pos(src.m_pos),
m_fontDesc(src.m_fontDesc),
m_metrics(src.m_metrics),
m_limitRect(m2::RectD(0, 0, 0, 0)),
m_pivot(0, 0)
{
m_fullLength = (m2::PointD(src.m_fullLength, 0) * m).Length(m2::PointD(0, 0) * m);
m_pathOffset = (m2::PointD(src.m_pathOffset, 0) * m).Length(m2::PointD(0, 0) * m);
recalcAlongPath();
}
GlyphLayout::GlyphLayout(GlyphCache * glyphCache,
FontDesc const & fontDesc,
m2::PointD const * pts,
@ -154,79 +155,90 @@ namespace yg
yg::EPosition pos)
: m_firstVisible(0),
m_lastVisible(0),
m_path(pts, ptsCount, fullLength, pathOffset),
m_fullLength(fullLength),
m_pathOffset(pathOffset),
m_visText(visText),
m_pos(pos),
m_fontDesc(fontDesc),
m_limitRect(m2::RectD(0, 0, 0, 0)),
m_pivot(0, 0)
{
TextPath arrPath(pts, ptsCount, fullLength, pathOffset);
for (size_t i = 0; i < m_visText.size(); ++i)
m_metrics.push_back(glyphCache->getGlyphMetrics(GlyphKey(visText[i], m_fontDesc.m_size, m_fontDesc.m_isMasked, yg::Color(0, 0, 0, 0))));
recalcAlongPath();
}
void GlyphLayout::recalcAlongPath()
{
// get vector of glyphs and calculate string length
double strLength = 0.0;
size_t count = visText.size();
size_t count = m_visText.size();
if (count != 0)
m_entries.resize(count);
for (size_t i = 0; i < m_entries.size(); ++i)
{
m_entries[i].m_sym = visText[i];
m_entries[i].m_metrics = glyphCache->getGlyphMetrics(GlyphKey(m_entries[i].m_sym, fontDesc.m_size, fontDesc.m_isMasked, yg::Color(0, 0, 0, 0)));
strLength += m_entries[i].m_metrics.m_xAdvance;
m_entries[i].m_sym = m_visText[i];
strLength += m_metrics[i].m_xAdvance;
}
if (fullLength < strLength)
if (m_fullLength < strLength)
return;
PathPoint arrPathStart(0, ang::AngleD(ang::AngleTo(arrPath.get(0), arrPath.get(1))), arrPath.get(0));
PathPoint arrPathStart(0, ang::AngleD(ang::AngleTo(m_path.get(0), m_path.get(1))), m_path.get(0));
m_pivot = arrPath.offsetPoint(arrPathStart, fullLength / 2.0).m_pt;
m_pivot = m_path.offsetPoint(arrPathStart, m_fullLength / 2.0).m_pt;
// offset of the text from path's start
double offset = (fullLength - strLength) / 2.0;
double offset = (m_fullLength - strLength) / 2.0;
if (pos & yg::EPosLeft)
if (m_pos & yg::EPosLeft)
{
offset = 0;
m_pivot = arrPathStart.m_pt;
}
if (pos & yg::EPosRight)
if (m_pos & yg::EPosRight)
{
offset = (fullLength - strLength);
m_pivot = arrPath.get(arrPath.size() - 1);
offset = (m_fullLength - strLength);
m_pivot = m_path.get(m_path.size() - 1);
}
// calculate base line offset
double blOffset = 2 - fontDesc.m_size / 2;
double blOffset = 2 - m_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)
if (m_pos & yg::EPosUnder)
blOffset = 2 - m_fontDesc.m_size;
if (m_pos & yg::EPosAbove)
blOffset = 2;
offset -= pathOffset;
offset -= m_pathOffset;
if (-offset >= strLength)
return;
// find first visible glyph
size_t symPos = 0;
while (offset < 0 && symPos < count)
offset += m_entries[symPos++].m_metrics.m_xAdvance;
offset += m_metrics[symPos++].m_xAdvance;
PathPoint glyphStartPt = arrPath.offsetPoint(arrPathStart, offset);
PathPoint glyphStartPt = m_path.offsetPoint(arrPathStart, offset);
m_firstVisible = symPos;
GlyphLayoutElem prevElem; //< previous glyph, to compute kerning from
GlyphMetrics prevMetrics;
bool hasPrevElem = false;
for (; symPos < count; ++symPos)
{
/// full advance, including possible kerning.
double fullGlyphAdvance = m_entries[symPos].m_metrics.m_xAdvance;
double fullGlyphAdvance = m_metrics[symPos].m_xAdvance;
GlyphMetrics const & metrics = m_entries[symPos].m_metrics;
GlyphMetrics const & metrics = m_metrics[symPos];
if (glyphStartPt.m_i == -1)
return;
@ -239,7 +251,7 @@ namespace yg
int i = 0;
for (; i < 100; ++i)
{
PivotPoint pivotPt = arrPath.findPivotPoint(glyphStartPt, metrics, fullKern);
PivotPoint pivotPt = m_path.findPivotPoint(glyphStartPt, metrics, fullKern);
if (pivotPt.m_pp.m_i == -1)
return;
@ -258,7 +270,7 @@ namespace yg
// < check whether we should "kern"
if (hasPrevElem)
{
kern = getKerning(prevElem, m_entries[symPos]);
kern = getKerning(prevElem, prevMetrics, m_entries[symPos], m_metrics[symPos]);
if (kern < 0.5)
kern = 0;
@ -276,6 +288,7 @@ namespace yg
/// kerning should be computed for baseline centered glyph
prevElem = m_entries[symPos];
prevMetrics = m_metrics[symPos];
hasPrevElem = true;
// < align to baseline
@ -294,7 +307,7 @@ namespace yg
}
}
glyphStartPt = arrPath.offsetPoint(glyphStartPt, fullGlyphAdvance);
glyphStartPt = m_path.offsetPoint(glyphStartPt, fullGlyphAdvance);
offset += fullGlyphAdvance;
m_lastVisible = symPos + 1;
@ -320,19 +333,19 @@ namespace yg
for (unsigned i = m_firstVisible; i < m_lastVisible; ++i)
{
if (m_entries[i].m_metrics.m_width != 0)
if (m_metrics[i].m_width != 0)
{
map<double, m2::AARectD>::iterator it = rects.find(m_entries[i].m_angle.val());
if (it == rects.end())
{
m2::AARectD symRectAA(
m_entries[i].m_pt.Move(m_entries[i].m_metrics.m_height, -m_entries[i].m_angle.cos(), m_entries[i].m_angle.sin()), //< moving by angle = m_entries[i].m_angle - math::pi / 2
m_entries[i].m_pt.Move(m_metrics[i].m_height, -m_entries[i].m_angle.cos(), m_entries[i].m_angle.sin()), //< moving by angle = m_entries[i].m_angle - math::pi / 2
m_entries[i].m_angle,
m2::RectD(m_entries[i].m_metrics.m_xOffset,
m_entries[i].m_metrics.m_yOffset,
m_entries[i].m_metrics.m_xOffset + m_entries[i].m_metrics.m_width,
m_entries[i].m_metrics.m_yOffset + m_entries[i].m_metrics.m_height
m2::RectD(m_metrics[i].m_xOffset,
m_metrics[i].m_yOffset,
m_metrics[i].m_xOffset + m_metrics[i].m_width,
m_metrics[i].m_yOffset + m_metrics[i].m_height
));
rects[m_entries[i].m_angle.val()] = symRectAA;
@ -342,15 +355,15 @@ namespace yg
for (unsigned i = m_firstVisible; i < m_lastVisible; ++i)
{
if (m_entries[i].m_metrics.m_width != 0)
if (m_metrics[i].m_width != 0)
{
m2::AARectD symRectAA(
m_entries[i].m_pt.Move(m_entries[i].m_metrics.m_height, -m_entries[i].m_angle.cos(), m_entries[i].m_angle.sin()), //< moving by angle = m_entries[i].m_angle - math::pi / 2
m_entries[i].m_pt.Move(m_metrics[i].m_height, -m_entries[i].m_angle.cos(), m_entries[i].m_angle.sin()), //< moving by angle = m_entries[i].m_angle - math::pi / 2
m_entries[i].m_angle,
m2::RectD(m_entries[i].m_metrics.m_xOffset,
m_entries[i].m_metrics.m_yOffset,
m_entries[i].m_metrics.m_xOffset + m_entries[i].m_metrics.m_width,
m_entries[i].m_metrics.m_yOffset + m_entries[i].m_metrics.m_height));
m2::RectD(m_metrics[i].m_xOffset,
m_metrics[i].m_yOffset,
m_metrics[i].m_xOffset + m_metrics[i].m_width,
m_metrics[i].m_yOffset + m_metrics[i].m_height));
for (map<double, m2::AARectD>::iterator it = rects.begin(); it != rects.end(); ++it)
it->second.Add(symRectAA);
@ -393,6 +406,11 @@ namespace yg
return m_entries;
}
vector<GlyphMetrics> const & GlyphLayout::metrics() const
{
return m_metrics;
}
m2::AARectD const GlyphLayout::limitRect() const
{
return m2::Offset(m_limitRect, pivot());

View file

@ -14,6 +14,8 @@
#include "../std/shared_ptr.hpp"
#include "glyph_cache.hpp"
#include "text_path.hpp"
#include "font_desc.hpp"
namespace yg
{
@ -23,7 +25,6 @@ namespace yg
struct GlyphLayoutElem
{
wchar_t m_sym;
GlyphMetrics m_metrics;
ang::AngleD m_angle;
m2::PointD m_pt;
@ -37,21 +38,34 @@ namespace yg
size_t m_firstVisible;
size_t m_lastVisible;
TextPath m_path;
double m_fullLength;
double m_pathOffset;
strings::UniString m_visText;
yg::EPosition m_pos;
yg::FontDesc m_fontDesc;
vector<GlyphMetrics> m_metrics;
vector<GlyphLayoutElem> m_entries;
m2::AARectD m_limitRect;
m2::PointD m_pivot;
double getKerning(GlyphLayoutElem const & prevElem, GlyphLayoutElem const & curElem);
double getKerning(GlyphLayoutElem const & prevElem, GlyphMetrics const & prevMetrics, GlyphLayoutElem const & curElem, GlyphMetrics const & curMetrics);
void computeMinLimitRect();
void recalcPivot();
void recalcAlongPath();
public:
GlyphLayout();
GlyphLayout(GlyphLayout const & layout, math::Matrix<double, 3, 3> const & m);
GlyphLayout(GlyphLayout const & layout,
math::Matrix<double, 3, 3> const & m);
GlyphLayout(GlyphCache * glyphCache,
FontDesc const & font,
@ -72,6 +86,7 @@ namespace yg
size_t lastVisible() const;
vector<GlyphLayoutElem> const & entries() const;
vector<GlyphMetrics> const & metrics() const;
m2::AARectD const limitRect() const;

View file

@ -16,10 +16,24 @@ namespace yg
: m_angle(angle), m_pp(pp)
{}
TextPath::TextPath(m2::PointD const * arr, size_t sz, double fullLength, double & pathOffset)
: m_arr(arr), m_size(sz), m_reverse(false)
TextPath::TextPath() : m_reverse(false)
{}
TextPath::TextPath(TextPath const & src, math::Matrix<double, 3, 3> const & m)
: m_reverse(src.m_reverse),
m_arr(src.m_arr)
{
ASSERT ( m_size > 1, () );
for (unsigned i = 0; i < m_arr.size(); ++i)
m_arr[i] = m_arr[i] * m;
}
TextPath::TextPath(m2::PointD const * arr, size_t sz, double fullLength, double & pathOffset)
: m_reverse(false)
{
ASSERT ( sz > 1, () );
m_arr.resize(sz);
copy(arr, arr + sz, m_arr.begin());
/* assume, that readable text in path should be ('o' - start draw point):
* / o
@ -28,12 +42,12 @@ namespace yg
* o \
*/
double const a = ang::AngleTo(m_arr[0], m_arr[m_size-1]);
double const a = ang::AngleTo(m_arr[0], m_arr[m_arr.size() - 1]);
if (fabs(a) > math::pi / 2.0)
{
// if we swap direction, we need to recalculate path offset from the end
double len = 0.0;
for (size_t i = 1; i < m_size; ++i)
for (size_t i = 1; i < m_arr.size(); ++i)
len += m_arr[i-1].Length(m_arr[i]);
pathOffset = fullLength - pathOffset - len;
@ -44,12 +58,12 @@ namespace yg
}
}
size_t TextPath::size() const { return m_size; }
size_t TextPath::size() const { return m_arr.size(); }
m2::PointD TextPath::get(size_t i) const
{
ASSERT ( i < m_size, ("Index out of range") );
return m_arr[m_reverse ? m_size - i - 1 : i];
ASSERT ( i < m_arr.size(), ("Index out of range") );
return m_arr[m_reverse ? m_arr.size() - i - 1 : i];
}
m2::PointD TextPath::operator[](size_t i) const { return get(i); }

View file

@ -2,6 +2,7 @@
#include "../geometry/point2d.hpp"
#include "../geometry/angles.hpp"
#include "../base/matrix.hpp"
#include "glyph_cache.hpp"
namespace yg
@ -25,10 +26,12 @@ namespace yg
class TextPath
{
m2::PointD const * m_arr;
size_t m_size;
buffer_vector<m2::PointD, 8> m_arr;
bool m_reverse;
public:
TextPath();
TextPath(TextPath const & src, math::Matrix<double, 3, 3> const & m);
TextPath(m2::PointD const * arr, size_t sz, double fullLength, double & pathOffset);
size_t size() const;