mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +00:00
ShapedText: Add bidirectional lookup functions relating positions, input indices and glyph indices
This commit is contained in:
parent
6cf697bd6d
commit
d72df5faa3
6 changed files with 205 additions and 0 deletions
|
|
@ -449,6 +449,69 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
realign.extraWhitespaceAdvance);
|
||||
}
|
||||
|
||||
int64 JustifiedText::getGlyphIndexAt (Point<float> p) const
|
||||
{
|
||||
auto lineIt = linesMetrics.begin();
|
||||
float lineTop = 0.0f;
|
||||
|
||||
while (lineIt != linesMetrics.end())
|
||||
{
|
||||
const auto nextLineTop = lineIt->value.nextLineTop;
|
||||
|
||||
if (lineTop <= p.getY() && p.getY() < nextLineTop)
|
||||
break;
|
||||
|
||||
lineTop = nextLineTop;
|
||||
++lineIt;
|
||||
}
|
||||
|
||||
if (lineIt == linesMetrics.end())
|
||||
return 0;
|
||||
|
||||
const auto glyphsInLine = shapedText.getGlyphs (lineIt->range);
|
||||
|
||||
auto glyphIndex = lineIt->range.getStart();
|
||||
auto glyphX = lineIt->value.anchor.getX();
|
||||
|
||||
for (const auto& glyph : glyphsInLine)
|
||||
{
|
||||
if ( p.getX() <= glyphX
|
||||
|| glyph.newline
|
||||
|| (glyphIndex - lineIt->range.getStart() == (int64) glyphsInLine.size() - 1 && glyph.whitespace))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
++glyphIndex;
|
||||
glyphX += glyph.advance.getX();
|
||||
}
|
||||
|
||||
return glyphIndex;
|
||||
}
|
||||
|
||||
Point<float> JustifiedText::getGlyphAnchor (int64 index) const
|
||||
{
|
||||
jassert (index >= 0);
|
||||
|
||||
if (linesMetrics.isEmpty())
|
||||
return {};
|
||||
|
||||
const auto lineItem = linesMetrics.getItemWithEnclosingRange (index).value_or (linesMetrics.back());
|
||||
const auto indexInLine = index - lineItem.range.getStart();
|
||||
|
||||
auto anchor = lineItem.value.anchor;
|
||||
|
||||
for (auto [i, glyph] : enumerate (shapedText.getGlyphs (lineItem.range), int64{}))
|
||||
{
|
||||
if (i == indexInLine)
|
||||
return anchor + glyph.offset;
|
||||
|
||||
anchor += glyph.advance;
|
||||
}
|
||||
|
||||
return anchor;
|
||||
}
|
||||
|
||||
float JustifiedText::getHeight() const
|
||||
{
|
||||
if (linesMetrics.isEmpty())
|
||||
|
|
|
|||
|
|
@ -153,6 +153,14 @@ public:
|
|||
*/
|
||||
auto& getMinimumRequiredWidthForLines() const { return minimumRequiredWidthsForLine; }
|
||||
|
||||
int64 getGlyphIndexAt (Point<float> p) const;
|
||||
|
||||
/* If the passed in index parameter is greater than the index of the last contained glyph,
|
||||
then the returned anchor specifies the location where the next glyph would have to be
|
||||
placed i.e. lastGlyphAnchor + lastGlyphAdvance.
|
||||
*/
|
||||
Point<float> getGlyphAnchor (int64 index) const;
|
||||
|
||||
/* Returns the vertical distance from the baseline of the first line to the bottom of the last
|
||||
plus any additional line spacing that follows from the leading and additiveLineSpacing
|
||||
members of the ShapedTextOptions object.
|
||||
|
|
|
|||
|
|
@ -54,6 +54,11 @@ public:
|
|||
return justifiedText.getHeight();
|
||||
}
|
||||
|
||||
int64 getNumGlyphs() const
|
||||
{
|
||||
return simpleShapedText.getNumGlyphs();
|
||||
}
|
||||
|
||||
auto& getText() const
|
||||
{
|
||||
return text;
|
||||
|
|
@ -64,6 +69,21 @@ public:
|
|||
return simpleShapedText.getTextRange (glyphIndex);
|
||||
}
|
||||
|
||||
int64 getGlyphIndexAt (Point<float> p) const
|
||||
{
|
||||
return justifiedText.getGlyphIndexAt (p);
|
||||
}
|
||||
|
||||
auto getGlyphRanges (Range<int64> textRange) const
|
||||
{
|
||||
return simpleShapedText.getGlyphRanges (textRange);
|
||||
}
|
||||
|
||||
auto getGlyphAnchor (int64 index) const
|
||||
{
|
||||
return justifiedText.getGlyphAnchor (index);
|
||||
}
|
||||
|
||||
Span<const float> getMinimumRequiredWidthForLines() const
|
||||
{
|
||||
return justifiedText.getMinimumRequiredWidthForLines();
|
||||
|
|
@ -104,6 +124,11 @@ float ShapedText::getHeight() const
|
|||
return impl->getHeight();
|
||||
}
|
||||
|
||||
int64 ShapedText::getNumGlyphs() const
|
||||
{
|
||||
return impl->getNumGlyphs();
|
||||
}
|
||||
|
||||
const String& ShapedText::getText() const
|
||||
{
|
||||
return impl->getText();
|
||||
|
|
@ -114,6 +139,21 @@ Range<int64> ShapedText::getTextRange (int64 glyphIndex) const
|
|||
return impl->getTextRange (glyphIndex);
|
||||
}
|
||||
|
||||
int64 ShapedText::getGlyphIndexAt (Point<float> p) const
|
||||
{
|
||||
return impl->getGlyphIndexAt (p);
|
||||
}
|
||||
|
||||
std::vector<Range<int64>> ShapedText::getGlyphRanges (Range<int64> textRange) const
|
||||
{
|
||||
return impl->getGlyphRanges (textRange);
|
||||
}
|
||||
|
||||
Point<float> ShapedText::getGlyphAnchor (int64 index) const
|
||||
{
|
||||
return impl->getGlyphAnchor (index);
|
||||
}
|
||||
|
||||
Span<const float> ShapedText::getMinimumRequiredWidthForLines() const
|
||||
{
|
||||
return impl->getMinimumRequiredWidthForLines();
|
||||
|
|
|
|||
|
|
@ -59,6 +59,13 @@ public:
|
|||
*/
|
||||
Range<int64> getTextRange (int64 glyphIndex) const;
|
||||
|
||||
int64 getGlyphIndexAt (Point<float> p) const;
|
||||
|
||||
std::vector<Range<int64>> getGlyphRanges (Range<int64> textRange) const;
|
||||
|
||||
/* @see JustifiedText::getGlyphAnchor() */
|
||||
Point<float> getGlyphAnchor (int64 index) const;
|
||||
|
||||
/* Returns the widths for each line, that the glyphs would require to be rendered without being
|
||||
truncated. This will or will not include the space required by trailing whitespaces in the
|
||||
line based on the ShapedTextOptions::withTrailingWhitespacesShouldFit() value.
|
||||
|
|
@ -79,8 +86,12 @@ public:
|
|||
/* Draws the text. */
|
||||
void draw (const Graphics& g, AffineTransform transform) const;
|
||||
|
||||
/* @see JustifiedText::getHeight
|
||||
*/
|
||||
float getHeight() const;
|
||||
|
||||
int64 getNumGlyphs() const;
|
||||
|
||||
/* @internal */
|
||||
const JustifiedText& getJustifiedText() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -343,6 +343,16 @@ static std::vector<ShapedGlyph> lowLevelShape (const String& string,
|
|||
&& font.getTypefacePtr()->getGlyphBounds (font.getMetricsKind(), (int) glyphId).isEmpty()
|
||||
&& xAdvance > 0;
|
||||
|
||||
const auto newline = std::invoke ([&controlChars, &shapingInfos = infos, j]
|
||||
{
|
||||
const auto it = controlChars.find ((size_t) shapingInfos[j].cluster);
|
||||
|
||||
if (it == controlChars.end())
|
||||
return false;
|
||||
|
||||
return it->second == ControlCharacter::cr || it->second == ControlCharacter::lf;
|
||||
});
|
||||
|
||||
// Tracking is only applied at the beginning of a new cluster to avoid inserting it before
|
||||
// diacritic marks.
|
||||
const auto appliedTracking = std::exchange (lastCluster, infos[j].cluster) != infos[j].cluster
|
||||
|
|
@ -354,6 +364,7 @@ static std::vector<ShapedGlyph> lowLevelShape (const String& string,
|
|||
(int64) infos[j].cluster + range.getStart(),
|
||||
(infos[j].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) != 0,
|
||||
whitespace,
|
||||
newline,
|
||||
Point<float> { HbScale::hbToJuce (xAdvance) + appliedTracking, -HbScale::hbToJuce (positions[j].y_advance) },
|
||||
Point<float> { HbScale::hbToJuce (positions[j].x_offset), -HbScale::hbToJuce (positions[j].y_offset) },
|
||||
});
|
||||
|
|
@ -1286,6 +1297,75 @@ Range<int64> SimpleShapedText::getTextRange (int64 glyphIndex) const
|
|||
return Range<int64>::withStartAndLength (cluster, std::max ((int64) 1, nextAdjacentCluster - cluster));
|
||||
}
|
||||
|
||||
/* Returns the last element that is smaller than value or equal to it. Returns begin of no such
|
||||
element is found.
|
||||
|
||||
NB: lower_bound: equal or greater
|
||||
upper_bound: greater
|
||||
lessThanOrEqual: less than or equal
|
||||
*/
|
||||
template <typename It, typename Value, typename Callback>
|
||||
auto lessThanOrEqual (It begin, It end, Value v, Callback extractValue)
|
||||
{
|
||||
auto it = std::lower_bound (begin,
|
||||
end,
|
||||
v,
|
||||
[&extractValue] (auto& elem, auto& value)
|
||||
{
|
||||
return extractValue (elem) < value;
|
||||
});
|
||||
|
||||
if (it == end || it == begin || extractValue (*it) == v)
|
||||
return it;
|
||||
|
||||
--it;
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
std::vector<Range<int64>> SimpleShapedText::getGlyphRanges (Range<int64> textRange) const
|
||||
{
|
||||
Ranges glyphRanges;
|
||||
|
||||
for (const auto is : glyphLookup.getIntersectionsWith (textRange))
|
||||
{
|
||||
const auto textSubRange = is.range;
|
||||
const auto glyphsSubRange = is.value.glyphRange;
|
||||
const auto& subRangeLookup = is.value;
|
||||
const auto glyphs = getGlyphs (glyphsSubRange);
|
||||
|
||||
const auto getGlyphSubRange = [&] (auto begin, auto end)
|
||||
{
|
||||
auto startIt = lessThanOrEqual (begin,
|
||||
end,
|
||||
textSubRange.getStart(),
|
||||
[] (auto& elem) -> auto& { return elem.cluster; });
|
||||
|
||||
auto endIt = std::lower_bound (begin,
|
||||
end,
|
||||
textSubRange.getEnd(),
|
||||
[] (auto& elem, auto& value) { return elem.cluster < value; });
|
||||
|
||||
return Range<int64> { (int64) std::distance (begin, startIt), std::distance (begin, endIt) };
|
||||
};
|
||||
|
||||
if (subRangeLookup.ltr)
|
||||
{
|
||||
glyphRanges.set (getGlyphSubRange (glyphs.begin(), glyphs.end()) + glyphsSubRange.getStart());
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto reverseRange = getGlyphSubRange (std::reverse_iterator { glyphs.end() },
|
||||
std::reverse_iterator { glyphs.begin() });
|
||||
|
||||
glyphRanges.set ({ glyphsSubRange.getEnd() - reverseRange.getEnd(),
|
||||
glyphsSubRange.getEnd() - reverseRange.getStart() });
|
||||
}
|
||||
}
|
||||
|
||||
return glyphRanges.getRanges();
|
||||
}
|
||||
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
struct SimpleShapedTextTests : public UnitTest
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ struct ShapedGlyph
|
|||
int64 cluster;
|
||||
bool unsafeToBreak;
|
||||
bool whitespace;
|
||||
bool newline;
|
||||
Point<float> advance;
|
||||
Point<float> offset;
|
||||
};
|
||||
|
|
@ -203,6 +204,8 @@ public:
|
|||
|
||||
Range<int64> getTextRange (int64 glyphIndex) const;
|
||||
|
||||
std::vector<Range<int64>> getGlyphRanges (Range<int64> textRange) const;
|
||||
|
||||
int64 getNumLines() const { return (int64) lineNumbers.getRanges().size(); }
|
||||
int64 getNumGlyphs() const { return (int64) glyphsInVisualOrder.size(); }
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue