diff --git a/yg/glyph_layout.cpp b/yg/glyph_layout.cpp index 1686964b0d..d88e1dbec5 100644 --- a/yg/glyph_layout.cpp +++ b/yg/glyph_layout.cpp @@ -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 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; } diff --git a/yg/glyph_layout.hpp b/yg/glyph_layout.hpp index 6de008215f..3032e968ca 100644 --- a/yg/glyph_layout.hpp +++ b/yg/glyph_layout.hpp @@ -34,6 +34,8 @@ namespace yg vector m_entries; + double getKerning(GlyphLayoutElem const & prevElem, GlyphLayoutElem const & curElem); + public: GlyphLayout(GlyphLayout const & layout, double shift); diff --git a/yg/text_renderer.cpp b/yg/text_renderer.cpp index f9e5465b1f..ce33bb5ad8 100644 --- a/yg/text_renderer.cpp +++ b/yg/text_renderer.cpp @@ -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 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(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, diff --git a/yg/yg_tests/screengl_test.cpp b/yg/yg_tests/screengl_test.cpp index 70b0bc2ec4..c23dee0ba0 100644 --- a/yg/yg_tests/screengl_test.cpp +++ b/yg/yg_tests/screengl_test.cpp @@ -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 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 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 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);