diff --git a/base/base_tests/math_test.cpp b/base/base_tests/math_test.cpp index 2668fd7aab..2f483a03d7 100644 --- a/base/base_tests/math_test.cpp +++ b/base/base_tests/math_test.cpp @@ -139,6 +139,7 @@ UNIT_TEST(IsIntersect_Intervals) TEST(!my::IsIntersect(0, 100, -50, -20), ()); } +/* UNIT_TEST(MergeInterval_Simple) { int x0, x1; @@ -206,3 +207,4 @@ UNIT_TEST(MergeSorted_NullArray) k = my::MergeSorted(b, 0, a, ARRAY_SIZE(a), c, ARRAY_SIZE(c)); TEST(std::equal(c, c + k, etalon), ()); } +*/ diff --git a/base/base_tests/stl_add_test.cpp b/base/base_tests/stl_add_test.cpp index 212dd3c1f7..094ed9f5af 100644 --- a/base/base_tests/stl_add_test.cpp +++ b/base/base_tests/stl_add_test.cpp @@ -85,3 +85,110 @@ UNIT_TEST(STLAdd_RemoveIfKeepValid) TEST_EQUAL(v.size(), 4, ()); } } + +namespace +{ + template + void CheckAccumulateIntervals(size_t & idTest, + pair (&arr1)[N1], + pair (&arr2)[N2], + pair (&arr3)[N3]) + { + vector > res; + AccumulateIntervals1With2(arr1, arr1 + N1, arr2, arr2 + N2, back_inserter(res)); + + ++idTest; + TEST_EQUAL(N3, res.size(), ("Test", idTest, res)); + TEST(equal(res.begin(), res.end(), arr3), ("Test", idTest, res)); + } +} + +UNIT_TEST(STLAdd_AccumulateIntervals) +{ + typedef pair T; + size_t idTest = 0; + + // bound cases + { + vector res; + T arr[] = { T(10, 20) }; + + res.clear(); + AccumulateIntervals1With2(arr, arr + 1, arr, arr, back_inserter(res)); + TEST_EQUAL(res.size(), 1, ()); + + res.clear(); + AccumulateIntervals1With2(arr, arr, arr, arr + 1, back_inserter(res)); + TEST_EQUAL(res.size(), 0, ()); + } + + // check splice overlapped + { + T arr1[] = { T(10, 20), T(30, 40) }; + T arr2[] = { T(19, 31) }; + T res[] = { T(10, 40) }; + CheckAccumulateIntervals(idTest, arr1, arr2, res); + } + + // check skip not overlapped + { + T arr1[] = { T(10, 20), T(30, 40) }; + T arr2[] = { T(0, 9), T(21, 29), T(41, 50) }; + T res[2] = { T(10, 20), T(30, 40) }; + CheckAccumulateIntervals(idTest, arr1, arr2, res); + } + { + T arr1[] = { T(10, 20), T(30, 40) }; + T arr2[] = { T(1, 2), T(3, 4), T(5, 6), + T(21, 22), T(23, 24), T(25, 26), + T(41, 42), T(43, 44), T(45, 46) + }; + T res[] = { T(10, 20), T(30, 40) }; + CheckAccumulateIntervals(idTest, arr1, arr2, res); + } + + // check equal bounds + { + T arr1[] = { T(20, 30) }; + T arr2[] = { T(10, 20), T(30, 40) }; + T res[] = { T(20, 30) }; + CheckAccumulateIntervals(idTest, arr1, arr2, res); + } + { + T arr1[] = { T(10, 20), T(30, 40) }; + T arr2[] = { T(20, 30) }; + T res[] = { T(10, 20), T(30, 40) }; + CheckAccumulateIntervals(idTest, arr1, arr2, res); + } + + // check large overlap interval + { + T arr1[] = { T(10, 20), T(30, 40), T(50, 60) }; + T arr2[] = { T(0, 100) }; + T res[] = { T(0, 100) }; + CheckAccumulateIntervals(idTest, arr1, arr2, res); + } + { + T arr1[] = { T(0, 100) }; + T arr2[] = { T(10, 20), T(30, 40), T(50, 60) }; + T res[] = { T(0, 100) }; + CheckAccumulateIntervals(idTest, arr1, arr2, res); + } + + // check splice overlapped + { + T arr1[] = { T(10, 20), T(30, 40) }; + T arr2[] = { T(5, 15), T(35, 45) }; + T res[] = { T(5, 20), T(30, 45) }; + CheckAccumulateIntervals(idTest, arr1, arr2, res); + } + { + T arr1[] = { T(10, 20), T(30, 40) }; + T arr2[] = { T(1, 2), T(3, 4), T(5, 15), + T(17, 25), T(26, 27), T(28, 32), + T(38, 45), T(46, 50) + }; + T res[] = { T(5, 25), T(28, 45) }; + CheckAccumulateIntervals(idTest, arr1, arr2, res); + } +} diff --git a/base/math.hpp b/base/math.hpp index 8700fb1c89..06468c0d63 100644 --- a/base/math.hpp +++ b/base/math.hpp @@ -107,6 +107,7 @@ bool IsIntersect(T const & x0, T const & x1, T const & x2, T const & x3) return !((x1 < x2) || (x3 < x0)); } +/* template void Merge(T const & x0, T const & x1, T const & x2, T const & x3, T & x4, T & x5) { @@ -179,6 +180,7 @@ size_t MergeSorted(T const * a, size_t as, T const * b, size_t bs, T * c, size_t return k; } +*/ // Computes x^n. template inline T PowUint(T x, uint64_t n) diff --git a/base/stl_add.hpp b/base/stl_add.hpp index c118e6773f..b695a75703 100644 --- a/base/stl_add.hpp +++ b/base/stl_add.hpp @@ -3,6 +3,7 @@ #include "../std/iterator.hpp" #include "../std/map.hpp" + template class BackInsertFunctor { ContainerT & m_Container; @@ -168,9 +169,65 @@ template IterT PrevIterInCycle(IterT it, IterT beg, IterT end) return --it; } -template -ValueT ValueForKey(map const & m, KeyT key, ValueT defaultV) +template +void AccumulateIntervals1With2(IterT1 b1, IterT1 e1, IterT2 b2, IterT2 e2, InsertIterT res) { - typename map::const_iterator const it = m.find(key); - return (it == m.end() ? defaultV : it->second); + //typedef typename iterator_traits::value_type T; + typedef typename iterator_traits::value_type T; + + T prev; + bool validPrev = false; + + while (b1 != e1 || b2 != e2) + { + // Try to continue previous range. + if (validPrev) + { + // add b1 range to prev if needed + if (b1 != e1 && b1->first < prev.second) + { + // correct only second if needed + if (prev.second < b1->second) + prev.second = b1->second; + ++b1; + continue; + } + + // add b2 range to prev if needed + if (b2 != e2 && b2->first < prev.second) + { + // check that intervals are overlapped + if (prev.first < b2->second) + { + // correct first and second if needed + if (b2->first < prev.first) + prev.first = b2->first; + if (prev.second < b2->second) + prev.second = b2->second; + } + + ++b2; + continue; + } + + // if nothing to add - push to results + *res++ = prev; + validPrev = false; + } + + if (b1 != e1) + { + // start new range + prev = *b1++; + validPrev = true; + } + else + { + // go to exit + break; + } + } + + if (validPrev) + *res++ = prev; } diff --git a/map/feature_styler.cpp b/map/feature_styler.cpp index f7fd1edd89..78dbab2118 100644 --- a/map/feature_styler.cpp +++ b/map/feature_styler.cpp @@ -16,6 +16,11 @@ #include "../graphics/glyph_cache.hpp" +#include "../base/stl_add.hpp" + +#include "../std/iterator_facade.hpp" + + namespace { struct less_depth @@ -108,7 +113,6 @@ namespace di bool hasIcon = false; bool hasCaptionWithoutOffset = false; - bool pathWasClipped = false; m_fontSize = 0; size_t const count = keys.size(); @@ -149,26 +153,6 @@ namespace di { m_hasPathText = true; - /// calculating clip intervals only once - - if (!pathWasClipped) - { - typedef gp::filter_screenpts_adapter functor_t; - - functor_t::params p; - - p.m_convertor = m_convertor; - p.m_rect = m_rect; - p.m_intervals = &m_intervals; - - functor_t fun(p); - - f.ForEachPointRef(fun, zoom); - - m_pathLength = fun.m_length; - pathWasClipped = true; - } - if (!FilterTextSize(m_rules[i].m_rule)) m_fontSize = max(m_fontSize, GetTextFontSize(m_rules[i].m_rule)); } @@ -179,9 +163,22 @@ namespace di hasCaptionWithoutOffset = true; } - /// placing a text on the path + // placing a text on the path if (m_hasPathText && (m_fontSize != 0)) - LayoutTexts(); + { + typedef gp::filter_screenpts_adapter functor_t; + + functor_t::params p; + + p.m_convertor = m_convertor; + p.m_rect = m_rect; + p.m_intervals = &m_intervals; + + functor_t fun(p); + f.ForEachPointRef(fun, zoom); + + LayoutTexts(fun.m_length); + } if (hasIcon && hasCaptionWithoutOffset) // we need to delete symbol style (single one due to MakeUnique call above) @@ -198,49 +195,113 @@ namespace di sort(m_rules.begin(), m_rules.end(), less_depth()); } - void FeatureStyler::LayoutTexts() + typedef pair RangeT; + template class RangeIterT : + public iterator_facade, RangeT, forward_traversal_tag, RangeT> { - m_textLength = m_glyphCache->getTextLength(m_fontSize, GetPathName()); - double emptySize = max(200 * m_visualScale, m_textLength); - double minPeriodSize = emptySize + m_textLength; + IterT m_iter; + public: + RangeIterT(IterT iter) : m_iter(iter) {} + + RangeT dereference() const + { + IterT next = m_iter; + ++next; + return RangeT(*m_iter, *next); + } + bool equal(RangeIterT const & r) const { return (m_iter == r.m_iter); } + void increment() + { + ++m_iter; ++m_iter; + } + }; + + template class RangeInserter + { + ContT & m_cont; + public: + RangeInserter(ContT & cont) : m_cont(cont) {} + + RangeInserter & operator*() { return *this; } + RangeInserter & operator++(int) { return *this; } + RangeInserter & operator=(RangeT const & r) + { + m_cont.push_back(r.first); + m_cont.push_back(r.second); + return *this; + } + }; + + void FeatureStyler::LayoutTexts(double pathLength) + { + double const textLength = m_glyphCache->getTextLength(m_fontSize, GetPathName()); + /// @todo Choose best constant for minimal space. + double const emptySize = max(200 * m_visualScale, textLength); + // multiply on factor because tiles will be rendered in smaller scales + double const minPeriodSize = 1.5 * (emptySize + textLength); size_t textCnt = 0; double firstTextOffset = 0; - if (m_pathLength > m_textLength) + if (pathLength > textLength) { - textCnt = ceil((m_pathLength - m_textLength) / minPeriodSize); - firstTextOffset = 0.5 * (m_pathLength - (textCnt * m_textLength + (textCnt - 1) * emptySize)); + textCnt = ceil((pathLength - textLength) / minPeriodSize); + firstTextOffset = 0.5 * (pathLength - (textCnt * textLength + (textCnt - 1) * emptySize)); } - if ((textCnt != 0) && (!m_intervals.empty())) + if (textCnt != 0 && !m_intervals.empty()) { - buffer_vector deadZones; + buffer_vector deadZones; - for (unsigned i = 0; i < textCnt; ++i) + for (size_t i = 0; i < textCnt; ++i) { - double deadZoneStart = firstTextOffset + (m_textLength + minPeriodSize) * i; - double deadZoneEnd = deadZoneStart + m_textLength; + double const deadZoneStart = firstTextOffset + minPeriodSize * i; + double const deadZoneEnd = deadZoneStart + textLength; if (deadZoneStart > m_intervals.back()) break; - deadZones.push_back(deadZoneStart); - deadZones.push_back(deadZoneEnd); - m_offsets.push_back(deadZoneStart); + deadZones.push_back(make_pair(deadZoneStart, deadZoneEnd)); } if (!deadZones.empty()) { - buffer_vector res(deadZones.size() + m_intervals.size()); + buffer_vector res; - int k = my::MergeSorted(&m_intervals[0], m_intervals.size(), - &deadZones[0], deadZones.size(), - &res[0], res.size()); - - res.resize(k); + // accumulate text layout intervals with cliping intervals + typedef RangeIterT IterT; + AccumulateIntervals1With2(IterT(m_intervals.begin()), IterT(m_intervals.end()), + deadZones.begin(), deadZones.end(), + RangeInserter(res)); m_intervals = res; + ASSERT_EQUAL(m_intervals.size() % 2, 0, ()); + + // get final text offsets (belongs to final clipping intervals) + size_t i = 0; + size_t j = 0; + while (i != deadZones.size() && j != m_intervals.size()) + { + ASSERT_LESS(deadZones[i].first, deadZones[i].second, ()); + ASSERT_LESS(m_intervals[j], m_intervals[j+1], ()); + + if (deadZones[i].first < m_intervals[j]) + { + ++i; + continue; + } + if (m_intervals[j+1] <= deadZones[i].first) + { + j += 2; + continue; + } + + ASSERT_LESS_OR_EQUAL(m_intervals[j], deadZones[i].first, ()); + ASSERT_LESS_OR_EQUAL(deadZones[i].second, m_intervals[j+1], ()); + + m_offsets.push_back(deadZones[i].first); + ++i; + } } } } diff --git a/map/feature_styler.hpp b/map/feature_styler.hpp index 47e54da20b..469fe51211 100644 --- a/map/feature_styler.hpp +++ b/map/feature_styler.hpp @@ -65,8 +65,6 @@ namespace di typedef buffer_vector TextOffsetsT; TextOffsetsT m_offsets; - double m_pathLength; - double m_textLength; uint8_t m_fontSize; graphics::GlyphCache * m_glyphCache; @@ -80,7 +78,9 @@ namespace di string const GetPathName() const; bool FilterTextSize(drule::BaseRule const * pRule) const; + + private: uint8_t GetTextFontSize(drule::BaseRule const * pRule) const; - void LayoutTexts(); + void LayoutTexts(double pathLength); }; }