/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=4 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS staticvoid AccountStorageForTextRun(gfxTextRun* aTextRun, int32_t aSign) { // Ignores detailed glyphs... we don't know when those have been constructed // Also ignores gfxSkipChars dynamic storage (which won't be anything // for preformatted text) // Also ignores GlyphRun array, again because it hasn't been constructed // by the time this gets called. If there's only one glyphrun that's stored // directly in the textrun anyway so no additional overhead.
uint32_t length = aTextRun->GetLength();
int32_t bytes = length * sizeof(gfxTextRun::CompressedGlyph);
bytes += sizeof(gfxTextRun);
gTextRunStorage += bytes * aSign;
gTextRunStorageHighWaterMark =
std::max(gTextRunStorageHighWaterMark, gTextRunStorage);
} #endif
bool gfxTextRun::NeedsGlyphExtents() const { if (GetFlags() & gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX) { returntrue;
} for (constauto& run : mGlyphRuns) { if (run.mFont->GetFontEntry()->IsUserFont()) { returntrue;
}
} returnfalse;
}
// Helper for textRun creation to preallocate storage for glyph records; // this function returns a pointer to the newly-allocated glyph storage. // Returns nullptr if allocation fails. void* gfxTextRun::AllocateStorageForTextRun(size_t aSize, uint32_t aLength) { // Allocate the storage we need, returning nullptr on failure rather than // throwing an exception (because web content can create huge runs). void* storage = malloc(aSize + aLength * sizeof(CompressedGlyph)); if (!storage) {
NS_WARNING("failed to allocate storage for text run!"); return nullptr;
}
// Initialize the glyph storage (beyond aSize) to zero
memset(reinterpret_cast<char*>(storage) + aSize, 0,
aLength * sizeof(CompressedGlyph));
gfxTextRun::~gfxTextRun() { #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
AccountStorageForTextRun(this, -1); #endif #ifdef DEBUG // Make it easy to detect a dead text run
mFlags = ~gfx::ShapedTextFlags();
mFlags2 = ~nsTextFrameUtils::Flags(); #endif
// The cached ellipsis textrun (if any) in a fontgroup will have already // been told to release its reference to the group, so we mustn't do that // again here. if (!mReleasedFontGroup) { #ifndef RELEASE_OR_BETA
gfxTextPerfMetrics* tp = mFontGroup->GetTextPerfMetrics(); if (tp) {
tp->current.textrunDestr++;
} #endif
NS_RELEASE(mFontGroup);
}
}
// After dropping our reference to the font group, we'll no longer be able // to get up-to-date results for ShouldSkipDrawing(). Store the current // value in mReleasedFontGroupSkippedDrawing. // // (It doesn't actually matter that we can't get up-to-date results for // ShouldSkipDrawing(), since the only text runs that we call // ReleaseFontGroup() for are ellipsis text runs, and we ask the font // group for a new ellipsis text run each time we want to draw one, // and ensure that the cached one is cleared in ClearCachedData() when // font loading status changes.)
mReleasedFontGroupSkippedDrawing = mFontGroup->ShouldSkipDrawing();
uint32_t changed = 0;
CompressedGlyph* cg = mCharacterGlyphs + aRange.start; const CompressedGlyph* const end = cg + aRange.Length(); while (cg < end) {
uint8_t canBreak = *aBreakBefore++; if (canBreak && !cg->IsClusterStart()) { // XXX If we replace the line-breaker with one based more closely // on UAX#14 (e.g. using ICU), this may not be needed any more. // Avoid possible breaks inside a cluster, EXCEPT when the previous // character was a space (compare UAX#14 rules LB9, LB10). if (cg == mCharacterGlyphs || !(cg - 1)->CharIsSpace()) {
canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE;
}
} // If a break is allowed here, set the break flag, but don't clear a // possible pre-existing emergency-break flag already in the run. if (canBreak) {
changed |= cg->SetCanBreakBefore(canBreak);
}
++cg;
} return changed != 0;
}
gfxTextRun::LigatureData gfxTextRun::ComputeLigatureData(
Range aPartRange, const PropertyProvider* aProvider) const {
NS_ASSERTION(aPartRange.start < aPartRange.end, "Computing ligature data for empty range");
NS_ASSERTION(aPartRange.end <= GetLength(), "Character length overflow");
uint32_t i; for (i = aPartRange.start; !charGlyphs[i].IsLigatureGroupStart(); --i) {
NS_ASSERTION(i > 0, "Ligature at the start of the run??");
}
result.mRange.start = i; for (i = aPartRange.start + 1;
i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) {
}
result.mRange.end = i;
int32_t ligatureWidth = GetAdvanceForGlyphs(result.mRange); // Count the number of started clusters we have seen
uint32_t totalClusterCount = 0;
uint32_t partClusterIndex = 0;
uint32_t partClusterCount = 0; for (i = result.mRange.start; i < result.mRange.end; ++i) { // Treat the first character of the ligature as the start of a // cluster for our purposes of allocating ligature width to its // characters. if (i == result.mRange.start || charGlyphs[i].IsClusterStart()) {
++totalClusterCount; if (i < aPartRange.start) {
++partClusterIndex;
} elseif (i < aPartRange.end) {
++partClusterCount;
}
}
}
NS_ASSERTION(totalClusterCount > 0, "Ligature involving no clusters??");
result.mPartAdvance = partClusterIndex * (ligatureWidth / totalClusterCount);
result.mPartWidth = partClusterCount * (ligatureWidth / totalClusterCount);
// Any rounding errors are apportioned to the final part of the ligature, // so that measuring all parts of a ligature and summing them is equal to // the ligature width. if (aPartRange.end == result.mRange.end) {
gfxFloat allParts = totalClusterCount * (ligatureWidth / totalClusterCount);
result.mPartWidth += ligatureWidth - allParts;
}
if (partClusterCount == 0) { // nothing to draw
result.mClipBeforePart = result.mClipAfterPart = true;
} else { // Determine whether we should clip before or after this part when // drawing its slice of the ligature. // We need to clip before the part if any cluster is drawn before // this part.
result.mClipBeforePart = partClusterIndex > 0; // We need to clip after the part if any cluster is drawn after // this part.
result.mClipAfterPart =
partClusterIndex + partClusterCount < totalClusterCount;
}
// Draw partial ligature. We hack this by clipping the ligature.
LigatureData data = ComputeLigatureData(aRange, aProvider);
gfxRect clipExtents = aParams.context->GetClipExtents();
gfxFloat start, end; if (aParams.isVerticalRun) {
start = clipExtents.Y() * mAppUnitsPerDevUnit;
end = clipExtents.YMost() * mAppUnitsPerDevUnit;
ClipPartialLigature(this, &start, &end, aPt->y, &data);
} else {
start = clipExtents.X() * mAppUnitsPerDevUnit;
end = clipExtents.XMost() * mAppUnitsPerDevUnit;
ClipPartialLigature(this, &start, &end, aPt->x, &data);
}
gfxClipAutoSaveRestore autoSaveClip(aParams.context);
{ // use division here to ensure that when the rect is aligned on multiples // of mAppUnitsPerDevUnit, we clip to true device unit boundaries. // Also, make sure we snap the rectangle to device pixels.
Rect clipRect =
aParams.isVerticalRun
? Rect(clipExtents.X(), start / mAppUnitsPerDevUnit,
clipExtents.Width(), (end - start) / mAppUnitsPerDevUnit)
: Rect(start / mAppUnitsPerDevUnit, clipExtents.Y(),
(end - start) / mAppUnitsPerDevUnit, clipExtents.Height());
MaybeSnapToDevicePixels(clipRect, *aParams.dt, true);
// Returns true if the font has synthetic bolding enabled, // or is a color font (COLR/SVG/sbix/CBDT), false otherwise. This is used to // check whether the text run needs to be explicitly composited in order to // support opacity. staticbool HasSyntheticBoldOrColor(gfxFont* aFont) { if (aFont->ApplySyntheticBold()) { returntrue;
}
gfxFontEntry* fe = aFont->GetFontEntry(); if (fe->TryGetSVGData(aFont) || fe->TryGetColorGlyphs()) { returntrue;
} #ifdefined(XP_MACOSX) // sbix fonts only supported via Core Text if (fe->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) { returntrue;
} #endif returnfalse;
}
// helper class for double-buffering drawing with non-opaque color struct MOZ_STACK_CLASS BufferAlphaColor { explicit BufferAlphaColor(gfxContext* aContext) : mContext(aContext) {}
void PopAlpha() { // pop the text, using the color alpha as the opacity
mContext->PopGroupAndBlend();
mContext->Restore();
}
gfxContext* mContext;
};
void gfxTextRun::Draw(const Range aRange, const gfx::Point aPt, const DrawParams& aParams) const {
NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH ||
!(aParams.drawMode & DrawMode::GLYPH_PATH), "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or " "GLYPH_STROKE_UNDERNEATH");
NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || !aParams.callbacks, "callback must not be specified unless using GLYPH_PATH");
if (skipDrawing) { // We don't need to draw anything; // but if the caller wants advance width, we need to compute it here if (aParams.advanceWidth) {
gfxTextRun::Metrics metrics =
MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS,
aParams.context->GetDrawTarget(), aParams.provider);
*aParams.advanceWidth = metrics.mAdvanceWidth * direction;
}
// return without drawing return;
}
// synthetic bolding draws glyphs twice ==> colors with opacity won't draw // correctly unless first drawn without alpha
BufferAlphaColor syntheticBoldBuffer(aParams.context);
DeviceColor currentColor; bool mayNeedBuffering =
aParams.drawMode & DrawMode::GLYPH_FILL &&
aParams.context->HasNonOpaqueNonTransparentColor(currentColor) &&
!textDrawer;
// If we need to double-buffer, we'll need to measure the text first to // get the bounds of the area of interest. Ideally we'd do that just for // the specific glyph run(s) that need buffering, but because of bug // 1612610 we currently use the extent of the entire range even when // just buffering a subrange. So we'll measure the full range once and // keep the metrics on hand for any subsequent subranges.
gfxTextRun::Metrics metrics; bool gotMetrics = false;
// Set up parameters that will be constant across all glyph runs we need // to draw, regardless of the font used.
TextRunDrawParams params(aParams.paletteCache);
params.context = aParams.context;
params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit());
params.isVerticalRun = IsVertical();
params.isRTL = IsRightToLeft();
params.direction = direction;
params.strokeOpts = aParams.strokeOpts;
params.textStrokeColor = aParams.textStrokeColor;
params.fontPalette = aParams.fontPalette;
params.textStrokePattern = aParams.textStrokePattern;
params.drawOpts = aParams.drawOpts;
params.drawMode = aParams.drawMode;
params.hasTextShadow = aParams.hasTextShadow;
params.callbacks = aParams.callbacks;
params.runContextPaint = aParams.contextPaint;
params.paintSVGGlyphs =
!aParams.callbacks || aParams.callbacks->mShouldPaintSVGGlyphs;
params.dt = aParams.context->GetDrawTarget();
params.textDrawer = textDrawer; if (textDrawer) {
params.clipRect = textDrawer->GeckoClipRect();
}
params.allowGDI = aParams.allowGDI;
gfxFloat advance = 0.0;
gfx::Point pt = aPt;
for (GlyphRunIterator iter(this, aRange); !iter.AtEnd(); iter.NextRun()) {
gfxFont* font = iter.GlyphRun()->mFont;
Range runRange(iter.StringStart(), iter.StringEnd());
bool needToRestore = false; if (mayNeedBuffering && HasSyntheticBoldOrColor(font)) {
needToRestore = true; if (!gotMetrics) { // Measure text; use the bounding box to determine the area we need // to buffer. We measure the entire range, rather than just the glyph // run that we're actually handling, because of bug 1612610: if the // bounding box passed to PushSolidColor does not intersect the // drawTarget's current clip, the skia backend fails to clip properly. // This means we may use a larger buffer than actually needed, but is // otherwise harmless.
metrics = MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS, params.dt,
aParams.provider); if (IsRightToLeft()) {
metrics.mBoundingBox.MoveBy(
gfxPoint(aPt.x - metrics.mAdvanceWidth, aPt.y));
} else {
metrics.mBoundingBox.MoveBy(gfxPoint(aPt.x, aPt.y));
}
gotMetrics = true;
}
syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor,
GetAppUnitsPerDevUnit());
}
Range ligatureRange(runRange); bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange);
// Measure partial ligature. We hack this by clipping the metrics in the // same way we clip the drawing.
LigatureData data = ComputeLigatureData(aRange, aProvider);
// First measure the complete ligature
Metrics metrics;
AccumulateMetricsForRun(aFont, data.mRange, aBoundingBoxType, aRefDrawTarget,
aProvider, aRange, aOrientation, &metrics);
// Clip the bounding box to the ligature part
gfxFloat bboxLeft = metrics.mBoundingBox.X();
gfxFloat bboxRight = metrics.mBoundingBox.XMost(); // Where we are going to start "drawing" relative to our left baseline origin
gfxFloat origin =
IsRightToLeft() ? metrics.mAdvanceWidth - data.mPartAdvance : 0;
ClipPartialLigature(this, &bboxLeft, &bboxRight, origin, &data);
metrics.mBoundingBox.SetBoxX(bboxLeft, bboxRight);
// mBoundingBox is now relative to the left baseline origin for the entire // ligature. Shift it left.
metrics.mBoundingBox.MoveByX(
-(IsRightToLeft()
? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth)
: data.mPartAdvance));
metrics.mAdvanceWidth = data.mPartWidth;
// XXX This sucks. We have to get glyph extents just so we can detect // glyphs outside the font box, even when aBoundingBoxType is LOOSE, // even though in almost all cases we could get correct results just // by getting some ascent/descent from the font and using our stored // advance widths.
AccumulateMetricsForRun(font, ligatureRange, aBoundingBoxType,
aRefDrawTarget, aProvider, ligatureRange,
iter.GlyphRun()->mOrientation, &accumulatedMetrics);
for (uint32_t i = start; i < aRange.end; ++i) { if (aHyphenBuffer[i - aStart] == HyphenType::Explicit &&
!aWordState->hasExplicitHyphen) {
aWordState->hasExplicitHyphen = true;
} if (!aWordState->hasManualHyphen &&
(aHyphenBuffer[i - aStart] == HyphenType::Soft ||
aHyphenBuffer[i - aStart] == HyphenType::Explicit)) {
aWordState->hasManualHyphen = true; // This is the first manual hyphen in the current word. We can only // know if the current word has a manual hyphen until now. So, we need // to run a sub loop to update the auto hyphens between the start of // the current word and this manual hyphen. if (aWordState->hasAutoHyphen) { for (uint32_t j = aWordState->mostRecentBoundary; j < i; j++) { if (aHyphenBuffer[j - aStart] ==
HyphenType::AutoWithoutManualInSameWord) {
aHyphenBuffer[j - aStart] = HyphenType::AutoWithManualInSameWord;
}
}
}
} if (aHyphenBuffer[i - aStart] == HyphenType::AutoWithoutManualInSameWord) { if (!aWordState->hasAutoHyphen) {
aWordState->hasAutoHyphen = true;
} if (aWordState->hasManualHyphen) {
aHyphenBuffer[i - aStart] = HyphenType::AutoWithManualInSameWord;
}
}
// If we're at the word boundary, clear/reset couple states. if (mCharacterGlyphs[i].CharIsSpace() || mCharacterGlyphs[i].CharIsTab() ||
mCharacterGlyphs[i].CharIsNewline() || // Since we will not have a boundary in the end of the string, let's // call the end of the string a special case for word boundary.
i == GetLength() - 1) { // We can only get to know whether we should raise/clear an explicit // manual hyphen until we get to the end of a word, because this depends // on whether there exists at least one auto hyphen in the same word. if (!aWordState->hasAutoHyphen && aWordState->hasExplicitHyphen) { for (uint32_t j = aWordState->mostRecentBoundary; j <= i; j++) { if (aHyphenBuffer[j - aStart] == HyphenType::Explicit) {
aHyphenBuffer[j - aStart] = HyphenType::None;
}
}
}
aWordState->mostRecentBoundary = i;
aWordState->hasManualHyphen = false;
aWordState->hasAutoHyphen = false;
aWordState->hasExplicitHyphen = false;
}
}
}
gfxFloat width = 0;
gfxFloat advance = 0; // The number of space characters that can be trimmed or hang at a soft-wrap
uint32_t trimmableChars = 0; // The amount of space removed by ignoring trimmableChars
gfxFloat trimmableAdvance = 0;
int32_t lastBreak = -1;
int32_t lastBreakTrimmableChars = -1;
gfxFloat lastBreakTrimmableAdvance = -1; // Cache the last candidate break
int32_t lastCandidateBreak = -1;
int32_t lastCandidateBreakTrimmableChars = -1;
gfxFloat lastCandidateBreakTrimmableAdvance = -1; bool lastCandidateBreakUsedHyphenation = false;
gfxBreakPriority lastCandidateBreakPriority = gfxBreakPriority::eNoBreak; bool aborted = false;
uint32_t end = aStart + aMaxLength; bool lastBreakUsedHyphenation = false;
Range ligatureRange(aStart, end);
ShrinkToLigatureBoundaries(&ligatureRange);
// We may need to move `i` backwards in the following loop, and re-scan // part of the textrun; we'll use `rescanLimit` so we can tell when that // is happening: if `i < rescanLimit` then we're rescanning.
uint32_t rescanLimit = aStart; for (uint32_t i = aStart; i < end; ++i) { if (i >= bufferRange.end) { // Fetch more spacing and hyphenation data
uint32_t oldHyphenBufferLength = hyphenBuffer.Length();
bufferRange.start = i;
bufferRange.end =
std::min(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE); // For spacing, we always overwrite the old data with the newly // fetched one. However, for hyphenation, hyphenation data sometimes // depends on the context in every word (if "hyphens: auto" is set). // To ensure we get enough information between neighboring buffers, // we grow the hyphenBuffer instead of overwrite it. // NOTE that this means bufferRange does not correspond to the // entire hyphenBuffer, but only to the most recently added portion. // Therefore, we need to add the old length to hyphenBuffer.Elements() // when getting more data. if (haveSpacing) {
GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
} if (haveHyphenation) { if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) {
aProvider.GetHyphenationBreaks(
bufferRange, hyphenBuffer.Elements() + oldHyphenBufferLength); if (aProvider.GetHyphensOption() == StyleHyphens::Auto) {
uint32_t prevMostRecentWordBoundary = wordState.mostRecentBoundary;
ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer,
&wordState); // If the buffer boundary is in the middle of a word, // we need to go back to the start of the current word. // So, we can correct the wrong candidates that we set // in the previous runs of the loop. if (prevMostRecentWordBoundary < oldHyphenBufferLength) {
rescanLimit = i;
i = prevMostRecentWordBoundary - 1; continue;
}
}
} else {
haveHyphenation = false;
}
}
}
// There can't be a word-wrap break opportunity at the beginning of the // line: if the width is too small for even one character to fit, it // could be the first and last break opportunity on the line, and that // would trigger an infinite loop. if (aSuppressBreak != eSuppressAllBreaks &&
(aSuppressBreak != eSuppressInitialBreak || i > aStart)) { bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() ==
CompressedGlyph::FLAG_BREAK_TYPE_NORMAL; // atHyphenationBreak indicates we're at a "soft" hyphen, where an extra // hyphen glyph will need to be painted. It is NOT set for breaks at an // explicit hyphen present in the text. // // NOTE(emilio): If you change this condition you also need to change // nsTextFrame::AddInlineMinISizeForFlow to match. bool atHyphenationBreak = !atNaturalBreak && haveHyphenation &&
(!aLineBreakBefore || i > aStart) &&
IsOptionalHyphenBreak(hyphenBuffer[i - aStart]); bool atAutoHyphenWithManualHyphenInSameWord =
atHyphenationBreak &&
hyphenBuffer[i - aStart] == HyphenType::AutoWithManualInSameWord; bool atBreak = atNaturalBreak || atHyphenationBreak; bool wordWrapping =
(aCanWordWrap ||
(aCanWhitespaceWrap &&
mCharacterGlyphs[i].CanBreakBefore() ==
CompressedGlyph::FLAG_BREAK_TYPE_EMERGENCY_WRAP)) &&
mCharacterGlyphs[i].IsClusterStart() &&
aBreakPriority <= gfxBreakPriority::eWordWrapBreak;
bool whitespaceWrapping = false; if (i > aStart) { // The spec says the breaking opportunity is *after* whitespace. autoconst& g = mCharacterGlyphs[i - 1];
whitespaceWrapping =
aIsBreakSpaces &&
(g.CharIsSpace() || g.CharIsTab() || g.CharIsNewline());
}
if (atBreak || wordWrapping || whitespaceWrapping) {
gfxFloat hyphenatedAdvance = advance; if (atHyphenationBreak) {
hyphenatedAdvance += aProvider.GetHyphenWidth();
}
width += advance;
advance = 0; if (width - trimmableAdvance > aWidth) { // No more text fits. Abort
aborted = true; break;
} // There are various kinds of break opportunities: // 1. word wrap break, // 2. natural break, // 3. manual hyphenation break, // 4. auto hyphenation break without any manual hyphenation // in the same word, // 5. auto hyphenation break with another manual hyphenation // in the same word. // Allow all of them except the last one to be a candidate. // So, we can ensure that we don't use an automatic // hyphenation opportunity within a word that contains another // manual hyphenation, unless it is the only choice. if (wordWrapping || !atAutoHyphenWithManualHyphenInSameWord) {
lastCandidateBreak = lastBreak;
lastCandidateBreakTrimmableChars = lastBreakTrimmableChars;
lastCandidateBreakTrimmableAdvance = lastBreakTrimmableAdvance;
lastCandidateBreakUsedHyphenation = lastBreakUsedHyphenation;
lastCandidateBreakPriority = aBreakPriority;
}
}
}
// If we're re-scanning part of a word (to re-process potential // hyphenation types) then we don't want to accumulate widths again // for the characters that were already added to `advance`. if (i < rescanLimit) { continue;
}
gfxFloat charAdvance; if (i >= ligatureRange.start && i < ligatureRange.end) {
charAdvance = GetAdvanceForGlyphs(Range(i, i + 1)); if (haveSpacing) {
PropertyProvider::Spacing* space =
&spacingBuffer[i - bufferRange.start];
charAdvance += space->mBefore + space->mAfter;
}
} else {
charAdvance = ComputePartialLigatureWidth(Range(i, i + 1), &aProvider);
}
// There are three possibilities: // 1) all the text fit (width <= aWidth) // 2) some of the text fit up to a break opportunity (width > aWidth && // lastBreak >= 0) // 3) none of the text fits before a break opportunity (width > aWidth && // lastBreak < 0)
uint32_t charsFit;
aOutUsedHyphenation = false; if (width - trimmableAdvance <= aWidth) {
charsFit = aMaxLength;
} elseif (lastBreak >= 0) { if (lastCandidateBreak >= 0 && lastCandidateBreak != lastBreak) {
lastBreak = lastCandidateBreak;
lastBreakTrimmableChars = lastCandidateBreakTrimmableChars;
lastBreakTrimmableAdvance = lastCandidateBreakTrimmableAdvance;
lastBreakUsedHyphenation = lastCandidateBreakUsedHyphenation;
aBreakPriority = lastCandidateBreakPriority;
}
charsFit = lastBreak - aStart;
trimmableChars = lastBreakTrimmableChars;
trimmableAdvance = lastBreakTrimmableAdvance;
aOutUsedHyphenation = lastBreakUsedHyphenation;
} else {
charsFit = aMaxLength;
}
// Get the overall metrics of the range that fit (including any potentially // trimmable or hanging whitespace).
aOutMetrics = MeasureText(Range(aStart, aStart + charsFit), aBoundingBoxType,
aRefDrawTarget, &aProvider);
if (aOutTrimmableWhitespace) {
aOutTrimmableWhitespace->mAdvance = trimmableAdvance;
aOutTrimmableWhitespace->mCount = trimmableChars;
}
if (aSpacing) {
aSpacing->mBefore = aSpacing->mAfter = 0;
}
// Account for all remaining spacing here. This is more efficient than // processing it along with the glyphs. if (aProvider && (mFlags & gfx::ShapedTextFlags::TEXT_ENABLE_SPACING)) {
uint32_t i;
AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer; if (spacingBuffer.AppendElements(aRange.Length(), fallible)) {
GetAdjustedSpacing(this, ligatureRange, *aProvider,
spacingBuffer.Elements()); for (i = 0; i < ligatureRange.Length(); ++i) {
PropertyProvider::Spacing* space = &spacingBuffer[i];
result += space->mBefore + space->mAfter;
} if (aSpacing) {
aSpacing->mBefore = spacingBuffer[0].mBefore;
aSpacing->mAfter = spacingBuffer.LastElement().mAfter;
}
}
}
return result + GetAdvanceForGlyphs(ligatureRange);
}
gfxFloat gfxTextRun::GetMinAdvanceWidth(Range aRange) {
MOZ_ASSERT(aRange.end <= GetLength(), "Substring out of range");
Range ligatureRange = aRange; bool adjusted = ShrinkToLigatureBoundaries(&ligatureRange);
// Compute min advance width by assuming each grapheme cluster takes its own // line.
gfxFloat clusterAdvance = 0; for (uint32_t i = ligatureRange.start; i < ligatureRange.end; ++i) { if (mCharacterGlyphs[i].CharIsSpace()) { // Skip space char to prevent its advance width contributing to the // result. That is, don't consider a space can be in its own line. continue;
}
clusterAdvance += GetAdvanceForGlyph(i); if (i + 1 == ligatureRange.end || IsClusterStart(i + 1)) {
result = std::max(result, clusterAdvance);
clusterAdvance = 0;
}
}
return result;
}
bool gfxTextRun::SetLineBreaks(Range aRange, bool aLineBreakBefore, bool aLineBreakAfter,
gfxFloat* aAdvanceWidthDelta) { // Do nothing because our shaping does not currently take linebreaks into // account. There is no change in advance width. if (aAdvanceWidthDelta) {
*aAdvanceWidthDelta = 0;
} returnfalse;
}
const gfxTextRun::GlyphRun* gfxTextRun::FindFirstGlyphRunContaining(
uint32_t aOffset) const {
MOZ_ASSERT(aOffset <= GetLength(), "Bad offset looking for glyphrun");
MOZ_ASSERT(GetLength() == 0 || !mGlyphRuns.IsEmpty(), "non-empty text but no glyph runs present!"); if (mGlyphRuns.Length() <= 1) { return mGlyphRuns.begin();
} if (aOffset == GetLength()) { return mGlyphRuns.end() - 1;
} constauto* start = mGlyphRuns.begin(); constauto* limit = mGlyphRuns.end(); while (limit - start > 1) { constauto* mid = start + (limit - start) / 2; if (mid->mCharacterOffset <= aOffset) {
start = mid;
} else {
limit = mid;
}
}
MOZ_ASSERT(start->mCharacterOffset <= aOffset, "Hmm, something went wrong, aOffset should have been found"); return start;
}
void gfxTextRun::AddGlyphRun(gfxFont* aFont, FontMatchType aMatchType,
uint32_t aUTF16Offset, bool aForceNewRun,
gfx::ShapedTextFlags aOrientation, bool aIsCJK) {
MOZ_ASSERT(aFont, "adding glyph run for null font!");
MOZ_ASSERT(aOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED, "mixed orientation should have been resolved"); if (!aFont) { return;
}
MOZ_ASSERT(lastGlyphRun->mCharacterOffset <= aUTF16Offset, "Glyph runs out of order (and run not forced)");
// Don't append a run if the font is already the one we want if (lastGlyphRun->Matches(aFont, aOrientation, aIsCJK, aMatchType)) { return;
}
// If the offset has not changed, avoid leaving a zero-length run // by overwriting the last entry instead of appending... if (lastGlyphRun->mCharacterOffset == aUTF16Offset) { // ...except that if the run before the last entry had the same // font as the new one wants, merge with it instead of creating // adjacent runs with the same font if (numGlyphRuns > 1 && mGlyphRuns[numGlyphRuns - 2].Matches(
aFont, aOrientation, aIsCJK, aMatchType)) {
mGlyphRuns.TruncateLength(numGlyphRuns - 1); return;
}
void gfxTextRun::SanitizeGlyphRuns() { if (mGlyphRuns.Length() < 2) { return;
}
auto& runs = mGlyphRuns.Array();
// The runs are almost certain to be already sorted, so it's worth avoiding // the Sort() call if possible. bool isSorted = true;
uint32_t prevOffset = 0; for (constauto& r : runs) { if (r.mCharacterOffset < prevOffset) {
isSorted = false; break;
}
prevOffset = r.mCharacterOffset;
} if (!isSorted) {
runs.Sort(GlyphRunOffsetComparator());
}
// Coalesce adjacent glyph runs that have the same properties, and eliminate // any empty runs.
GlyphRun* prevRun = nullptr; const CompressedGlyph* charGlyphs = mCharacterGlyphs;
runs.RemoveElementsBy([&](GlyphRun& aRun) -> bool { // First run is always retained. if (!prevRun) {
prevRun = &aRun; returnfalse;
}
// Merge any run whose properties match its predecessor. if (prevRun->Matches(aRun.mFont, aRun.mOrientation, aRun.mIsCJK,
aRun.mMatchType)) { returntrue;
}
if (prevRun->mCharacterOffset >= aRun.mCharacterOffset) { // Preceding run is empty (or has become so due to the adjusting for // ligature boundaries), so we will overwrite it with this one, which // will then be discarded.
*prevRun = aRun; returntrue;
}
// If any glyph run starts with ligature-continuation characters, we need to // advance it to the first "real" character to avoid drawing partial // ligature glyphs from wrong font (seen with U+FEFF in reftest 474417-1, as // Core Text eliminates the glyph, which makes it appear as if a ligature // has been formed) while (charGlyphs[aRun.mCharacterOffset].IsLigatureContinuation() &&
aRun.mCharacterOffset < GetLength()) {
aRun.mCharacterOffset++;
}
// We're keeping another run, so update prevRun pointer to refer to it (in // its new position).
++prevRun; returnfalse;
});
MOZ_ASSERT(prevRun == &runs.LastElement(), "lost track of prevRun!");
// Drop any trailing empty run. if (runs.Length() > 1 && prevRun->mCharacterOffset == GetLength()) {
runs.RemoveLastElement();
}
MOZ_ASSERT(!runs.IsEmpty()); if (runs.Length() == 1) {
mGlyphRuns.ConvertToElement();
}
}
void gfxTextRun::CopyGlyphDataFrom(gfxShapedWord* aShapedWord,
uint32_t aOffset) {
uint32_t wordLen = aShapedWord->GetLength();
MOZ_ASSERT(aOffset + wordLen <= GetLength(), "word overruns end of textrun");
// These used to be NS_ASSERTION()s, but WARNING is more appropriate. // Although it's unusual (and not desirable), it's possible for us to assign // different fonts to a base character and a following diacritic. // Example on OSX 10.5/10.6 with default fonts installed: // data:text/html,<p style="font-family:helvetica, arial, sans-serif;"> // &%23x043E;&%23x0486;&%23x20;&%23x043E;&%23x0486; // This means the rendering of the cluster will probably not be very good, // but it's the best we can do for now if the specified font only covered // the initial base character and not its applied marks.
NS_WARNING_ASSERTION(aSource->IsClusterStart(start), "Started font run in the middle of a cluster");
NS_WARNING_ASSERTION(
end == aSource->GetLength() || aSource->IsClusterStart(end), "Ended font run in the middle of a cluster");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.