diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index 5af401e1b0a2..2c602dd20b9e 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -27,6 +27,8 @@ #include "gfxUserFontSet.h" #include "gfxPlatformFontList.h" #include "gfxScriptItemizer.h" +#include "nsSpecialCasingData.h" +#include "nsTextRunTransformations.h" #include "nsUnicodeProperties.h" #include "nsMathUtils.h" #include "nsBidiUtils.h" @@ -5442,16 +5444,27 @@ gfxFontGroup::InitScriptRun(gfxContext *aContext, // create the glyph run for this range if (matchedFont) { - aTextRun->AddGlyphRun(matchedFont, range.matchType, - aOffset + runStart, (matchedLength > 0)); - // do glyph layout and record the resulting positioned glyphs - if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun, - aString + runStart, - aOffset + runStart, - matchedLength, - aRunScript)) { - // glyph layout failed! treat as missing glyphs - matchedFont = nullptr; + if (mStyle.smallCaps) { + if (!matchedFont->InitSmallCapsRun(aContext, aTextRun, + aString + runStart, + aOffset + runStart, + matchedLength, + range.matchType, + aRunScript)) { + matchedFont = nullptr; + } + } else { + aTextRun->AddGlyphRun(matchedFont, range.matchType, + aOffset + runStart, (matchedLength > 0)); + // do glyph layout and record the resulting positioned glyphs + if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun, + aString + runStart, + aOffset + runStart, + matchedLength, + aRunScript)) { + // glyph layout failed! treat as missing glyphs + matchedFont = nullptr; + } } } else { aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup, @@ -5535,6 +5548,180 @@ gfxFontGroup::InitScriptRun(gfxContext *aContext, } } +bool +gfxFont::InitSmallCapsRun(gfxContext *aContext, + gfxTextRun *aTextRun, + const uint8_t *aText, + uint32_t aOffset, + uint32_t aLength, + uint8_t aMatchType, + int32_t aScript) +{ + NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast(aText), + aLength); + return InitSmallCapsRun(aContext, aTextRun, unicodeString.get(), + aOffset, aLength, aMatchType, aScript); +} + +bool +gfxFont::InitSmallCapsRun(gfxContext *aContext, + gfxTextRun *aTextRun, + const char16_t *aText, + uint32_t aOffset, + uint32_t aLength, + uint8_t aMatchType, + int32_t aScript) +{ + bool ok = true; + + nsRefPtr smallCapsFont = GetSmallCapsFont(); + + enum RunCaseState { + kUpperOrCaseless, // will be untouched by font-variant:small-caps + kLowercase, // will be uppercased and reduced + kSpecialUpper // specials: don't shrink, but apply uppercase mapping + }; + RunCaseState runCase = kUpperOrCaseless; + uint32_t runStart = 0; + + for (uint32_t i = 0; i <= aLength; ++i) { + RunCaseState chCase = kUpperOrCaseless; + // Unless we're at the end, figure out what treatment the current + // character will need. + if (i < aLength) { + uint32_t ch = aText[i]; + if (NS_IS_HIGH_SURROGATE(ch) && i < aLength - 1 && + NS_IS_LOW_SURROGATE(aText[i + 1])) { + ch = SURROGATE_TO_UCS4(ch, aText[i + 1]); + } + // Characters that aren't the start of a cluster are ignored here. + // They get added to whatever lowercase/non-lowercase run we're in. + if (IsClusterExtender(ch)) { + chCase = runCase; + } else { + uint32_t ch2 = ToUpperCase(ch); + if (ch != ch2 || mozilla::unicode::SpecialUpper(ch)) { + chCase = kLowercase; + } + else if (mStyle.language == nsGkAtoms::el) { + // In Greek, check for characters that will be modified by + // the GreekUpperCase mapping - this catches accented + // capitals where the accent is to be removed (bug 307039). + // These are handled by using the full-size font with the + // uppercasing transform. + GreekCasing::State state; + ch2 = GreekCasing::UpperCase(ch, state); + if (ch != ch2) { + chCase = kSpecialUpper; + } + } + } + } + + // At the end of the text or when the current character needs different + // casing treatment from the current run, finish the run-in-progress + // and prepare to accumulate a new run. + // Note that we do not look at any source data for offset [i] here, + // as that would be invalid in the case where i==length. + if ((i == aLength || runCase != chCase) && runStart < i) { + uint32_t runLength = i - runStart; + gfxFont* f = this; + switch (runCase) { + case kUpperOrCaseless: + // just use the current font and the existing string + aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true); + if (!f->SplitAndInitTextRun(aContext, aTextRun, + aText + runStart, + aOffset + runStart, runLength, + aScript)) { + ok = false; + } + break; + + case kLowercase: + // use reduced-size font, fall through to uppercase the text + f = smallCapsFont; + + case kSpecialUpper: + // apply uppercase transform to the string + nsDependentSubstring origString(aText + runStart, runLength); + nsAutoString convertedString; + nsAutoTArray charsToMergeArray; + nsAutoTArray deletedCharsArray; + + bool mergeNeeded = nsCaseTransformTextRunFactory:: + TransformString(origString, + convertedString, + true, + mStyle.language, + charsToMergeArray, + deletedCharsArray); + + if (mergeNeeded) { + // This is the hard case: the transformation caused chars + // to be inserted or deleted, so we can't shape directly + // into the destination textrun but have to handle the + // mismatch of character positions. + gfxTextRunFactory::Parameters params = { + aContext, nullptr, nullptr, nullptr, 0, + aTextRun->GetAppUnitsPerDevUnit() + }; + nsAutoPtr tempRun; + tempRun = + gfxTextRun::Create(¶ms, convertedString.Length(), + aTextRun->GetFontGroup(), 0); + tempRun->AddGlyphRun(f, aMatchType, 0, true); + if (!f->SplitAndInitTextRun(aContext, tempRun, + convertedString.BeginReading(), + 0, convertedString.Length(), + aScript)) { + ok = false; + } else { + nsAutoPtr mergedRun; + mergedRun = + gfxTextRun::Create(¶ms, runLength, + aTextRun->GetFontGroup(), 0); + MergeCharactersInTextRun(mergedRun, tempRun, + charsToMergeArray.Elements(), + deletedCharsArray.Elements()); + aTextRun->CopyGlyphDataFrom(mergedRun, 0, runLength, + aOffset + runStart); + } + } else { + aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, + true); + if (!f->SplitAndInitTextRun(aContext, aTextRun, + convertedString.BeginReading(), + aOffset + runStart, runLength, + aScript)) { + ok = false; + } + } + break; + } + + runStart = i; + } + + if (i < aLength) { + runCase = chCase; + } + } + + return ok; +} + +already_AddRefed +gfxFont::GetSmallCapsFont() +{ + gfxFontStyle style(*GetStyle()); + style.size *= SMALL_CAPS_SCALE_FACTOR; + style.smallCaps = false; + gfxFontEntry* fe = GetFontEntry(); + bool needsBold = style.weight >= 600 && !fe->IsBold(); + return fe->FindOrMakeFont(&style, needsBold); +} + gfxTextRun * gfxFontGroup::GetEllipsisTextRun(int32_t aAppUnitsPerDevPixel, LazyReferenceContextGetter& aRefContextGetter) diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h index ad023e814556..c9fde71965a3 100644 --- a/gfx/thebes/gfxFont.h +++ b/gfx/thebes/gfxFont.h @@ -60,6 +60,8 @@ class nsILanguageAtomService; #define NO_FONT_LANGUAGE_OVERRIDE 0 +#define SMALL_CAPS_SCALE_FACTOR 0.8 + struct FontListSizes; struct gfxTextRunDrawCallbacks; @@ -1806,6 +1808,22 @@ public: return mFontEntry->GetUVSGlyph(aCh, aVS); } + bool InitSmallCapsRun(gfxContext *aContext, + gfxTextRun *aTextRun, + const uint8_t *aText, + uint32_t aOffset, + uint32_t aLength, + uint8_t aMatchType, + int32_t aScript); + + bool InitSmallCapsRun(gfxContext *aContext, + gfxTextRun *aTextRun, + const char16_t *aText, + uint32_t aOffset, + uint32_t aLength, + uint8_t aMatchType, + int32_t aScript); + // call the (virtual) InitTextRun method to do glyph generation/shaping, // limiting the length of text passed by processing the run in multiple // segments if necessary @@ -1914,6 +1932,13 @@ public: } protected: + // Return a font that is a "clone" of this one, but reduced to 80% size + // (and with the smallCaps style set to false). + // Default implementation relies on gfxFontEntry::CreateFontInstance; + // backends that don't implement that will need to override this and use + // an alternative technique. (gfxPangoFonts, I'm looking at you...) + virtual already_AddRefed GetSmallCapsFont(); + // subclasses may provide (possibly hinted) glyph widths (in font units); // if they do not override this, harfbuzz will use unhinted widths // derived from the font tables diff --git a/gfx/thebes/gfxPangoFonts.cpp b/gfx/thebes/gfxPangoFonts.cpp index 98b1a199a626..abfc5e7c71e7 100644 --- a/gfx/thebes/gfxPangoFonts.cpp +++ b/gfx/thebes/gfxPangoFonts.cpp @@ -661,6 +661,8 @@ public: #endif protected: + virtual already_AddRefed GetSmallCapsFont(); + virtual bool ShapeText(gfxContext *aContext, const char16_t *aText, uint32_t aOffset, @@ -669,12 +671,6 @@ protected: gfxShapedText *aShapedText, bool aPreferPlatformShaping); - bool InitGlyphRunWithPango(const char16_t *aString, - uint32_t aOffset, - uint32_t aLength, - int32_t aScript, - gfxShapedText *aShapedText); - private: gfxFcFont(cairo_scaled_font_t *aCairoFont, gfxFcFontEntry *aFontEntry, const gfxFontStyle *aFontStyle); @@ -1601,6 +1597,41 @@ gfxFcFont::~gfxFcFont() nullptr); } +already_AddRefed +gfxFcFont::GetSmallCapsFont() +{ + gfxFontStyle style(*GetStyle()); + style.size *= SMALL_CAPS_SCALE_FACTOR; + style.smallCaps = false; + gfxFcFontEntry* fe = static_cast(GetFontEntry()); + nsRefPtr font = gfxFontCache::GetCache()->Lookup(fe, &style); + if (font) { + return font.forget(); + } + + cairo_matrix_t fontMatrix; + cairo_scaled_font_get_font_matrix(mScaledFont, &fontMatrix); + cairo_matrix_scale(&fontMatrix, + SMALL_CAPS_SCALE_FACTOR, SMALL_CAPS_SCALE_FACTOR); + + cairo_matrix_t ctm; + cairo_scaled_font_get_ctm(mScaledFont, &ctm); + + cairo_font_options_t *options = cairo_font_options_create(); + cairo_scaled_font_get_font_options(mScaledFont, options); + + cairo_scaled_font_t *smallFont = + cairo_scaled_font_create(cairo_scaled_font_get_font_face(mScaledFont), + &fontMatrix, &ctm, options); + cairo_font_options_destroy(options); + + font = new gfxFcFont(smallFont, fe, &style); + gfxFontCache::GetCache()->AddNew(font); + cairo_scaled_font_destroy(smallFont); + + return font.forget(); +} + bool gfxFcFont::ShapeText(gfxContext *aContext, const char16_t *aText, diff --git a/layout/generic/moz.build b/layout/generic/moz.build index dcafd0480b5c..b8d4252e3df2 100644 --- a/layout/generic/moz.build +++ b/layout/generic/moz.build @@ -32,6 +32,7 @@ EXPORTS += [ 'nsQueryFrame.h', 'nsSplittableFrame.h', 'nsSubDocumentFrame.h', + 'nsTextRunTransformations.h', 'ScrollbarActivity.h', 'Selection.h', 'WritingModes.h', diff --git a/layout/generic/nsTextRunTransformations.h b/layout/generic/nsTextRunTransformations.h index 8c8b3482fd2c..b4adbdd18978 100644 --- a/layout/generic/nsTextRunTransformations.h +++ b/layout/generic/nsTextRunTransformations.h @@ -9,9 +9,9 @@ #include "mozilla/Attributes.h" #include "mozilla/MemoryReporting.h" #include "gfxFont.h" +#include "nsStyleContext.h" class nsTransformedTextRun; -class nsStyleContext; class nsTransformingTextRunFactory { public: