Files
palemoon27/layout/generic/nsTextRunTransformations.cpp
T
roytam1 f32bf3ebba import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1268976 - Assert that the micro-task queues are empty upon destruction of CycleCollectedJSRuntime; r=mccr8 (a459a4af91)
- Bug 1191354 part 1 - Clean up nsHTMLEditRules::GetHighestInlineParent; r=ehsan (a2f401887c)
- Bug 1191354 part 2 - Clean up nsHTMLEditRules::CheckInterlinePosition; r=ehsan (75d3829eec)
- Bug 1191354 part 3 - Clean up nsHTMLEditRules::InsertMozBRIfNeeded; r=ehsan (bedbba7c14)
- Bug 772796 - Handle newlines correctly when joining <div> and <pre>. r=roc (323eaf4fe9)
- Bug 1258085 - Avoid empty whitespace nodes when editing (b77825fb31)
- Bug 1156062 part 4 - Clean up ns*EditRules::WillInsert; r=ehsan (f351557776)
- Bug 1156062 part 5 - Make CreateBR return regular pointer; r=ehsan (8720917dee)
- Bug 1156062 part 6 - Clean up nsHTMLEditRules::WillInsertBreak; r=ehsan (d71bca568f)
- Bug 1156062 part 7 - Clean up nsHTMLEditRules::StandardBreakImpl; r=ehsan (625db32c02)
- Bug 1156062 part 8 - Clean up nsHTMLEditRules::JoinBlocks; r=ehsan (266b6c5150)
- Bug 1156062 part 9 - Clean up nsHTMLEditRules::WillAlign; r=masayuki (a6e3227001)
- Bug 1156062 part 10 - Clean up MarginPropertyAtomForIndent; r=masayuki (28612e062c)
- Bug 1184665 - Remove a spammy editor warning (a2db555918)
- Bug 1156062 part 11 - Clean up nsHTMLEditRules::Before/AfterEdit; r=masayuki (6be722082e)
- Bug 1156062 part 12 - Clean up nsHTMLEditRules::GetAlignment; r=masayuki (350be10ee8)
- Bug 1190172 part 1 - Clean up nsHTMLEditor::SplitStyleAbovePoint; r=ehsan (ce5e360af6)
- Bug 1145395 - Introduce an overload of nsHTMLEditor::SelElementPosition that takes an Element; r=ehsan (7ca9dfd2f8)
- Bug 1190172 part 2 - Clean up nsHTMLEditor::ClearStyle; r=ehsan (84334514ad)
- Bug 1190172 part 3 - Remove old method variants; r=ehsan (e042bd64d5)
- Bug 1190172 part 4 - Remove unused nsHTMLEditor::HasAttr; r=ehsan (a4f1563465)
- Bug 1190172 part 5 - Clean up nsHTMLEditor::NodeIsProperty; r=ehsan (98afb84bd4)
- Bug 1190172 part 6 - Clean up nsHTMLEditor::RelativeFontChangeOnTextNode; r=ehsan (0bf036d206)
- Bug 1190172 part 7 - Clean up nsHTMLEditor::PromoteInlineRange; r=ehsan (0a21347500)
- Bug 1190172 part 8 - Clean up nsHTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor; r=ehsan (78cf619036)
- Bug 1190172 part 9 - Clean up nsHTMLEditor::ReturnInHeader; r=ehsan (1d8d151ff1)
- Bug 1190172 part 10 - Clean up nsHTMLEditor::ReturnInListItem; r=ehsan (0ce94d3cb1)
- Bug 1190172 part 11 - Clean up nsHTMLEditRules::IsEmptyBlock; r=ehsan (a8dc5092f8)
- Bug 1190172 part 12 - Clean up nsHTMLEditRules::ExpandSelectionForDeletion; r=ehsan (3d4a87a56d)
- Bug 1191354 part 4 - Clean up nsHTMLEditRules::AlignBlock; r=ehsan (b39372d726)
- Bug 1191354 part 5 - Clean up nsHTMLEditRules::IsEmptyInline; r=ehsan (0b8ab19fd1)
- Bug 1191354 part 6 - Clean up IsBlockNode/IsInlineNode in nsHTMLEditRules.cpp; r=ehsan (8ba8047e12)
- Bug 1191354 part 7 - Clean up nsHTMLEditRules::CheckForInvisibleBR; r=ehsan (0bc61269bd)
- Bug 1191354 part 8 - Typedefs in headers for readability; r=ehsan (50b43dc3e9)
- Bug 1191354 part 9 - Remove old nsHTMLEditRules::ConvertListType variant; r=ehsan (871f1b30a5)
- Bug 1191354 part 10 - More features for OwningNonNull; r=froydnj (8e365d079a)
- Bug 1191354 part 11 - Clean up nsHTMLEditRules::WillOutdent; r=ehsan (0c59a81c10)
- Bug 1191354 part 12 - Clean up nsHTMLEditRules::OutdentPartOfBlock; r=ehsan (243d7ab42f)
- Bug 1191354 part 13 - Clean up nsHTMLEditRules::SplitBlock; r=ehsan (c1141ab04d)
- Bug 1191356 part 1 - Clean up nsHTMLEditRules::RemoveListStructure; r=ehsan (1d2ad5faa6)
- Bug 1191356 part 2 - Clean up nsHTMLEditor::RemoveBlockContainer; r=ehsan (a9d140f511)
- Bug 1191356 part 3 - Clean up nsHTMLEditRules::MoveBlock; r=ehsan (6bc14cd40c)
- Bug 1191356 part 4 - Clean up nsHTMLEditRules::MoveNodeSmart, MoveContents; r=ehsan (00e6536e47)
- Bug 1191356 part 5 - Clean up nsHTMLEditRules::RelativeChangeIndentationOfElementNode; r=ehsan (c3139c7d90)
- Bug 1191356 part 6 - Clean up nsHTMLEditRules::WillMakeBasicBlock; r=ehsan (b3f73c2e12)
- Bug 1191356 part 7 - Clean up nsHTMLEditor::GetSelectionContainer; r=ehsan (091e999587)
- Bug 1191356 part 8 - Clean up nsHTMLEditRules::WillAbsolutePosition; r=ehsan (9982c65a99)
- Bug 1208884 - Fix a few null check ordering bugs in nsHTMLEditRules::SplitParagraph; r=roc (d04eece297)
- Bug 1209037 - Eliminate some unneeded null checks; r=jdm (b35d5308c5)
- Bug 1191356 part 9 - Convert nsHTMLEditRules::mNewBlock to Element; r=ehsan (f374780e22)
- Bug 1253734 - Add some more editor things to CC traversal. r=mccr8. (94a0c77bb1)
- Bug 1193762 part 1 - Remove nsEditor::IsBlockNode(nsIDOMNode*); r=ehsan (5e107cbdbb)
- Bug 1193762 part 2 - Remove nsHTMLEditor::IsVisBreak(nsIDOMNode*); r=ehsan (5e61c27d21)
- Bug 1193762 part 3 - Remove nsEditor::IsDescendantOfEditorRoot(nsIDOMNode*); r=ehsan (022a37d442)
- Bug 1193762 part 4 - Avoid nsCOMPtr in ternary operator; r=froydnj (e4bc35cbe0)
- Bug 1193762 part 5 - Convert DecodePool::threads to nsTArray; r=froydnj (c61f063f4d)
- Bug 1193762 part 6 - Return raw pointer, not nsCOMPtr; r=froydnj (a43c30f92e)
- Bug 1193762 part 7 - Use .get() when assigning to variables; r=froydnj (a61ad819f0)
- Bug 1193762 part 8 - Fix things that will break; r=froydnj (40a76db662)
- Bug 1193762 part 9 - Delete nsCOMPtr<T>::operator T*()&&; r=froydnj (022c1f0dba)
- Bug 1168223 - Fix a crash caused by unexpected flushes under nsIEditor::SetFlags. r=ehsan (cc49d468b8)
- Bug 1158837 - Ensure the start and end nodes are non-null before using them. r=roc (7c28121aa7)
- Bug 1269047 - Remove chained ENSURE calls from GetStartNodeAndOffset. r=ehsan (f36c1c67aa)
- Bug 1248078 - Remove the dedicated scroll acknowledgement message. r=botond (500b8d7574)
- Bug 1260806: Remove some more dead MessageLoop code. r=jld (1c223cafe8)
- Bug 1266595: Replace Chromium Task with Runnable. r=froydnj (5c1c056c83)
- Bug 1242343 - p1. ConstructSystem32Path from LoadLibrarySystem32 - r=jimm (b28fcbc0c4)
- Bug 1242343 - p2. Blacklist msmpeg2vdec.dll 12.0.9200.16426 & .17037 - r=cpearce (9a71795ab0)
- Bug 1253395 - Disable msmpeg2vdec.dll blackslisting - r=cpearce (e1d4dc47be)
- Bug 1242456 - Create RAIIs to manage HGLOBAL and printer HANDLE in ShowNativePrintDialog and CreateGlobalDevModeAndInit. r=jimm, r=bobowen (a22cecaebd)
- Bug 1262399 - remove Impl suffixes from nsSupports* implementations; r=mccr8 (6733db9394)
- Bug 1268772 (part 1) - Remove nsCheapSet::Put()'s return value. r=erahm. (5f3f553e84)
- Bug 1268772 (part 2) - Make infallible nsVariant methods return |void| instead of |nsresult|. r=erahm. (22ae74fdaf)
- Bug 1268772 (part 3) - Remove NS_NewWindowsRegKey()'s return value. r=erahm. (dc32bd698e)
- Bug 1268772 (part 4) - Use MOZ_MUST_USE with NS_NewISupportsArray(). r=erahm. (e51ecfdf9f)
- Bug 1268772 (part 5) - Use MOZ_MUST_USE in other parts of xpcom/ds/. r=erahm. (4d8a5183eb)
- Bug 1266027 part 1 - make the MediaDecoderReaderWrapper as a proxy of requesting media data; r=jwwang (fbaa79019d)
- Bug 1195601 - Remove MediaDecoderStateMachine::mLogicallySeeking. r=kinetik. (5ab1ff1ca0)
- Bug 1266027 part 2 - make MDSM and SeekTask to adopt new MediaDecoderReaderWrapper API; r=jwwang (1961e26e80)
- add mac specific dupes (4e4ff4b123)
- Bug 1269262 - Unbreak build without NO_EXPAND_LIBS in js/src/moz.build after bug 1239083. r=ted (46d2dec64a)
- Bug 1253215 - Initialize RequestSyncService only if its pref is enabled, r=ehsan (aec1b2f889)
- Bug 1269303 - Remove RequestSync API, r=fabrice (f774336e34)
2024-08-26 16:01:17 +08:00

690 lines
25 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* 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/. */
#include "nsTextRunTransformations.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Move.h"
#include "nsGkAtoms.h"
#include "nsStyleConsts.h"
#include "nsUnicharUtils.h"
#include "nsUnicodeProperties.h"
#include "nsSpecialCasingData.h"
#include "mozilla/gfx/2D.h"
#include "nsTextFrameUtils.h"
#include "nsIPersistentProperties2.h"
#include "GreekCasing.h"
#include "IrishCasing.h"
using namespace mozilla;
// Unicode characters needing special casing treatment in tr/az languages
#define LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE 0x0130
#define LATIN_SMALL_LETTER_DOTLESS_I 0x0131
// Greek sigma needs custom handling for the lowercase transform; for details
// see comments under "case NS_STYLE_TEXT_TRANSFORM_LOWERCASE" within
// nsCaseTransformTextRunFactory::RebuildTextRun(), and bug 740120.
#define GREEK_CAPITAL_LETTER_SIGMA 0x03A3
#define GREEK_SMALL_LETTER_FINAL_SIGMA 0x03C2
#define GREEK_SMALL_LETTER_SIGMA 0x03C3
UniquePtr<nsTransformedTextRun>
nsTransformedTextRun::Create(const gfxTextRunFactory::Parameters* aParams,
nsTransformingTextRunFactory* aFactory,
gfxFontGroup* aFontGroup,
const char16_t* aString, uint32_t aLength,
const uint32_t aFlags,
nsTArray<RefPtr<nsTransformedCharStyle>>&& aStyles,
bool aOwnsFactory)
{
NS_ASSERTION(!(aFlags & gfxTextRunFactory::TEXT_IS_8BIT),
"didn't expect text to be marked as 8-bit here");
void *storage = AllocateStorageForTextRun(sizeof(nsTransformedTextRun), aLength);
if (!storage) {
return nullptr;
}
return UniquePtr<nsTransformedTextRun>(
new (storage) nsTransformedTextRun(aParams, aFactory, aFontGroup,
aString, aLength, aFlags,
Move(aStyles), aOwnsFactory));
}
void
nsTransformedTextRun::SetCapitalization(uint32_t aStart, uint32_t aLength,
bool* aCapitalization)
{
if (mCapitalize.IsEmpty()) {
if (!mCapitalize.AppendElements(GetLength()))
return;
memset(mCapitalize.Elements(), 0, GetLength()*sizeof(bool));
}
memcpy(mCapitalize.Elements() + aStart, aCapitalization, aLength*sizeof(bool));
mNeedsRebuild = true;
}
bool
nsTransformedTextRun::SetPotentialLineBreaks(Range aRange,
uint8_t* aBreakBefore)
{
bool changed =
gfxTextRun::SetPotentialLineBreaks(aRange, aBreakBefore);
if (changed) {
mNeedsRebuild = true;
}
return changed;
}
size_t
nsTransformedTextRun::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
{
size_t total = gfxTextRun::SizeOfExcludingThis(aMallocSizeOf);
total += mStyles.ShallowSizeOfExcludingThis(aMallocSizeOf);
total += mCapitalize.ShallowSizeOfExcludingThis(aMallocSizeOf);
if (mOwnsFactory) {
total += aMallocSizeOf(mFactory);
}
return total;
}
size_t
nsTransformedTextRun::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
{
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
UniquePtr<nsTransformedTextRun>
nsTransformingTextRunFactory::MakeTextRun(const char16_t* aString, uint32_t aLength,
const gfxTextRunFactory::Parameters* aParams,
gfxFontGroup* aFontGroup, uint32_t aFlags,
nsTArray<RefPtr<nsTransformedCharStyle>>&& aStyles,
bool aOwnsFactory)
{
return nsTransformedTextRun::Create(aParams, this, aFontGroup,
aString, aLength, aFlags, Move(aStyles),
aOwnsFactory);
}
UniquePtr<nsTransformedTextRun>
nsTransformingTextRunFactory::MakeTextRun(const uint8_t* aString, uint32_t aLength,
const gfxTextRunFactory::Parameters* aParams,
gfxFontGroup* aFontGroup, uint32_t aFlags,
nsTArray<RefPtr<nsTransformedCharStyle>>&& aStyles,
bool aOwnsFactory)
{
// We'll only have a Unicode code path to minimize the amount of code needed
// for these rarely used features
NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString), aLength);
return MakeTextRun(unicodeString.get(), aLength, aParams, aFontGroup,
aFlags & ~(gfxFontGroup::TEXT_IS_PERSISTENT | gfxFontGroup::TEXT_IS_8BIT),
Move(aStyles), aOwnsFactory);
}
void
MergeCharactersInTextRun(gfxTextRun* aDest, gfxTextRun* aSrc,
const bool* aCharsToMerge, const bool* aDeletedChars)
{
aDest->ResetGlyphRuns();
gfxTextRun::GlyphRunIterator iter(aSrc, gfxTextRun::Range(aSrc));
uint32_t offset = 0;
AutoTArray<gfxTextRun::DetailedGlyph,2> glyphs;
while (iter.NextRun()) {
gfxTextRun::GlyphRun* run = iter.GetGlyphRun();
nsresult rv = aDest->AddGlyphRun(run->mFont, run->mMatchType,
offset, false, run->mOrientation);
if (NS_FAILED(rv))
return;
bool anyMissing = false;
uint32_t mergeRunStart = iter.GetStringStart();
const gfxTextRun::CompressedGlyph *srcGlyphs = aSrc->GetCharacterGlyphs();
gfxTextRun::CompressedGlyph mergedGlyph = srcGlyphs[mergeRunStart];
uint32_t stringEnd = iter.GetStringEnd();
for (uint32_t k = iter.GetStringStart(); k < stringEnd; ++k) {
const gfxTextRun::CompressedGlyph g = srcGlyphs[k];
if (g.IsSimpleGlyph()) {
if (!anyMissing) {
gfxTextRun::DetailedGlyph details;
details.mGlyphID = g.GetSimpleGlyph();
details.mAdvance = g.GetSimpleAdvance();
details.mXOffset = 0;
details.mYOffset = 0;
glyphs.AppendElement(details);
}
} else {
if (g.IsMissing()) {
anyMissing = true;
glyphs.Clear();
}
if (g.GetGlyphCount() > 0) {
glyphs.AppendElements(aSrc->GetDetailedGlyphs(k), g.GetGlyphCount());
}
}
if (k + 1 < iter.GetStringEnd() && aCharsToMerge[k + 1]) {
// next char is supposed to merge with current, so loop without
// writing current merged glyph to the destination
continue;
}
// If the start of the merge run is actually a character that should
// have been merged with the previous character (this can happen
// if there's a font change in the middle of a case-mapped character,
// that decomposed into a sequence of base+diacritics, for example),
// just discard the entire merge run. See comment at start of this
// function.
NS_WARN_IF_FALSE(!aCharsToMerge[mergeRunStart],
"unable to merge across a glyph run boundary, "
"glyph(s) discarded");
if (!aCharsToMerge[mergeRunStart]) {
if (anyMissing) {
mergedGlyph.SetMissing(glyphs.Length());
} else {
mergedGlyph.SetComplex(mergedGlyph.IsClusterStart(),
mergedGlyph.IsLigatureGroupStart(),
glyphs.Length());
}
aDest->SetGlyphs(offset, mergedGlyph, glyphs.Elements());
++offset;
while (offset < aDest->GetLength() && aDeletedChars[offset]) {
aDest->SetGlyphs(offset++, gfxTextRun::CompressedGlyph(), nullptr);
}
}
glyphs.Clear();
anyMissing = false;
mergeRunStart = k + 1;
if (mergeRunStart < stringEnd) {
mergedGlyph = srcGlyphs[mergeRunStart];
}
}
NS_ASSERTION(glyphs.Length() == 0,
"Leftover glyphs, don't request merging of the last character with its next!");
}
NS_ASSERTION(offset == aDest->GetLength(), "Bad offset calculations");
}
gfxTextRunFactory::Parameters
GetParametersForInner(nsTransformedTextRun* aTextRun, uint32_t* aFlags,
DrawTarget* aRefDrawTarget)
{
gfxTextRunFactory::Parameters params =
{ aRefDrawTarget, nullptr, nullptr,
nullptr, 0, aTextRun->GetAppUnitsPerDevUnit()
};
*aFlags = aTextRun->GetFlags() & ~gfxFontGroup::TEXT_IS_PERSISTENT;
return params;
}
// Some languages have special casing conventions that differ from the
// default Unicode mappings.
// The enum values here are named for well-known exemplar languages that
// exhibit the behavior in question; multiple lang tags may map to the
// same setting here, if the behavior is shared by other languages.
enum LanguageSpecificCasingBehavior {
eLSCB_None, // default non-lang-specific behavior
eLSCB_Dutch, // treat "ij" digraph as a unit for capitalization
eLSCB_Greek, // strip accent when uppercasing Greek vowels
eLSCB_Irish, // keep prefix letters as lowercase when uppercasing Irish
eLSCB_Turkish // preserve dotted/dotless-i distinction in uppercase
};
static LanguageSpecificCasingBehavior
GetCasingFor(const nsIAtom* aLang)
{
if (!aLang) {
return eLSCB_None;
}
if (aLang == nsGkAtoms::tr ||
aLang == nsGkAtoms::az ||
aLang == nsGkAtoms::ba ||
aLang == nsGkAtoms::crh ||
aLang == nsGkAtoms::tt) {
return eLSCB_Turkish;
}
if (aLang == nsGkAtoms::nl) {
return eLSCB_Dutch;
}
if (aLang == nsGkAtoms::el) {
return eLSCB_Greek;
}
if (aLang == nsGkAtoms::ga) {
return eLSCB_Irish;
}
// Is there a region subtag we should ignore?
nsAtomString langStr(const_cast<nsIAtom*>(aLang));
int index = langStr.FindChar('-');
if (index > 0) {
langStr.Truncate(index);
nsCOMPtr<nsIAtom> truncatedLang = NS_Atomize(langStr);
return GetCasingFor(truncatedLang);
}
return eLSCB_None;
}
bool
nsCaseTransformTextRunFactory::TransformString(
const nsAString& aString,
nsString& aConvertedString,
bool aAllUppercase,
const nsIAtom* aLanguage,
nsTArray<bool>& aCharsToMergeArray,
nsTArray<bool>& aDeletedCharsArray,
nsTransformedTextRun* aTextRun,
nsTArray<uint8_t>* aCanBreakBeforeArray,
nsTArray<RefPtr<nsTransformedCharStyle>>* aStyleArray)
{
NS_PRECONDITION(!aTextRun || (aCanBreakBeforeArray && aStyleArray),
"either none or all three optional parameters required");
uint32_t length = aString.Length();
const char16_t* str = aString.BeginReading();
bool mergeNeeded = false;
bool capitalizeDutchIJ = false;
bool prevIsLetter = false;
bool ntPrefix = false; // true immediately after a word-initial 'n' or 't'
// when doing Irish lowercasing
uint32_t sigmaIndex = uint32_t(-1);
nsIUGenCategory::nsUGenCategory cat;
uint8_t style = aAllUppercase ? NS_STYLE_TEXT_TRANSFORM_UPPERCASE : 0;
bool forceNonFullWidth = false;
const nsIAtom* lang = aLanguage;
LanguageSpecificCasingBehavior languageSpecificCasing = GetCasingFor(lang);
mozilla::GreekCasing::State greekState;
mozilla::IrishCasing::State irishState;
uint32_t irishMark = uint32_t(-1); // location of possible prefix letter(s)
// in the output string
uint32_t irishMarkSrc = uint32_t(-1); // corresponding location in source
// string (may differ from output due to
// expansions like eszet -> 'SS')
for (uint32_t i = 0; i < length; ++i) {
uint32_t ch = str[i];
RefPtr<nsTransformedCharStyle> charStyle;
if (aTextRun) {
charStyle = aTextRun->mStyles[i];
style = aAllUppercase ? NS_STYLE_TEXT_TRANSFORM_UPPERCASE :
charStyle->mTextTransform;
forceNonFullWidth = charStyle->mForceNonFullWidth;
nsIAtom* newLang = charStyle->mExplicitLanguage
? charStyle->mLanguage.get() : nullptr;
if (lang != newLang) {
lang = newLang;
languageSpecificCasing = GetCasingFor(lang);
greekState.Reset();
irishState.Reset();
irishMark = uint32_t(-1);
irishMarkSrc = uint32_t(-1);
}
}
int extraChars = 0;
const mozilla::unicode::MultiCharMapping *mcm;
bool inhibitBreakBefore = false; // have we just deleted preceding hyphen?
if (NS_IS_HIGH_SURROGATE(ch) && i < length - 1 &&
NS_IS_LOW_SURROGATE(str[i + 1])) {
ch = SURROGATE_TO_UCS4(ch, str[i + 1]);
}
switch (style) {
case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
if (languageSpecificCasing == eLSCB_Turkish) {
if (ch == 'I') {
ch = LATIN_SMALL_LETTER_DOTLESS_I;
prevIsLetter = true;
sigmaIndex = uint32_t(-1);
break;
}
if (ch == LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE) {
ch = 'i';
prevIsLetter = true;
sigmaIndex = uint32_t(-1);
break;
}
}
cat = mozilla::unicode::GetGenCategory(ch);
if (languageSpecificCasing == eLSCB_Irish &&
cat == nsIUGenCategory::kLetter) {
// See bug 1018805 for Irish lowercasing requirements
if (!prevIsLetter && (ch == 'n' || ch == 't')) {
ntPrefix = true;
} else {
if (ntPrefix && mozilla::IrishCasing::IsUpperVowel(ch)) {
aConvertedString.Append('-');
++extraChars;
}
ntPrefix = false;
}
} else {
ntPrefix = false;
}
// Special lowercasing behavior for Greek Sigma: note that this is listed
// as context-sensitive in Unicode's SpecialCasing.txt, but is *not* a
// language-specific mapping; it applies regardless of the language of
// the element.
//
// The lowercase mapping for CAPITAL SIGMA should be to SMALL SIGMA (i.e.
// the non-final form) whenever there is a following letter, or when the
// CAPITAL SIGMA occurs in isolation (neither preceded nor followed by a
// LETTER); and to FINAL SIGMA when it is preceded by another letter but
// not followed by one.
//
// To implement the context-sensitive nature of this mapping, we keep
// track of whether the previous character was a letter. If not, CAPITAL
// SIGMA will map directly to SMALL SIGMA. If the previous character
// was a letter, CAPITAL SIGMA maps to FINAL SIGMA and we record the
// position in the converted string; if we then encounter another letter,
// that FINAL SIGMA is replaced with a standard SMALL SIGMA.
// If sigmaIndex is not -1, it marks where we have provisionally mapped
// a CAPITAL SIGMA to FINAL SIGMA; if we now find another letter, we
// need to change it to SMALL SIGMA.
if (sigmaIndex != uint32_t(-1)) {
if (cat == nsIUGenCategory::kLetter) {
aConvertedString.SetCharAt(GREEK_SMALL_LETTER_SIGMA, sigmaIndex);
}
}
if (ch == GREEK_CAPITAL_LETTER_SIGMA) {
// If preceding char was a letter, map to FINAL instead of SMALL,
// and note where it occurred by setting sigmaIndex; we'll change it
// to standard SMALL SIGMA later if another letter follows
if (prevIsLetter) {
ch = GREEK_SMALL_LETTER_FINAL_SIGMA;
sigmaIndex = aConvertedString.Length();
} else {
// CAPITAL SIGMA not preceded by a letter is unconditionally mapped
// to SMALL SIGMA
ch = GREEK_SMALL_LETTER_SIGMA;
sigmaIndex = uint32_t(-1);
}
prevIsLetter = true;
break;
}
// ignore diacritics for the purpose of contextual sigma mapping;
// otherwise, reset prevIsLetter appropriately and clear the
// sigmaIndex marker
if (cat != nsIUGenCategory::kMark) {
prevIsLetter = (cat == nsIUGenCategory::kLetter);
sigmaIndex = uint32_t(-1);
}
mcm = mozilla::unicode::SpecialLower(ch);
if (mcm) {
int j = 0;
while (j < 2 && mcm->mMappedChars[j + 1]) {
aConvertedString.Append(mcm->mMappedChars[j]);
++extraChars;
++j;
}
ch = mcm->mMappedChars[j];
break;
}
ch = ToLowerCase(ch);
break;
case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
if (languageSpecificCasing == eLSCB_Turkish && ch == 'i') {
ch = LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE;
break;
}
if (languageSpecificCasing == eLSCB_Greek) {
ch = mozilla::GreekCasing::UpperCase(ch, greekState);
break;
}
if (languageSpecificCasing == eLSCB_Irish) {
bool mark;
uint8_t action;
ch = mozilla::IrishCasing::UpperCase(ch, irishState, mark, action);
if (mark) {
irishMark = aConvertedString.Length();
irishMarkSrc = i;
break;
} else if (action) {
nsString& str = aConvertedString; // shorthand
switch (action) {
case 1:
// lowercase a single prefix letter
NS_ASSERTION(str.Length() > 0 && irishMark < str.Length(),
"bad irishMark!");
str.SetCharAt(ToLowerCase(str[irishMark]), irishMark);
irishMark = uint32_t(-1);
irishMarkSrc = uint32_t(-1);
break;
case 2:
// lowercase two prefix letters (immediately before current pos)
NS_ASSERTION(str.Length() >= 2 && irishMark == str.Length() - 2,
"bad irishMark!");
str.SetCharAt(ToLowerCase(str[irishMark]), irishMark);
str.SetCharAt(ToLowerCase(str[irishMark + 1]), irishMark + 1);
irishMark = uint32_t(-1);
irishMarkSrc = uint32_t(-1);
break;
case 3:
// lowercase one prefix letter, and delete following hyphen
// (which must be the immediately-preceding char)
NS_ASSERTION(str.Length() >= 2 && irishMark == str.Length() - 2,
"bad irishMark!");
MOZ_ASSERT((irishMark == uint32_t(-1)) ==
(irishMarkSrc == uint32_t(-1)),
"irishMark and irishMarkSrc initialization out of sync");
str.Replace(irishMark, 2, ToLowerCase(str[irishMark]));
aDeletedCharsArray[irishMarkSrc + 1] = true;
// Remove the trailing entries (corresponding to the deleted hyphen)
// from the auxiliary arrays.
aCharsToMergeArray.SetLength(aCharsToMergeArray.Length() - 1);
if (aTextRun) {
aStyleArray->SetLength(aStyleArray->Length() - 1);
aCanBreakBeforeArray->SetLength(aCanBreakBeforeArray->Length() - 1);
inhibitBreakBefore = true;
}
mergeNeeded = true;
irishMark = uint32_t(-1);
irishMarkSrc = uint32_t(-1);
break;
}
// ch has been set to the uppercase for current char;
// No need to check for SpecialUpper here as none of the characters
// that could trigger an Irish casing action have special mappings.
break;
}
// If we didn't have any special action to perform, fall through
// to check for special uppercase (ß)
}
mcm = mozilla::unicode::SpecialUpper(ch);
if (mcm) {
int j = 0;
while (j < 2 && mcm->mMappedChars[j + 1]) {
aConvertedString.Append(mcm->mMappedChars[j]);
++extraChars;
++j;
}
ch = mcm->mMappedChars[j];
break;
}
ch = ToUpperCase(ch);
break;
case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
if (aTextRun) {
if (capitalizeDutchIJ && ch == 'j') {
ch = 'J';
capitalizeDutchIJ = false;
break;
}
capitalizeDutchIJ = false;
if (i < aTextRun->mCapitalize.Length() && aTextRun->mCapitalize[i]) {
if (languageSpecificCasing == eLSCB_Turkish && ch == 'i') {
ch = LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE;
break;
}
if (languageSpecificCasing == eLSCB_Dutch && ch == 'i') {
ch = 'I';
capitalizeDutchIJ = true;
break;
}
mcm = mozilla::unicode::SpecialTitle(ch);
if (mcm) {
int j = 0;
while (j < 2 && mcm->mMappedChars[j + 1]) {
aConvertedString.Append(mcm->mMappedChars[j]);
++extraChars;
++j;
}
ch = mcm->mMappedChars[j];
break;
}
ch = ToTitleCase(ch);
}
}
break;
case NS_STYLE_TEXT_TRANSFORM_FULLWIDTH:
ch = mozilla::unicode::GetFullWidth(ch);
break;
default:
break;
}
if (forceNonFullWidth) {
ch = mozilla::unicode::GetFullWidthInverse(ch);
}
if (ch == uint32_t(-1)) {
aDeletedCharsArray.AppendElement(true);
mergeNeeded = true;
} else {
aDeletedCharsArray.AppendElement(false);
aCharsToMergeArray.AppendElement(false);
if (aTextRun) {
aStyleArray->AppendElement(charStyle);
aCanBreakBeforeArray->AppendElement(
inhibitBreakBefore ? gfxShapedText::CompressedGlyph::FLAG_BREAK_TYPE_NONE
: aTextRun->CanBreakBefore(i));
}
if (IS_IN_BMP(ch)) {
aConvertedString.Append(ch);
} else {
aConvertedString.Append(H_SURROGATE(ch));
aConvertedString.Append(L_SURROGATE(ch));
++i;
aDeletedCharsArray.AppendElement(true); // not exactly deleted, but the
// trailing surrogate is skipped
++extraChars;
}
while (extraChars-- > 0) {
mergeNeeded = true;
aCharsToMergeArray.AppendElement(true);
if (aTextRun) {
aStyleArray->AppendElement(charStyle);
aCanBreakBeforeArray->AppendElement(
gfxShapedText::CompressedGlyph::FLAG_BREAK_TYPE_NONE);
}
}
}
}
return mergeNeeded;
}
void
nsCaseTransformTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun,
DrawTarget* aRefDrawTarget,
gfxMissingFontRecorder* aMFR)
{
nsAutoString convertedString;
AutoTArray<bool,50> charsToMergeArray;
AutoTArray<bool,50> deletedCharsArray;
AutoTArray<uint8_t,50> canBreakBeforeArray;
AutoTArray<RefPtr<nsTransformedCharStyle>,50> styleArray;
bool mergeNeeded = TransformString(aTextRun->mString,
convertedString,
mAllUppercase,
nullptr,
charsToMergeArray,
deletedCharsArray,
aTextRun,
&canBreakBeforeArray,
&styleArray);
uint32_t flags;
gfxTextRunFactory::Parameters innerParams =
GetParametersForInner(aTextRun, &flags, aRefDrawTarget);
gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
UniquePtr<nsTransformedTextRun> transformedChild;
UniquePtr<gfxTextRun> cachedChild;
gfxTextRun* child;
if (mInnerTransformingTextRunFactory) {
transformedChild = mInnerTransformingTextRunFactory->MakeTextRun(
convertedString.BeginReading(), convertedString.Length(),
&innerParams, fontGroup, flags, Move(styleArray), false);
child = transformedChild.get();
} else {
cachedChild = fontGroup->MakeTextRun(
convertedString.BeginReading(), convertedString.Length(),
&innerParams, flags, aMFR);
child = cachedChild.get();
}
if (!child)
return;
// Copy potential linebreaks into child so they're preserved
// (and also child will be shaped appropriately)
NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
"Dropped characters or break-before values somewhere!");
gfxTextRun::Range range(0, uint32_t(canBreakBeforeArray.Length()));
child->SetPotentialLineBreaks(range, canBreakBeforeArray.Elements());
if (transformedChild) {
transformedChild->FinishSettingProperties(aRefDrawTarget, aMFR);
}
if (mergeNeeded) {
// Now merge multiple characters into one multi-glyph character as required
// and deal with skipping deleted accent chars
NS_ASSERTION(charsToMergeArray.Length() == child->GetLength(),
"source length mismatch");
NS_ASSERTION(deletedCharsArray.Length() == aTextRun->GetLength(),
"destination length mismatch");
MergeCharactersInTextRun(aTextRun, child, charsToMergeArray.Elements(),
deletedCharsArray.Elements());
} else {
// No merging to do, so just copy; this produces a more optimized textrun.
// We can't steal the data because the child may be cached and stealing
// the data would break the cache.
aTextRun->ResetGlyphRuns();
aTextRun->CopyGlyphDataFrom(child, gfxTextRun::Range(child), 0);
}
}