2025-11-28 00:35:46 +09:00

922 lines
30 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
//
// Contents: Custom layout, demonstrating usage of shaping and glyph
// results.
//
//----------------------------------------------------------------------------
#include "common.h"
#include "TextAnalysis.h"
#include "FlowSource.h"
#include "FlowSink.h"
#include "FlowLayout.h"
namespace
{
// Estimates the maximum number of glyph indices needed to hold a string of
// a given length. This is the formula given in the Uniscribe SDK and should
// cover most cases. Degenerate cases will require a reallocation.
UINT32 EstimateGlyphCount(UINT32 textLength)
{
return 3 * textLength / 2 + 16;
}
}
STDMETHODIMP FlowLayout::SetTextFormat(IDWriteTextFormat* textFormat)
{
// Initializes properties using a text format, like font family, font size,
// and reading direction. For simplicity, this custom layout supports
// minimal formatting. No mixed formatting or alignment modes are supported.
HRESULT hr = S_OK;
IDWriteFontCollection* fontCollection = NULL;
IDWriteFontFamily* fontFamily = NULL;
IDWriteFont* font = NULL;
wchar_t fontFamilyName[100];
readingDirection_ = textFormat->GetReadingDirection();
fontEmSize_ = textFormat->GetFontSize();
hr = textFormat->GetLocaleName(localeName_, ARRAYSIZE(localeName_));
////////////////////
// Map font and style to fontFace.
if (SUCCEEDED(hr))
{
// Need the font collection to map from font name to actual font.
textFormat->GetFontCollection(&fontCollection);
if (fontCollection == NULL)
{
// No font collection was set in the format, so use the system default.
hr = dwriteFactory_->GetSystemFontCollection(&fontCollection);
}
}
// Find matching family name in collection.
if (SUCCEEDED(hr))
{
hr = textFormat->GetFontFamilyName(fontFamilyName, ARRAYSIZE(fontFamilyName));
}
UINT32 fontIndex = 0;
if (SUCCEEDED(hr))
{
BOOL fontExists = false;
hr = fontCollection->FindFamilyName(fontFamilyName, &fontIndex, &fontExists);
if (!fontExists)
{
// If the given font does not exist, take what we can get
// (displaying something instead nothing), choosing the foremost
// font in the collection.
fontIndex = 0;
}
}
if (SUCCEEDED(hr))
{
hr = fontCollection->GetFontFamily(fontIndex, &fontFamily);
}
if (SUCCEEDED(hr))
{
hr = fontFamily->GetFirstMatchingFont(
textFormat->GetFontWeight(),
textFormat->GetFontStretch(),
textFormat->GetFontStyle(),
&font
);
}
if (SUCCEEDED(hr))
{
SafeRelease(&fontFace_);
hr = font->CreateFontFace(&fontFace_);
}
SafeRelease(&font);
SafeRelease(&fontFamily);
SafeRelease(&fontCollection);
return S_OK;
}
STDMETHODIMP FlowLayout::SetNumberSubstitution(IDWriteNumberSubstitution* numberSubstitution)
{
SafeSet(&numberSubstitution_, numberSubstitution);
return S_OK;
}
STDMETHODIMP FlowLayout::AnalyzeText(
const wchar_t* text, // [textLength]
UINT32 textLength
) throw()
{
// Analyzes the given text and keeps the results for later reflow.
isTextAnalysisComplete_ = false;
if (fontFace_ == NULL)
return E_FAIL; // Need a font face to determine metrics.
HRESULT hr = S_OK;
try
{
text_.assign(text, textLength);
}
catch (...)
{
hr = ExceptionToHResult();
}
// Query for the text analyzer's interface.
IDWriteTextAnalyzer* textAnalyzer = NULL;
if (SUCCEEDED(hr))
{
hr = dwriteFactory_->CreateTextAnalyzer(&textAnalyzer);
}
// Record the analyzer's results.
if (SUCCEEDED(hr))
{
TextAnalysis textAnalysis(text, textLength, localeName_, numberSubstitution_, readingDirection_);
hr = textAnalysis.GenerateResults(textAnalyzer, runs_, breakpoints_);
}
// Convert the entire text to glyphs.
if (SUCCEEDED(hr))
{
hr = ShapeGlyphRuns(textAnalyzer);
}
if (SUCCEEDED(hr))
{
isTextAnalysisComplete_ = true;
}
SafeRelease(&textAnalyzer);
return hr;
}
STDMETHODIMP FlowLayout::ShapeGlyphRuns(IDWriteTextAnalyzer* textAnalyzer)
{
// Shapes all the glyph runs in the layout.
HRESULT hr = S_OK;
UINT32 textLength = static_cast<UINT32>(text_.size());
// Estimate the maximum number of glyph indices needed to hold a string.
UINT32 estimatedGlyphCount = EstimateGlyphCount(textLength);
try
{
glyphIndices_.resize(estimatedGlyphCount);
glyphOffsets_.resize(estimatedGlyphCount);
glyphAdvances_.resize(estimatedGlyphCount);
glyphClusters_.resize(textLength);
UINT32 glyphStart = 0;
// Shape each run separately. This is needed whenever script, locale,
// or reading direction changes.
for (UINT32 runIndex = 0; runIndex < runs_.size(); ++runIndex)
{
hr = ShapeGlyphRun(textAnalyzer, runIndex, glyphStart);
if (FAILED(hr))
break;
}
glyphIndices_.resize(glyphStart);
glyphOffsets_.resize(glyphStart);
glyphAdvances_.resize(glyphStart);
}
catch (...)
{
hr = ExceptionToHResult();
}
return hr;
}
STDMETHODIMP FlowLayout::ShapeGlyphRun(
IDWriteTextAnalyzer* textAnalyzer,
UINT32 runIndex,
IN OUT UINT32& glyphStart
)
{
// Shapes a single run of text into glyphs.
// Alternately, you could iteratively interleave shaping and line
// breaking to reduce the number glyphs held onto at once. It's simpler
// for this demostration to just do shaping and line breaking as two
// separate processes, but realize that this does have the consequence that
// certain advanced fonts containing line specific features (like Gabriola)
// will shape as if the line is not broken.
HRESULT hr = S_OK;
try
{
TextAnalysis::Run& run = runs_[runIndex];
UINT32 textStart = run.textStart;
UINT32 textLength = run.textLength;
UINT32 maxGlyphCount = static_cast<UINT32>(glyphIndices_.size() - glyphStart);
UINT32 actualGlyphCount = 0;
run.glyphStart = glyphStart;
run.glyphCount = 0;
if (textLength == 0)
return S_OK; // Nothing to do..
HRESULT hr = S_OK;
////////////////////
// Allocate space for shaping to fill with glyphs and other information,
// with about as many glyphs as there are text characters. We'll actually
// need more glyphs than codepoints if they are decomposed into separate
// glyphs, or fewer glyphs than codepoints if multiple are substituted
// into a single glyph. In any case, the shaping process will need some
// room to apply those rules to even make that determintation.
if (textLength > maxGlyphCount)
{
maxGlyphCount = EstimateGlyphCount(textLength);
UINT32 totalGlyphsArrayCount = glyphStart + maxGlyphCount;
glyphIndices_.resize(totalGlyphsArrayCount);
}
std::vector<DWRITE_SHAPING_TEXT_PROPERTIES> textProps(textLength);
std::vector<DWRITE_SHAPING_GLYPH_PROPERTIES> glyphProps(maxGlyphCount);
////////////////////
// Get the glyphs from the text, retrying if needed.
int tries = 0;
do
{
hr = textAnalyzer->GetGlyphs(
&text_[textStart],
textLength,
fontFace_,
run.isSideways, // isSideways,
(run.bidiLevel & 1), // isRightToLeft
&run.script,
localeName_,
(run.isNumberSubstituted) ? numberSubstitution_ : NULL,
NULL, // features
NULL, // featureLengths
0, // featureCount
maxGlyphCount, // maxGlyphCount
&glyphClusters_[textStart],
&textProps[0],
&glyphIndices_[glyphStart],
&glyphProps[0],
&actualGlyphCount
);
tries++;
if (hr == E_NOT_SUFFICIENT_BUFFER)
{
// Try again using a larger buffer.
maxGlyphCount = EstimateGlyphCount(maxGlyphCount);
UINT32 totalGlyphsArrayCount = glyphStart + maxGlyphCount;
glyphProps.resize(maxGlyphCount);
glyphIndices_.resize(totalGlyphsArrayCount);
}
else
{
break;
}
} while (tries < 2); // We'll give it two chances.
if (FAILED(hr))
return hr;
////////////////////
// Get the placement of the all the glyphs.
glyphAdvances_.resize(std::max(static_cast<size_t>(glyphStart + actualGlyphCount), glyphAdvances_.size()));
glyphOffsets_.resize( std::max(static_cast<size_t>(glyphStart + actualGlyphCount), glyphOffsets_.size()));
hr = textAnalyzer->GetGlyphPlacements(
&text_[textStart],
&glyphClusters_[textStart],
&textProps[0],
textLength,
&glyphIndices_[glyphStart],
&glyphProps[0],
actualGlyphCount,
fontFace_,
fontEmSize_,
run.isSideways,
(run.bidiLevel & 1), // isRightToLeft
&run.script,
localeName_,
NULL, // features
NULL, // featureRangeLengths
0, // featureRanges
&glyphAdvances_[glyphStart],
&glyphOffsets_[glyphStart]
);
if (FAILED(hr))
return hr;
////////////////////
// Certain fonts, like Batang, contain glyphs for hidden control
// and formatting characters. So we'll want to explicitly force their
// advance to zero.
if (run.script.shapes & DWRITE_SCRIPT_SHAPES_NO_VISUAL)
{
std::fill(glyphAdvances_.begin() + glyphStart,
glyphAdvances_.begin() + glyphStart + actualGlyphCount,
0.0f
);
}
////////////////////
// Set the final glyph count of this run and advance the starting glyph.
run.glyphCount = actualGlyphCount;
glyphStart += actualGlyphCount;
}
catch (...)
{
hr = ExceptionToHResult();
}
return hr;
}
STDMETHODIMP FlowLayout::FlowText(
FlowLayoutSource* flowSource,
FlowLayoutSink* flowSink
)
{
// Reflow all the text, from source to sink.
if (!isTextAnalysisComplete_)
return E_FAIL;
HRESULT hr = S_OK;
// Determine the font line height, needed by the flow source.
DWRITE_FONT_METRICS fontMetrics = {};
fontFace_->GetMetrics(&fontMetrics);
float fontHeight = (fontMetrics.ascent + fontMetrics.descent + fontMetrics.lineGap)
* fontEmSize_ / fontMetrics.designUnitsPerEm;
// Get ready for series of glyph runs.
hr = flowSink->Prepare(static_cast<UINT32>(glyphIndices_.size()));
if (SUCCEEDED(hr))
{
// Set initial cluster position to beginning of text.
ClusterPosition cluster, nextCluster;
SetClusterPosition(cluster, 0);
FlowLayoutSource::RectF rect;
UINT32 textLength = static_cast<UINT32>(text_.size());
// Iteratively pull rect's from the source,
// and push as much text will fit to the sink.
while (cluster.textPosition < textLength)
{
// Pull the next rect from the source.
if (FAILED(flowSource->GetNextRect(fontHeight, &rect)))
break;
if (rect.right - rect.left <= 0)
break; // Stop upon reaching zero sized rects.
// Fit as many clusters between breakpoints that will go in.
if (FAILED(FitText(cluster, textLength, rect.right - rect.left, &nextCluster)))
break;
// Push the glyph runs to the sink.
if (FAILED(ProduceGlyphRuns(flowSink, rect, cluster, nextCluster)))
break;
cluster = nextCluster;
}
}
return hr;
}
STDMETHODIMP FlowLayout::FitText(
const ClusterPosition& clusterStart,
UINT32 textEnd,
float maxWidth,
OUT ClusterPosition* clusterEnd
)
{
// Fits as much text as possible into the given width,
// using the clusters and advances returned by DWrite.
////////////////////////////////////////
// Set the starting cluster to the starting text position,
// and continue until we exceed the maximum width or hit
// a hard break.
ClusterPosition cluster(clusterStart);
ClusterPosition nextCluster(clusterStart);
UINT32 validBreakPosition = cluster.textPosition;
UINT32 bestBreakPosition = cluster.textPosition;
float textWidth = 0;
while (cluster.textPosition < textEnd)
{
// Use breakpoint information to find where we can safely break words.
AdvanceClusterPosition(nextCluster);
const DWRITE_LINE_BREAKPOINT breakpoint = breakpoints_[nextCluster.textPosition - 1];
// Check whether we exceeded the amount of text that can fit,
// unless it's whitespace, which we allow to flow beyond the end.
textWidth += GetClusterRangeWidth(cluster, nextCluster);
if (textWidth > maxWidth && !breakpoint.isWhitespace)
{
// Want a minimum of one cluster.
if (validBreakPosition > clusterStart.textPosition)
break;
}
validBreakPosition = nextCluster.textPosition;
// See if we can break after this character cluster, and if so,
// mark it as the new potential break point.
if (breakpoint.breakConditionAfter != DWRITE_BREAK_CONDITION_MAY_NOT_BREAK)
{
bestBreakPosition = validBreakPosition;
if (breakpoint.breakConditionAfter == DWRITE_BREAK_CONDITION_MUST_BREAK)
break; // we have a hard return, so we've fit all we can.
}
cluster = nextCluster;
}
////////////////////////////////////////
// Want last best position that didn't break a word, but if that's not available,
// fit at least one cluster (emergency line breaking).
if (bestBreakPosition == clusterStart.textPosition)
bestBreakPosition = validBreakPosition;
SetClusterPosition(cluster, bestBreakPosition);
*clusterEnd = cluster;
return S_OK;
}
STDMETHODIMP FlowLayout::ProduceGlyphRuns(
FlowLayoutSink* flowSink,
const FlowLayoutSource::RectF& rect,
const ClusterPosition& clusterStart,
const ClusterPosition& clusterEnd
) const throw()
{
// Produce a series of glyph runs from the given range
// and send them to the sink. If the entire text fit
// into the rect, then we'll only pass on a single glyph
// run.
HRESULT hr = S_OK;
////////////////////////////////////////
// Figure out how many runs we cross, because this is the number
// of distinct glyph runs we'll need to reorder visually.
UINT32 runIndexEnd = clusterEnd.runIndex;
if (clusterEnd.textPosition > runs_[runIndexEnd].textStart)
++runIndexEnd; // Only partially cover the run, so round up.
// Fill in mapping from visual run to logical sequential run.
UINT32 bidiOrdering[100];
UINT32 totalRuns = runIndexEnd - clusterStart.runIndex;
totalRuns = std::min(totalRuns, static_cast<UINT32>(ARRAYSIZE(bidiOrdering)));
ProduceBidiOrdering(
clusterStart.runIndex,
totalRuns,
&bidiOrdering[0]
);
////////////////////////////////////////
// Ignore any trailing whitespace
// Look backwards from end until we find non-space.
UINT32 trailingWsPosition = clusterEnd.textPosition;
for ( ; trailingWsPosition > clusterStart.textPosition; --trailingWsPosition)
{
if (!breakpoints_[trailingWsPosition-1].isWhitespace)
break; // Encountered last significant character.
}
// Set the glyph run's ending cluster to the last whitespace.
ClusterPosition clusterWsEnd(clusterStart);
SetClusterPosition(clusterWsEnd, trailingWsPosition);
////////////////////////////////////////
// Produce justified advances to reduce the jagged edge.
std::vector<float> justifiedAdvances;
hr = ProduceJustifiedAdvances(rect, clusterStart, clusterWsEnd, justifiedAdvances);
UINT32 justificationGlyphStart = GetClusterGlyphStart(clusterStart);
////////////////////////////////////////
// Determine starting point for alignment.
float x = rect.left;
float y = rect.bottom;
if (SUCCEEDED(hr))
{
DWRITE_FONT_METRICS fontMetrics;
fontFace_->GetMetrics(&fontMetrics);
float descent = (fontMetrics.descent * fontEmSize_ / fontMetrics.designUnitsPerEm);
y -= descent;
if (readingDirection_ == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT)
{
// For RTL, we neeed the run width to adjust the origin
// so it starts on the right side.
UINT32 glyphStart = GetClusterGlyphStart(clusterStart);
UINT32 glyphEnd = GetClusterGlyphStart(clusterWsEnd);
if (glyphStart < glyphEnd)
{
float lineWidth = GetClusterRangeWidth(
glyphStart - justificationGlyphStart,
glyphEnd - justificationGlyphStart,
&justifiedAdvances.front()
);
x = rect.right - lineWidth;
}
}
}
////////////////////////////////////////
// Send each glyph run to the sink.
if (SUCCEEDED(hr))
{
for (size_t i = 0; i < totalRuns; ++i)
{
const TextAnalysis::Run& run = runs_[bidiOrdering[i]];
UINT32 glyphStart = run.glyphStart;
UINT32 glyphEnd = glyphStart + run.glyphCount;
// If the run is only partially covered, we'll need to find
// the subsection of glyphs that were fit.
if (clusterStart.textPosition > run.textStart)
{
glyphStart = GetClusterGlyphStart(clusterStart);
}
if (clusterWsEnd.textPosition < run.textStart + run.textLength)
{
glyphEnd = GetClusterGlyphStart(clusterWsEnd);
}
if ((glyphStart >= glyphEnd)
|| (run.script.shapes & DWRITE_SCRIPT_SHAPES_NO_VISUAL))
{
// The itemizer told us not to draw this character,
// either because it was a formatting, control, or other hidden character.
continue;
}
// The run width is needed to know how far to move forward,
// and to flip the origin for right-to-left text.
float runWidth = GetClusterRangeWidth(
glyphStart - justificationGlyphStart,
glyphEnd - justificationGlyphStart,
&justifiedAdvances.front()
);
// Flush this glyph run.
hr = flowSink->SetGlyphRun(
(run.bidiLevel & 1) ? (x + runWidth) : (x), // origin starts from right if RTL
y,
glyphEnd - glyphStart,
&glyphIndices_[glyphStart],
&justifiedAdvances[glyphStart - justificationGlyphStart],
&glyphOffsets_[glyphStart],
fontFace_,
fontEmSize_,
run.bidiLevel,
run.isSideways
);
if (FAILED(hr))
break;
x += runWidth;
}
}
return hr;
}
void FlowLayout::ProduceBidiOrdering(
UINT32 spanStart,
UINT32 spanCount,
OUT UINT32* spanIndices // [spanCount]
) const throw()
{
// Produces an index mapping from sequential order to visual bidi order.
// The function progresses forward, checking the bidi level of each
// pair of spans, reversing when needed.
//
// See the Unicode technical report 9 for an explanation.
// http://www.unicode.org/reports/tr9/tr9-17.html
// Fill all entries with initial indices
for (UINT32 i = 0; i < spanCount; ++i)
{
spanIndices[i] = spanStart + i;
}
if (spanCount <= 1)
return;
size_t runStart = 0;
UINT32 currentLevel = runs_[spanStart].bidiLevel;
// Rearrange each run to produced reordered spans.
for (size_t i = 0; i < spanCount; ++i )
{
size_t runEnd = i + 1;
UINT32 nextLevel = (runEnd < spanCount)
? runs_[spanIndices[runEnd]].bidiLevel
: 0; // past last element
// We only care about transitions, particularly high to low,
// because that means we have a run behind us where we can
// do something.
if (currentLevel <= nextLevel) // This is now the beginning of the next run.
{
if (currentLevel < nextLevel)
{
currentLevel = nextLevel;
runStart = i + 1;
}
continue; // Skip past equal levels, or increasing stairsteps.
}
do // currentLevel > nextLevel
{
// Recede to find start of the run and previous level.
UINT32 previousLevel;
for (;;)
{
if (runStart <= 0) // reached front of index list
{
previousLevel = 0; // position before string has bidi level of 0
break;
}
if (runs_[spanIndices[--runStart]].bidiLevel < currentLevel)
{
previousLevel = runs_[spanIndices[runStart]].bidiLevel;
++runStart; // compensate for going one element past
break;
}
}
// Reverse the indices, if the difference between the current and
// next/previous levels is odd. Otherwise, it would be a no-op, so
// don't bother.
if ((std::min(currentLevel - nextLevel, currentLevel - previousLevel) & 1) != 0)
{
std::reverse(spanIndices + runStart, spanIndices + runEnd);
}
// Descend to the next lower level, the greater of previous and next
currentLevel = std::max(previousLevel, nextLevel);
}
while (currentLevel > nextLevel); // Continue until completely flattened.
}
}
STDMETHODIMP FlowLayout::ProduceJustifiedAdvances(
const FlowLayoutSource::RectF& rect,
const ClusterPosition& clusterStart,
const ClusterPosition& clusterEnd,
OUT std::vector<float>& justifiedAdvances
) const throw()
{
// Performs simple inter-word justification
// using the breakpoint analysis whitespace property.
// Copy out default, unjustified advances.
UINT32 glyphStart = GetClusterGlyphStart(clusterStart);
UINT32 glyphEnd = GetClusterGlyphStart(clusterEnd);
try
{
justifiedAdvances.assign(glyphAdvances_.begin() + glyphStart, glyphAdvances_.begin() + glyphEnd);
}
catch (...)
{
return ExceptionToHResult();
}
if (glyphEnd - glyphStart == 0)
return S_OK; // No glyphs to modify.
float maxWidth = rect.right - rect.left;
if (maxWidth <= 0)
return S_OK; // Text can't fit anyway.
////////////////////////////////////////
// First, count how many spaces there are in the text range.
ClusterPosition cluster(clusterStart);
UINT32 whitespaceCount = 0;
while (cluster.textPosition < clusterEnd.textPosition)
{
if (breakpoints_[cluster.textPosition].isWhitespace)
++whitespaceCount;
AdvanceClusterPosition(cluster);
}
if (whitespaceCount <= 0)
return S_OK; // Can't justify using spaces, since none exist.
////////////////////////////////////////
// Second, determine the needed contribution to each space.
float lineWidth = GetClusterRangeWidth(glyphStart, glyphEnd, &glyphAdvances_.front());
float justificationPerSpace = (maxWidth - lineWidth) / whitespaceCount;
if (justificationPerSpace <= 0)
return S_OK; // Either already justified or would be negative justification.
if (justificationPerSpace > maxSpaceWidth_)
return S_OK; // Avoid justification if it would space the line out awkwardly far.
////////////////////////////////////////
// Lastly, adjust the advance widths, adding the difference to each space character.
cluster = clusterStart;
while (cluster.textPosition < clusterEnd.textPosition)
{
if (breakpoints_[cluster.textPosition].isWhitespace)
justifiedAdvances[GetClusterGlyphStart(cluster) - glyphStart] += justificationPerSpace;
AdvanceClusterPosition(cluster);
}
return S_OK;
}
////////////////////////////////////////////////////////////////////////////////
// Text/cluster navigation.
//
// Since layout should never split text clusters, we want to move ahead whole
// clusters at a time.
void FlowLayout::SetClusterPosition(
IN OUT ClusterPosition& cluster,
UINT32 textPosition
) const throw()
{
// Updates the current position and seeks its matching text analysis run.
cluster.textPosition = textPosition;
// If the new text position is outside the previous analysis run,
// find the right one.
if (textPosition >= cluster.runEndPosition
|| !runs_[cluster.runIndex].ContainsTextPosition(textPosition))
{
// If we can resume the search from the previous run index,
// (meaning the new text position comes after the previously
// seeked one), we can save some time. Otherwise restart from
// the beginning.
UINT32 newRunIndex = 0;
if (textPosition >= runs_[cluster.runIndex].textStart)
{
newRunIndex = cluster.runIndex;
}
// Find new run that contains the text position.
newRunIndex = static_cast<UINT32>(
std::find(runs_.begin() + newRunIndex, runs_.end(), textPosition)
- runs_.begin()
);
// Keep run index within the list, rather than pointing off the end.
if (newRunIndex >= runs_.size())
{
newRunIndex = static_cast<UINT32>(runs_.size() - 1);
}
// Cache the position of the next analysis run to efficiently
// move forward in the clustermap.
const TextAnalysis::Run& matchingRun = runs_[newRunIndex];
cluster.runIndex = newRunIndex;
cluster.runEndPosition = matchingRun.textStart + matchingRun.textLength;
}
}
void FlowLayout::AdvanceClusterPosition(
IN OUT ClusterPosition& cluster
) const throw()
{
// Looks forward in the cluster map until finding a new cluster,
// or until we reach the end of a cluster run returned by shaping.
//
// Glyph shaping can produce a clustermap where a:
// - A single codepoint maps to a single glyph (simple Latin and precomposed CJK)
// - A single codepoint to several glyphs (diacritics decomposed into distinct glyphs)
// - Multiple codepoints are coalesced into a single glyph.
//
UINT32 textPosition = cluster.textPosition;
UINT32 clusterId = glyphClusters_[textPosition];
for (++textPosition; textPosition < cluster.runEndPosition; ++textPosition)
{
if (glyphClusters_[textPosition] != clusterId)
{
// Now pointing to the next cluster.
cluster.textPosition = textPosition;
return;
}
}
if (textPosition == cluster.runEndPosition)
{
// We crossed a text analysis run.
SetClusterPosition(cluster, textPosition);
}
}
UINT32 FlowLayout::GetClusterGlyphStart(const ClusterPosition& cluster) const throw()
{
// Maps from text position to corresponding starting index in the glyph array.
// This is needed because there isn't a 1:1 correspondence between text and
// glyphs produced.
UINT32 glyphStart = runs_[cluster.runIndex].glyphStart;
return (cluster.textPosition < glyphClusters_.size())
? glyphStart + glyphClusters_[cluster.textPosition]
: glyphStart + runs_[cluster.runIndex].glyphCount;
}
float FlowLayout::GetClusterRangeWidth(
const ClusterPosition& clusterStart,
const ClusterPosition& clusterEnd
) const throw()
{
// Sums the glyph advances between two cluster positions,
// useful for determining how long a line or word is.
return GetClusterRangeWidth(
GetClusterGlyphStart(clusterStart),
GetClusterGlyphStart(clusterEnd),
&glyphAdvances_.front()
);
}
float FlowLayout::GetClusterRangeWidth(
UINT32 glyphStart,
UINT32 glyphEnd,
const float* glyphAdvances // [glyphEnd]
) const throw()
{
// Sums the glyph advances between two glyph offsets, given an explicit
// advances array - useful for determining how long a line or word is.
return std::accumulate(glyphAdvances + glyphStart, glyphAdvances + glyphEnd, 0.0f);
}