mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +00:00
ShapedText: Break ligatures into multiple iterable placeholder glyphs
This commit is contained in:
parent
bc093fa64c
commit
427852836c
8 changed files with 282 additions and 70 deletions
|
|
@ -221,7 +221,7 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
|
||||
std::vector<LineInfo> lineInfos;
|
||||
|
||||
for (const auto [range, lineNumber] : shapedText.getLineNumbers())
|
||||
for (const auto [range, lineNumber] : shapedText.getLineNumbersForGlyphRanges())
|
||||
{
|
||||
// This is guaranteed by the RangedValues implementation. You can't assign a value to an
|
||||
// empty range.
|
||||
|
|
@ -259,7 +259,7 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
const auto containsHardBreak = shapedText.getCodepoint (range.getEnd() - 1) == 0xa
|
||||
|| shapedText.getCodepoint (range.getStart()) == 0xa;
|
||||
|
||||
if (containsHardBreak || lineNumber == shapedText.getLineNumbers().back().value)
|
||||
if (containsHardBreak || lineNumber == shapedText.getLineNumbersForGlyphRanges().back().value)
|
||||
{
|
||||
m.extraWhitespaceAdvance = {};
|
||||
m.stretchableWhitespaces = {};
|
||||
|
|
@ -282,7 +282,7 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
|
||||
for (const auto [lineIndex, lineInfo] : enumerate (lineInfos))
|
||||
{
|
||||
const auto lineNumber = shapedText.getLineNumbers().getItem ((size_t) lineIndex);
|
||||
const auto lineNumber = shapedText.getLineNumbersForGlyphRanges().getItem ((size_t) lineIndex);
|
||||
const auto range = lineNumber.range;
|
||||
|
||||
const auto maxDescent = lineInfo.lineHeight - lineInfo.maxAscent;
|
||||
|
|
@ -291,16 +291,17 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
if (! top.has_value())
|
||||
top = baseline - (1.0f + leading) * lineInfo.maxAscent;
|
||||
|
||||
linesMetrics.set (range,
|
||||
{ lineNumber.value,
|
||||
{ lineInfo.mainAxisLineAlignment.anchor, baseline },
|
||||
lineInfo.maxAscent,
|
||||
lineInfo.lineHeight - lineInfo.maxAscent,
|
||||
lineInfo.mainAxisLineAlignment.effectiveLineLength + lineInfo.mainAxisLineAlignment.extraWhitespaceAdvance,
|
||||
*top,
|
||||
nextLineTop },
|
||||
ops,
|
||||
MergeEqualItemsNo{});
|
||||
lineMetricsForGlyphRange.set (range,
|
||||
{ lineNumber.value,
|
||||
{ lineInfo.mainAxisLineAlignment.anchor, baseline },
|
||||
lineInfo.maxAscent,
|
||||
lineInfo.lineHeight - lineInfo.maxAscent,
|
||||
lineInfo.mainAxisLineAlignment.effectiveLineLength
|
||||
+ lineInfo.mainAxisLineAlignment.extraWhitespaceAdvance,
|
||||
*top,
|
||||
nextLineTop },
|
||||
ops,
|
||||
MergeEqualItemsNo{});
|
||||
|
||||
whitespaceStretch.set (range, 0.0f, ops);
|
||||
const auto stretchRange = lineInfo.mainAxisLineAlignment.stretchableWhitespaces + range.getStart();
|
||||
|
|
@ -324,10 +325,10 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
// The remaining logic below is for supporting
|
||||
// GlyphArrangement::addFittedText() when the maximum number of lines is
|
||||
// constrained.
|
||||
if (linesMetrics.isEmpty())
|
||||
if (lineMetricsForGlyphRange.isEmpty())
|
||||
return;
|
||||
|
||||
const auto lastLineMetrics = linesMetrics.back();
|
||||
const auto lastLineMetrics = lineMetricsForGlyphRange.back();
|
||||
const auto lastLineGlyphRange = lastLineMetrics.range;
|
||||
const auto lastLineGlyphs = shapedText.getGlyphs (lastLineGlyphRange);
|
||||
const auto lastLineLengths = getMainAxisLineLength (lastLineGlyphs);
|
||||
|
|
@ -469,12 +470,12 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
realign.extraWhitespaceAdvance, ops);
|
||||
}
|
||||
|
||||
int64 JustifiedText::getGlyphIndexAt (Point<float> p) const
|
||||
int64 JustifiedText::getGlyphIndexToTheRightOf (Point<float> p) const
|
||||
{
|
||||
auto lineIt = linesMetrics.begin();
|
||||
auto lineIt = lineMetricsForGlyphRange.begin();
|
||||
float lineTop = 0.0f;
|
||||
|
||||
while (lineIt != linesMetrics.end())
|
||||
while (lineIt != lineMetricsForGlyphRange.end())
|
||||
{
|
||||
const auto nextLineTop = lineIt->value.nextLineTop;
|
||||
|
||||
|
|
@ -485,7 +486,7 @@ int64 JustifiedText::getGlyphIndexAt (Point<float> p) const
|
|||
++lineIt;
|
||||
}
|
||||
|
||||
if (lineIt == linesMetrics.end())
|
||||
if (lineIt == lineMetricsForGlyphRange.end())
|
||||
return 0;
|
||||
|
||||
const auto glyphsInLine = shapedText.getGlyphs (lineIt->range);
|
||||
|
|
@ -495,7 +496,7 @@ int64 JustifiedText::getGlyphIndexAt (Point<float> p) const
|
|||
|
||||
for (const auto& glyph : glyphsInLine)
|
||||
{
|
||||
if ( p.getX() <= glyphX
|
||||
if ( p.getX() < glyphX + glyph.advance.getX() / 2.0f
|
||||
|| glyph.isNewline()
|
||||
|| (glyphIndex - lineIt->range.getStart() == (int64) glyphsInLine.size() - 1 && glyph.isWhitespace()))
|
||||
{
|
||||
|
|
@ -513,10 +514,12 @@ GlyphAnchorResult JustifiedText::getGlyphAnchor (int64 index) const
|
|||
{
|
||||
jassert (index >= 0);
|
||||
|
||||
if (linesMetrics.isEmpty())
|
||||
if (lineMetricsForGlyphRange.isEmpty())
|
||||
return {};
|
||||
|
||||
const auto lineItem = linesMetrics.getItemWithEnclosingRange (index).value_or (linesMetrics.back());
|
||||
const auto lineItem = lineMetricsForGlyphRange.getItemWithEnclosingRange (index)
|
||||
.value_or (lineMetricsForGlyphRange.back());
|
||||
|
||||
const auto indexInLine = index - lineItem.range.getStart();
|
||||
|
||||
GlyphAnchorResult anchor { lineItem.value.anchor, lineItem.value.maxAscent, lineItem.value.maxDescent };
|
||||
|
|
@ -539,7 +542,7 @@ RectangleList<float> JustifiedText::getGlyphsBounds (Range<int64> glyphRange) co
|
|||
{
|
||||
RectangleList<float> bounds;
|
||||
|
||||
if (linesMetrics.isEmpty())
|
||||
if (lineMetricsForGlyphRange.isEmpty())
|
||||
return bounds;
|
||||
|
||||
const auto getBounds = [&] (const LineMetrics& line, int64 lineStart, int64 boundsStart, int64 boundsEnd) -> Rectangle<float>
|
||||
|
|
@ -569,7 +572,7 @@ RectangleList<float> JustifiedText::getGlyphsBounds (Range<int64> glyphRange) co
|
|||
|
||||
for (auto consumeFrom = glyphRange.getStart(); consumeFrom < glyphRange.getEnd();)
|
||||
{
|
||||
const auto lineItem = linesMetrics.getItemWithEnclosingRange (consumeFrom);
|
||||
const auto lineItem = lineMetricsForGlyphRange.getItemWithEnclosingRange (consumeFrom);
|
||||
|
||||
if (! lineItem.has_value())
|
||||
break;
|
||||
|
|
@ -585,10 +588,10 @@ RectangleList<float> JustifiedText::getGlyphsBounds (Range<int64> glyphRange) co
|
|||
|
||||
float JustifiedText::getHeight() const
|
||||
{
|
||||
if (linesMetrics.isEmpty())
|
||||
if (lineMetricsForGlyphRange.isEmpty())
|
||||
return 0.0f;
|
||||
|
||||
return linesMetrics.back().value.nextLineTop;
|
||||
return lineMetricsForGlyphRange.back().value.nextLineTop;
|
||||
}
|
||||
|
||||
void drawJustifiedText (const JustifiedText& text, const Graphics& g, AffineTransform transform)
|
||||
|
|
|
|||
|
|
@ -113,10 +113,11 @@ public:
|
|||
void accessTogetherWith (Callable&& callback, RangedValues&&... rangedValues) const
|
||||
{
|
||||
std::optional<int64> lastLine;
|
||||
int64 lastGlyph = 0;
|
||||
Point<float> anchor {};
|
||||
|
||||
for (const auto item : makeIntersectingRangedValues (&shapedText.getResolvedFonts(),
|
||||
&linesMetrics,
|
||||
&lineMetricsForGlyphRange,
|
||||
&rangesToDraw,
|
||||
&whitespaceStretch,
|
||||
(&rangedValues)...))
|
||||
|
|
@ -127,6 +128,34 @@ public:
|
|||
if (std::exchange (lastLine, lineMetrics.lineNumber) != lineMetrics.lineNumber)
|
||||
anchor = lineMetrics.anchor;
|
||||
|
||||
if (range.getStart() != lastGlyph)
|
||||
{
|
||||
detail::RangedValues<int8_t> glyphMask;
|
||||
Ranges::Operations ops;
|
||||
|
||||
const auto firstGlyphInCurrentLine = shapedText.getLineNumbersForGlyphRanges().getItem ((size_t) lineMetrics.lineNumber)
|
||||
.range
|
||||
.getStart();
|
||||
|
||||
glyphMask.set ({ std::max (lastGlyph, firstGlyphInCurrentLine), range.getStart() }, 1, ops);
|
||||
|
||||
for (const auto [skippedRange, skippedStretch, _] : makeIntersectingRangedValues (&whitespaceStretch,
|
||||
&glyphMask))
|
||||
{
|
||||
ignoreUnused (_);
|
||||
|
||||
for (const auto& skippedGlyph : shapedText.getGlyphs (skippedRange))
|
||||
{
|
||||
anchor += skippedGlyph.advance;
|
||||
|
||||
if (skippedGlyph.isWhitespace())
|
||||
anchor.addXY (skippedStretch, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastGlyph = range.getEnd();
|
||||
|
||||
const auto glyphs = [this, r = range, dt = drawType]() -> Span<const ShapedGlyph>
|
||||
{
|
||||
if (dt == DrawType::ellipsis)
|
||||
|
|
@ -170,7 +199,7 @@ public:
|
|||
*/
|
||||
auto& getMinimumRequiredWidthForLines() const { return minimumRequiredWidthsForLine; }
|
||||
|
||||
int64 getGlyphIndexAt (Point<float> p) const;
|
||||
int64 getGlyphIndexToTheRightOf (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
|
||||
|
|
@ -190,11 +219,11 @@ public:
|
|||
*/
|
||||
float getHeight() const;
|
||||
|
||||
const auto& getLinesMetrics() const { return linesMetrics; }
|
||||
const auto& getLineMetricsForGlyphRange() const { return lineMetricsForGlyphRange; }
|
||||
|
||||
private:
|
||||
const SimpleShapedText& shapedText;
|
||||
detail::RangedValues<LineMetrics> linesMetrics;
|
||||
detail::RangedValues<LineMetrics> lineMetricsForGlyphRange;
|
||||
std::optional<SimpleShapedText> ellipsis;
|
||||
detail::RangedValues<DrawType> rangesToDraw;
|
||||
detail::RangedValues<float> whitespaceStretch;
|
||||
|
|
|
|||
|
|
@ -59,9 +59,14 @@ public:
|
|||
return simpleShapedText.getNumGlyphs();
|
||||
}
|
||||
|
||||
const detail::RangedValues<LineMetrics>& getLinesMetrics() const
|
||||
const detail::RangedValues<LineMetrics>& getLineMetricsForGlyphRange() const
|
||||
{
|
||||
return justifiedText.getLinesMetrics();
|
||||
return justifiedText.getLineMetricsForGlyphRange();
|
||||
}
|
||||
|
||||
const detail::Ranges& getLineTextRanges() const
|
||||
{
|
||||
return simpleShapedText.getLineTextRanges();
|
||||
}
|
||||
|
||||
auto& getText() const
|
||||
|
|
@ -74,9 +79,40 @@ public:
|
|||
return simpleShapedText.getTextRange (glyphIndex);
|
||||
}
|
||||
|
||||
int64 getGlyphIndexAt (Point<float> p) const
|
||||
auto isLtr (int64 glyphIndex) const
|
||||
{
|
||||
return justifiedText.getGlyphIndexAt (p);
|
||||
return simpleShapedText.isLtr (glyphIndex);
|
||||
}
|
||||
|
||||
int64 getTextIndexForCaret (Point<float> p) const
|
||||
{
|
||||
const auto getGlyph = [&] (int64 i)
|
||||
{
|
||||
return simpleShapedText.getGlyphs()[(size_t) i];
|
||||
};
|
||||
|
||||
if (getNumGlyphs() == 0)
|
||||
return 0;
|
||||
|
||||
const auto glyphOnTheRight = justifiedText.getGlyphIndexToTheRightOf (p);
|
||||
|
||||
if (glyphOnTheRight >= getNumGlyphs())
|
||||
{
|
||||
const auto glyphOnTheLeft = glyphOnTheRight - 1;
|
||||
const auto ltr = simpleShapedText.getGlyphLookup().find (getGlyph (glyphOnTheLeft).cluster)->value.ltr;
|
||||
|
||||
if (ltr)
|
||||
return simpleShapedText.getTextIndexAfterGlyph (glyphOnTheLeft);
|
||||
|
||||
return simpleShapedText.getGlyphs()[(size_t) glyphOnTheLeft].cluster;
|
||||
}
|
||||
|
||||
const auto ltr = simpleShapedText.getGlyphLookup().find (getGlyph (glyphOnTheRight).cluster)->value.ltr;
|
||||
|
||||
if (ltr)
|
||||
return simpleShapedText.getGlyphs()[(size_t) glyphOnTheRight].cluster;
|
||||
|
||||
return simpleShapedText.getTextIndexAfterGlyph (glyphOnTheRight);
|
||||
}
|
||||
|
||||
void getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const
|
||||
|
|
@ -139,9 +175,14 @@ int64 ShapedText::getNumGlyphs() const
|
|||
return impl->getNumGlyphs();
|
||||
}
|
||||
|
||||
const detail::RangedValues<LineMetrics>& ShapedText::getLinesMetrics() const
|
||||
const detail::RangedValues<LineMetrics>& ShapedText::getLineMetricsForGlyphRange() const
|
||||
{
|
||||
return impl->getLinesMetrics();
|
||||
return impl->getLineMetricsForGlyphRange();
|
||||
}
|
||||
|
||||
const detail::Ranges& ShapedText::getLineTextRanges() const
|
||||
{
|
||||
return impl->getLineTextRanges();
|
||||
}
|
||||
|
||||
const String& ShapedText::getText() const
|
||||
|
|
@ -154,9 +195,14 @@ Range<int64> ShapedText::getTextRange (int64 glyphIndex) const
|
|||
return impl->getTextRange (glyphIndex);
|
||||
}
|
||||
|
||||
int64 ShapedText::getGlyphIndexAt (Point<float> p) const
|
||||
bool ShapedText::isLtr (int64 glyphIndex) const
|
||||
{
|
||||
return impl->getGlyphIndexAt (p);
|
||||
return impl->isLtr (glyphIndex);
|
||||
}
|
||||
|
||||
int64 ShapedText::getTextIndexForCaret (Point<float> p) const
|
||||
{
|
||||
return impl->getTextIndexForCaret (p);
|
||||
}
|
||||
|
||||
void ShapedText::getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ public:
|
|||
/* Returns the text which was used to construct this object. */
|
||||
const String& getText() const;
|
||||
|
||||
Span<const ShapedGlyph> getGlyphs() const;
|
||||
|
||||
/* Returns the text's codepoint range, to which the glyph under the provided index belongs.
|
||||
|
||||
This range will have a length of at least one, and potentially more than one if ligatures
|
||||
|
|
@ -59,7 +61,9 @@ public:
|
|||
*/
|
||||
Range<int64> getTextRange (int64 glyphIndex) const;
|
||||
|
||||
int64 getGlyphIndexAt (Point<float> p) const;
|
||||
bool isLtr (int64 glyphIndex) const;
|
||||
|
||||
int64 getTextIndexForCaret (Point<float> p) const;
|
||||
|
||||
void getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const;
|
||||
|
||||
|
|
@ -94,7 +98,9 @@ public:
|
|||
|
||||
int64 getNumGlyphs() const;
|
||||
|
||||
const detail::RangedValues<LineMetrics>& getLinesMetrics() const;
|
||||
const detail::RangedValues<LineMetrics>& getLineMetricsForGlyphRange() const;
|
||||
|
||||
const detail::Ranges& getLineTextRanges() const;
|
||||
|
||||
/* @internal */
|
||||
const JustifiedText& getJustifiedText() const;
|
||||
|
|
|
|||
|
|
@ -317,14 +317,28 @@ static std::vector<ShapedGlyph> lowLevelShape (const String& string,
|
|||
std::vector<size_t> characterLookup;
|
||||
std::vector<ShapedGlyph> glyphs;
|
||||
|
||||
std::optional<uint32_t> lastCluster;
|
||||
std::optional<int64> lastCluster;
|
||||
|
||||
for (size_t i = 0; i < infos.size(); ++i)
|
||||
const auto ltr = (embeddingLevel % 2) == 0;
|
||||
|
||||
const auto getNextCluster = [<r, &infosCapt = infos, &range] (size_t visualIndex)
|
||||
{
|
||||
const auto j = (embeddingLevel % 2) == 0 ? i : infos.size() - 1 - i;
|
||||
const auto next = (int64) visualIndex + (ltr ? 1 : -1);
|
||||
|
||||
const auto glyphId = infos[j].codepoint;
|
||||
const auto xAdvance = positions[j].x_advance;
|
||||
if (next < 0)
|
||||
return ltr ? range.getStart() : range.getEnd();
|
||||
|
||||
if (next >= (int64) infosCapt.size())
|
||||
return ltr ? range.getEnd() : range.getStart();
|
||||
|
||||
return (int64) infosCapt[(size_t) next].cluster + range.getStart();
|
||||
};
|
||||
|
||||
for (size_t visualIndex = 0; visualIndex < infos.size(); ++visualIndex)
|
||||
{
|
||||
const auto glyphId = infos[visualIndex].codepoint;
|
||||
const auto xAdvanceBase = HbScale::hbToJuce (positions[visualIndex].x_advance);
|
||||
const auto yAdvanceBase = -HbScale::hbToJuce (positions[visualIndex].y_advance);
|
||||
|
||||
// For certain OS, Font and glyph ID combinations harfbuzz will not find extents data and
|
||||
// hb_font_get_glyph_extents will return false. In such cases Typeface::getGlyphBounds
|
||||
|
|
@ -341,11 +355,11 @@ static std::vector<ShapedGlyph> lowLevelShape (const String& string,
|
|||
|
||||
const auto whitespace = extentsDataAvailable
|
||||
&& font.getTypefacePtr()->getGlyphBounds (font.getMetricsKind(), (int) glyphId).isEmpty()
|
||||
&& xAdvance > 0;
|
||||
&& xAdvanceBase > 0;
|
||||
|
||||
const auto newline = std::invoke ([&controlChars, &shapingInfos = infos, j]
|
||||
const auto newline = std::invoke ([&controlChars, &shapingInfos = infos, visualIndex]
|
||||
{
|
||||
const auto it = controlChars.find ((size_t) shapingInfos[j].cluster);
|
||||
const auto it = controlChars.find ((size_t) shapingInfos[visualIndex].cluster);
|
||||
|
||||
if (it == controlChars.end())
|
||||
return false;
|
||||
|
|
@ -353,23 +367,56 @@ static std::vector<ShapedGlyph> lowLevelShape (const String& string,
|
|||
return it->second == ControlCharacter::cr || it->second == ControlCharacter::lf;
|
||||
});
|
||||
|
||||
const auto cluster = (int64) infos[visualIndex].cluster + range.getStart();
|
||||
|
||||
const auto numLigaturePlaceholders = std::max ((int64) 0,
|
||||
std::abs (getNextCluster (visualIndex) - cluster) - 1);
|
||||
|
||||
// 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
|
||||
const auto appliedTracking = std::exchange (lastCluster, cluster) != cluster
|
||||
? trackingAmount
|
||||
: 0;
|
||||
|
||||
const auto advanceMultiplier = numLigaturePlaceholders == 0 ? 1.0f
|
||||
: 1.0f / (float) (numLigaturePlaceholders + 1);
|
||||
|
||||
Point<float> advance { xAdvanceBase * advanceMultiplier + appliedTracking, yAdvanceBase * advanceMultiplier };
|
||||
|
||||
const auto ligatureClusterNumber = cluster + (ltr ? 0 : numLigaturePlaceholders);
|
||||
|
||||
glyphs.push_back ({
|
||||
glyphId,
|
||||
(int64) infos[j].cluster + range.getStart(),
|
||||
(infos[j].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) != 0,
|
||||
ligatureClusterNumber,
|
||||
(infos[visualIndex].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) },
|
||||
numLigaturePlaceholders == 0 ? (int8_t) 0 : (int8_t) -numLigaturePlaceholders ,
|
||||
advance,
|
||||
Point<float> { HbScale::hbToJuce (positions[visualIndex].x_offset),
|
||||
-HbScale::hbToJuce (positions[visualIndex].y_offset) },
|
||||
});
|
||||
|
||||
for (int l = 0; l < numLigaturePlaceholders; ++l)
|
||||
{
|
||||
const auto clusterDiff = l + 1;
|
||||
|
||||
glyphs.push_back ({
|
||||
glyphId,
|
||||
ligatureClusterNumber + (ltr ? clusterDiff : -clusterDiff),
|
||||
true,
|
||||
whitespace,
|
||||
newline,
|
||||
(int8_t) (l + 1),
|
||||
advance,
|
||||
Point<float>{},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (! ltr)
|
||||
std::reverse (glyphs.begin(), glyphs.end());
|
||||
|
||||
return glyphs;
|
||||
}
|
||||
|
||||
|
|
@ -1229,9 +1276,9 @@ void SimpleShapedText::shape (const String& data,
|
|||
.fillLines (shaper);
|
||||
auto& lineData = lineDataAndStorage.lines;
|
||||
|
||||
foldLinesBeyondLineLimit (lineData, (size_t) options.getMaxNumLines() - lineNumbers.size());
|
||||
foldLinesBeyondLineLimit (lineData, (size_t) options.getMaxNumLines() - lineNumbersForGlyphRanges.size());
|
||||
|
||||
if (lineNumbers.size() >= (size_t) options.getMaxNumLines())
|
||||
if (lineNumbersForGlyphRanges.size() >= (size_t) options.getMaxNumLines())
|
||||
break;
|
||||
|
||||
for (const auto& line : lineData)
|
||||
|
|
@ -1269,8 +1316,23 @@ void SimpleShapedText::shape (const String& data,
|
|||
ops.clear();
|
||||
}
|
||||
|
||||
const auto lineTextRange = std::accumulate (glyphSpansInLine.begin(),
|
||||
glyphSpansInLine.end(),
|
||||
std::make_pair (std::numeric_limits<int64>::max(),
|
||||
std::numeric_limits<int64>::min()),
|
||||
[&] (auto& sum, auto& elem) -> std::pair<int64, int64>
|
||||
{
|
||||
const auto r = elem.textRange + lineRange.getStart();
|
||||
|
||||
return { std::min (sum.first, r.getStart()),
|
||||
std::max (sum.second, r.getEnd()) };
|
||||
});
|
||||
|
||||
lineTextRanges.set ({ lineTextRange.first, lineTextRange.second }, ops);
|
||||
ops.clear();
|
||||
|
||||
const auto lineEnd = (int64) glyphsInVisualOrder.size();
|
||||
lineNumbers.set ({ lineStart, lineEnd}, (int64) lineNumbers.size(), ops);
|
||||
lineNumbersForGlyphRanges.set ({ lineStart, lineEnd}, (int64) lineNumbersForGlyphRanges.size(), ops);
|
||||
ops.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -1339,6 +1401,13 @@ Range<int64> SimpleShapedText::getTextRange (int64 glyphIndex) const
|
|||
return Range<int64>::withStartAndLength (cluster, std::max ((int64) 1, nextAdjacentCluster - cluster));
|
||||
}
|
||||
|
||||
bool SimpleShapedText::isLtr (int64 glyphIndex) const
|
||||
{
|
||||
const auto it = glyphLookup.find (glyphsInVisualOrder[(size_t) glyphIndex].cluster);
|
||||
jassert (it != glyphLookup.end());
|
||||
return it->value.ltr;
|
||||
}
|
||||
|
||||
/* Returns the first element that equals value, if such an element exists.
|
||||
|
||||
Otherwise, returns the last element that is smaller than value, if such an element exists.
|
||||
|
|
@ -1351,8 +1420,11 @@ Range<int64> SimpleShapedText::getTextRange (int64 glyphIndex) const
|
|||
lessThanOrEqual: less than or equal
|
||||
*/
|
||||
template <typename It, typename Value, typename Callback>
|
||||
auto lessThanOrEqual (It begin, It end, Value v, Callback extractValue)
|
||||
auto equalOrLessThan (It begin, It end, Value v, Callback extractValue)
|
||||
{
|
||||
if (begin == end)
|
||||
return end;
|
||||
|
||||
auto it = std::lower_bound (begin,
|
||||
end,
|
||||
v,
|
||||
|
|
@ -1361,7 +1433,7 @@ auto lessThanOrEqual (It begin, It end, Value v, Callback extractValue)
|
|||
return extractValue (elem) < value;
|
||||
});
|
||||
|
||||
if (it == end || it == begin || extractValue (*it) == v)
|
||||
if (it == begin || (it != end && extractValue (*it) == v))
|
||||
return it;
|
||||
|
||||
--it;
|
||||
|
|
@ -1384,7 +1456,7 @@ void SimpleShapedText::getGlyphRanges (Range<int64> textRange, std::vector<Range
|
|||
|
||||
const auto getGlyphSubRange = [&] (auto begin, auto end)
|
||||
{
|
||||
auto startIt = lessThanOrEqual (begin,
|
||||
auto startIt = equalOrLessThan (begin,
|
||||
end,
|
||||
textSubRange.getStart(),
|
||||
[] (auto& elem) -> auto& { return elem.cluster; });
|
||||
|
|
@ -1417,6 +1489,27 @@ void SimpleShapedText::getGlyphRanges (Range<int64> textRange, std::vector<Range
|
|||
outRanges = std::move (glyphRanges.getRanges());
|
||||
}
|
||||
|
||||
int64 SimpleShapedText::getTextIndexAfterGlyph (int64 glyphIndex) const
|
||||
{
|
||||
const auto& cluster = glyphsInVisualOrder[(size_t) glyphIndex].cluster;
|
||||
auto it = glyphLookup.find (glyphsInVisualOrder[(size_t) glyphIndex].cluster);
|
||||
|
||||
if (it->value.ltr)
|
||||
{
|
||||
for (auto i = glyphIndex + 1; i < it->value.glyphRange.getEnd(); ++i)
|
||||
if (const auto nextCluster = glyphsInVisualOrder[(size_t) i].cluster; nextCluster != cluster)
|
||||
return nextCluster;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto i = glyphIndex - 1; i >= it->value.glyphRange.getStart(); --i)
|
||||
if (const auto nextCluster = glyphsInVisualOrder[(size_t) i].cluster; nextCluster != cluster)
|
||||
return nextCluster;
|
||||
}
|
||||
|
||||
return it->range.getEnd();
|
||||
}
|
||||
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
struct SimpleShapedTextTests : public UnitTest
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ struct ShapedGlyph
|
|||
bool unsafeToBreakIn,
|
||||
bool whitespaceIn,
|
||||
bool newlineIn,
|
||||
int8_t distanceFromLigatureIn,
|
||||
Point<float> advanceIn,
|
||||
Point<float> offsetIn)
|
||||
: advance (advanceIn),
|
||||
|
|
@ -209,12 +210,20 @@ struct ShapedGlyph
|
|||
glyphId (glyphIdIn),
|
||||
unsafeToBreak (unsafeToBreakIn),
|
||||
whitespace (whitespaceIn),
|
||||
newline (newlineIn) {}
|
||||
newline (newlineIn),
|
||||
distanceFromLigature (distanceFromLigatureIn) {}
|
||||
|
||||
bool isUnsafeToBreak() const { return unsafeToBreak; }
|
||||
bool isWhitespace() const { return whitespace; }
|
||||
bool isNewline() const { return newline; }
|
||||
|
||||
bool isNonLigature() const { return distanceFromLigature == 0; }
|
||||
bool isLigature() const { return distanceFromLigature < 0; }
|
||||
bool isPlaceholderForLigature() const { return distanceFromLigature > 0; }
|
||||
|
||||
int8_t getDistanceFromLigature() const { return distanceFromLigature; }
|
||||
int8_t getNumTrailingLigaturePlaceholders() const { return -distanceFromLigature; }
|
||||
|
||||
Point<float> advance;
|
||||
Point<float> offset;
|
||||
int64 cluster;
|
||||
|
|
@ -225,6 +234,7 @@ private:
|
|||
int8_t unsafeToBreak;
|
||||
int8_t whitespace;
|
||||
int8_t newline;
|
||||
int8_t distanceFromLigature;
|
||||
};
|
||||
|
||||
struct GlyphLookupEntry
|
||||
|
|
@ -242,18 +252,30 @@ public:
|
|||
SimpleShapedText (const String* data,
|
||||
const ShapedTextOptions& options);
|
||||
|
||||
/* The returned container associates line numbers with the range of glyphs (not input codepoints)
|
||||
that make up the line.
|
||||
*/
|
||||
const auto& getLineNumbers() const { return lineNumbers; }
|
||||
const auto& getLineNumbersForGlyphRanges() const { return lineNumbersForGlyphRanges; }
|
||||
|
||||
const auto& getLineTextRanges() const { return lineTextRanges; }
|
||||
|
||||
const auto& getResolvedFonts() const { return resolvedFonts; }
|
||||
|
||||
Range<int64> getTextRange (int64 glyphIndex) const;
|
||||
|
||||
/* Returns true if the specified glyph is inside to an LTR run.
|
||||
*/
|
||||
bool isLtr (int64 glyphIndex) const;
|
||||
|
||||
void getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const;
|
||||
|
||||
int64 getNumLines() const { return (int64) lineNumbers.getRanges().size(); }
|
||||
/* Returns the input codepoint index that follows the glyph in a logical sense. So for LTR text
|
||||
this is the cluster number of the glyph to the right. For RTL text it's the one on the left.
|
||||
|
||||
If there is no subsequent glyph, the returned number is the first Unicode codepoint index
|
||||
that isn't covered by the cluster to which the selected glyph belongs, so for the glyph 'o'
|
||||
in "hello" this would be 5, given there are no ligatures in use.
|
||||
*/
|
||||
int64 getTextIndexAfterGlyph (int64 glyphIndex) const;
|
||||
|
||||
int64 getNumLines() const { return (int64) lineNumbersForGlyphRanges.getRanges().size(); }
|
||||
int64 getNumGlyphs() const { return (int64) glyphsInVisualOrder.size(); }
|
||||
|
||||
juce_wchar getCodepoint (int64 glyphIndex) const;
|
||||
|
|
@ -262,13 +284,16 @@ public:
|
|||
|
||||
Span<const ShapedGlyph> getGlyphs() const;
|
||||
|
||||
const auto& getGlyphLookup() const { return glyphLookup; }
|
||||
|
||||
private:
|
||||
void shape (const String& data,
|
||||
const ShapedTextOptions& options);
|
||||
|
||||
const String& string;
|
||||
std::vector<ShapedGlyph> glyphsInVisualOrder;
|
||||
detail::RangedValues<int64> lineNumbers;
|
||||
detail::RangedValues<int64> lineNumbersForGlyphRanges;
|
||||
detail::Ranges lineTextRanges;
|
||||
detail::RangedValues<Font> resolvedFonts;
|
||||
detail::RangedValues<GlyphLookupEntry> glyphLookup;
|
||||
|
||||
|
|
|
|||
|
|
@ -182,6 +182,9 @@ static void addGlyphsFromShapedText (GlyphArrangement& ga, const detail::ShapedT
|
|||
auto& glyph = shapedGlyphs[i];
|
||||
auto& position = positions[i];
|
||||
|
||||
if (glyph.isPlaceholderForLigature())
|
||||
continue;
|
||||
|
||||
PositionedGlyph pg { font,
|
||||
st.getText()[(int) st.getTextRange (glyphIndex).getStart()],
|
||||
(int) glyph.glyphId,
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ static Range<int64> getLineInputRange (const detail::ShapedText& st, int64 lineN
|
|||
using namespace detail;
|
||||
|
||||
return getInputRange (st, st.getSimpleShapedText()
|
||||
.getLineNumbers()
|
||||
.getLineNumbersForGlyphRanges()
|
||||
.getItem ((size_t) lineNumber).range);
|
||||
}
|
||||
|
||||
|
|
@ -360,7 +360,7 @@ static MaxFontAscentAndDescent getMaxFontAscentAndDescentInEnclosingLine (const
|
|||
{
|
||||
const auto sst = st.getSimpleShapedText();
|
||||
|
||||
const auto lineRange = sst.getLineNumbers()
|
||||
const auto lineRange = sst.getLineNumbersForGlyphRanges()
|
||||
.getItemWithEnclosingRange (lineChunkRange.getStart())->range;
|
||||
|
||||
const auto fonts = sst.getResolvedFonts().getIntersectionsWith (lineRange);
|
||||
|
|
@ -427,7 +427,7 @@ void TextLayout::createStandardLayout (const AttributedString& text)
|
|||
std::unique_ptr<Line> line;
|
||||
|
||||
st.accessTogetherWith ([&] (Span<const ShapedGlyph> glyphs,
|
||||
Span<Point<float>> positions,
|
||||
Span<const Point<float>> positions,
|
||||
Font font,
|
||||
Range<int64> glyphRange,
|
||||
LineMetrics lineMetrics,
|
||||
|
|
@ -470,7 +470,14 @@ void TextLayout::createStandardLayout (const AttributedString& text)
|
|||
}();
|
||||
|
||||
for (size_t i = 0; i < beyondLastNonWhitespace; ++i)
|
||||
run->glyphs.add ({ (int) glyphs[i].glyphId, positions[i] - line->lineOrigin, glyphs[i].advance.x });
|
||||
{
|
||||
if (glyphs[i].isPlaceholderForLigature())
|
||||
continue;
|
||||
|
||||
run->glyphs.add ({ (int) glyphs[i].glyphId,
|
||||
positions[i] - line->lineOrigin,
|
||||
glyphs[i].advance.x });
|
||||
}
|
||||
|
||||
line->runs.add (std::move (run));
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue