From 55dbc6f8403af1cb1fbd7f48cabf30ad55519087 Mon Sep 17 00:00:00 2001 From: Cameron McCormack Date: Sat, 27 Jun 2015 11:41:10 +1000 Subject: [PATCH] Bug 1072102 - Part 1: Implement FontFaceSet load and check. r=jdaggett,bzbarsky --- dom/bindings/Bindings.conf | 4 + dom/webidl/FontFaceSet.webidl | 6 +- gfx/thebes/gfxFontConstants.h | 1 + layout/style/FontFace.cpp | 19 +++- layout/style/FontFace.h | 2 + layout/style/FontFaceSet.cpp | 200 ++++++++++++++++++++++++++++++++-- layout/style/FontFaceSet.h | 18 ++- 7 files changed, 232 insertions(+), 18 deletions(-) diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index f72b101c66bb..305c908b1681 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -495,6 +495,10 @@ DOMInterfaces = { 'wrapperCache': False, }, +'FontFaceSet': { + 'implicitJSContext': [ 'load' ], +}, + 'FontFaceSetIterator': { 'wrapperCache': False, }, diff --git a/dom/webidl/FontFaceSet.webidl b/dom/webidl/FontFaceSet.webidl index 0921a24d4940..7f58fbe5e407 100644 --- a/dom/webidl/FontFaceSet.webidl +++ b/dom/webidl/FontFaceSet.webidl @@ -50,13 +50,11 @@ interface FontFaceSet : EventTarget { // check and start loads if appropriate // and fulfill promise when all loads complete - // Not implemented yet: bug 1072102. - // [Throws] Promise> load(DOMString font, optional DOMString text = " "); + [NewObject] Promise> load(DOMString font, optional DOMString text = " "); // return whether all fonts in the fontlist are loaded // (does not initiate load if not available) - // Not implemented yet: bug 1072102. - // [Throws] boolean check(DOMString font, optional DOMString text = " "); + [Throws] boolean check(DOMString font, optional DOMString text = " "); // async notification that font loading and layout operations are done [Throws] readonly attribute Promise ready; diff --git a/gfx/thebes/gfxFontConstants.h b/gfx/thebes/gfxFontConstants.h index a995dd82acfa..31d038ba6954 100644 --- a/gfx/thebes/gfxFontConstants.h +++ b/gfx/thebes/gfxFontConstants.h @@ -19,6 +19,7 @@ #define NS_FONT_WEIGHT_NORMAL 400 #define NS_FONT_WEIGHT_BOLD 700 +#define NS_FONT_WEIGHT_THIN 100 #define NS_FONT_STRETCH_ULTRA_CONDENSED (-4) #define NS_FONT_STRETCH_EXTRA_CONDENSED (-3) diff --git a/layout/style/FontFace.cpp b/layout/style/FontFace.cpp index 8ff0e94d0599..9a2e7ec7af17 100644 --- a/layout/style/FontFace.cpp +++ b/layout/style/FontFace.cpp @@ -386,8 +386,8 @@ FontFace::Load(ErrorResult& aRv) return mLoaded; } -void -FontFace::DoLoad() +gfxUserFontEntry* +FontFace::CreateUserFontEntry() { if (!mUserFontEntry) { MOZ_ASSERT(!HasRule(), @@ -396,13 +396,20 @@ FontFace::DoLoad() nsRefPtr newEntry = mFontFaceSet->FindOrCreateUserFontEntryFromFontFace(this); - if (!newEntry) { - return; + if (newEntry) { + SetUserFontEntry(newEntry); } - - SetUserFontEntry(newEntry); } + return mUserFontEntry; +} + +void +FontFace::DoLoad() +{ + if (!CreateUserFontEntry()) { + return; + } mUserFontEntry->Load(); } diff --git a/layout/style/FontFace.h b/layout/style/FontFace.h index ec60eae33a2f..7d4c667fafff 100644 --- a/layout/style/FontFace.h +++ b/layout/style/FontFace.h @@ -53,6 +53,7 @@ public: aUnicodeRanges) {} virtual void SetLoadState(UserFontLoadState aLoadState) override; + const nsAutoTArray& GetFontFaces() { return mFontFaces; } protected: // The FontFace objects that use this user font entry. We need to store @@ -76,6 +77,7 @@ public: void GetDesc(nsCSSFontDesc aDescID, nsCSSValue& aResult) const; + gfxUserFontEntry* CreateUserFontEntry(); gfxUserFontEntry* GetUserFontEntry() const { return mUserFontEntry; } void SetUserFontEntry(gfxUserFontEntry* aEntry); diff --git a/layout/style/FontFaceSet.cpp b/layout/style/FontFaceSet.cpp index 5148aadd182d..6e88ab1eb7ed 100644 --- a/layout/style/FontFaceSet.cpp +++ b/layout/style/FontFaceSet.cpp @@ -6,8 +6,8 @@ #include "FontFaceSet.h" -#include "mozilla/Logging.h" - +#include "gfxFontConstants.h" +#include "mozilla/css/Declaration.h" #include "mozilla/css/Loader.h" #include "mozilla/dom/CSSFontFaceLoadEvent.h" #include "mozilla/dom/CSSFontFaceLoadEventBinding.h" @@ -15,9 +15,12 @@ #include "mozilla/dom/FontFaceSetIterator.h" #include "mozilla/dom/Promise.h" #include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/Logging.h" #include "mozilla/Preferences.h" #include "mozilla/Snprintf.h" #include "nsCORSListenerProxy.h" +#include "nsCSSParser.h" +#include "nsDeviceContext.h" #include "nsFontFaceLoader.h" #include "nsIConsoleService.h" #include "nsIContentPolicy.h" @@ -33,8 +36,10 @@ #include "nsPresContext.h" #include "nsPrintfCString.h" #include "nsStyleSet.h" +#include "nsUTF8Utils.h" using namespace mozilla; +using namespace mozilla::css; using namespace mozilla::dom; #define LOG(args) MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args) @@ -161,13 +166,181 @@ FontFaceSet::RemoveDOMContentLoadedListener() } } +void +FontFaceSet::ParseFontShorthandForMatching( + const nsAString& aFont, + nsRefPtr& aFamilyList, + uint32_t& aWeight, + int32_t& aStretch, + uint32_t& aItalicStyle, + ErrorResult& aRv) +{ + // Parse aFont as a 'font' property value. + Declaration declaration; + declaration.InitializeEmpty(); + + bool changed = false; + nsCSSParser parser; + parser.ParseProperty(eCSSProperty_font, + aFont, + mDocument->GetDocumentURI(), + mDocument->GetDocumentURI(), + mDocument->NodePrincipal(), + &declaration, + &changed, + /* aIsImportant */ false); + + // All of the properties we are interested in should have been set at once. + MOZ_ASSERT(changed == (declaration.HasProperty(eCSSProperty_font_family) && + declaration.HasProperty(eCSSProperty_font_style) && + declaration.HasProperty(eCSSProperty_font_weight) && + declaration.HasProperty(eCSSProperty_font_stretch))); + + if (!changed) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + + nsCSSCompressedDataBlock* data = declaration.GetNormalBlock(); + MOZ_ASSERT(!declaration.GetImportantBlock()); + + const nsCSSValue* family = data->ValueFor(eCSSProperty_font_family); + if (family->GetUnit() != eCSSUnit_FontFamilyList) { + // We got inherit, initial, unset, a system font, or a token stream. + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + + aFamilyList = + static_cast(family->GetFontFamilyListValue()); + + int32_t weight = data->ValueFor(eCSSProperty_font_weight)->GetIntValue(); + + // Resolve relative font weights against the initial of font-weight + // (normal, which is equivalent to 400). + if (weight == NS_STYLE_FONT_WEIGHT_BOLDER) { + weight = NS_FONT_WEIGHT_BOLD; + } else if (weight == NS_STYLE_FONT_WEIGHT_LIGHTER) { + weight = NS_FONT_WEIGHT_THIN; + } + + aWeight = weight; + + aStretch = data->ValueFor(eCSSProperty_font_stretch)->GetIntValue(); + aItalicStyle = data->ValueFor(eCSSProperty_font_style)->GetIntValue(); +} + +static bool +HasAnyCharacterInUnicodeRange(gfxUserFontEntry* aEntry, + const nsAString& aInput) +{ + const char16_t* p = aInput.Data(); + const char16_t* end = p + aInput.Length(); + + while (p < end) { + uint32_t c = UTF16CharEnumerator::NextChar(&p, end); + if (aEntry->CharacterInUnicodeRange(c)) { + return true; + } + } + return false; +} + +void +FontFaceSet::FindMatchingFontFaces(const nsAString& aFont, + const nsAString& aText, + nsTArray& aFontFaces, + ErrorResult& aRv) +{ + nsRefPtr familyList; + uint32_t weight; + int32_t stretch; + uint32_t italicStyle; + ParseFontShorthandForMatching(aFont, familyList, weight, stretch, italicStyle, + aRv); + if (aRv.Failed()) { + return; + } + + gfxFontStyle style; + style.style = italicStyle; + style.weight = weight; + style.stretch = stretch; + + nsTArray* arrays[2]; + arrays[0] = &mNonRuleFaces; + arrays[1] = &mRuleFaces; + + // Set of FontFaces that we want to return. + nsTHashtable> matchingFaces; + + for (const FontFamilyName& fontFamilyName : familyList->GetFontlist()) { + nsRefPtr family = + mUserFontSet->LookupFamily(fontFamilyName.mName); + + if (!family) { + continue; + } + + nsAutoTArray entries; + bool needsBold; + family->FindAllFontsForStyle(style, entries, needsBold); + + for (gfxFontEntry* e : entries) { + FontFace::Entry* entry = static_cast(e); + if (HasAnyCharacterInUnicodeRange(entry, aText)) { + for (FontFace* f : entry->GetFontFaces()) { + matchingFaces.PutEntry(f); + } + } + } + } + + // Add all FontFaces in matchingFaces to aFontFaces, in the order + // they appear in the FontFaceSet. + for (nsTArray* array : arrays) { + for (FontFaceRecord& record : *array) { + FontFace* f = record.mFontFace; + if (matchingFaces.Contains(f)) { + aFontFaces.AppendElement(f); + } + } + } +} + already_AddRefed -FontFaceSet::Load(const nsAString& aFont, +FontFaceSet::Load(JSContext* aCx, + const nsAString& aFont, const nsAString& aText, ErrorResult& aRv) { - aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); - return nullptr; + FlushUserFontSet(); + + nsTArray> promises; + + nsTArray faces; + FindMatchingFontFaces(aFont, aText, faces, aRv); + if (aRv.Failed()) { + return nullptr; + } + + for (FontFace* f : faces) { + nsRefPtr promise = f->Load(aRv); + if (aRv.Failed()) { + return nullptr; + } + if (!promises.AppendElement(promise, fallible)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + } + + nsIGlobalObject* globalObject = GetParentObject(); + JS::Rooted jsGlobal(aCx, globalObject->GetGlobalJSObject()); + GlobalObject global(aCx, jsGlobal); + + nsRefPtr result = Promise::All(global, promises, aRv); + return result.forget(); } bool @@ -175,8 +348,21 @@ FontFaceSet::Check(const nsAString& aFont, const nsAString& aText, ErrorResult& aRv) { - aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); - return false; + FlushUserFontSet(); + + nsTArray faces; + FindMatchingFontFaces(aFont, aText, faces, aRv); + if (aRv.Failed()) { + return false; + } + + for (FontFace* f : faces) { + if (f->Status() != FontFaceLoadStatus::Loaded) { + return false; + } + } + + return true; } Promise* diff --git a/layout/style/FontFaceSet.h b/layout/style/FontFaceSet.h index 53a7189f8d78..af03a3a942d2 100644 --- a/layout/style/FontFaceSet.h +++ b/layout/style/FontFaceSet.h @@ -21,6 +21,9 @@ class nsIPrincipal; class nsPIDOMWindow; namespace mozilla { +namespace css { +class FontFamilyListRefCnt; +} namespace dom { class FontFace; class Promise; @@ -151,7 +154,8 @@ public: IMPL_EVENT_HANDLER(loading) IMPL_EVENT_HANDLER(loadingdone) IMPL_EVENT_HANDLER(loadingerror) - already_AddRefed Load(const nsAString& aFont, + already_AddRefed Load(JSContext* aCx, + const nsAString& aFont, const nsAString& aText, mozilla::ErrorResult& aRv); bool Check(const nsAString& aFont, @@ -271,6 +275,18 @@ private: // Helper function for HasLoadingFontFaces. void UpdateHasLoadingFontFaces(); + void ParseFontShorthandForMatching( + const nsAString& aFont, + nsRefPtr& aFamilyList, + uint32_t& aWeight, + int32_t& aStretch, + uint32_t& aItalicStyle, + ErrorResult& aRv); + void FindMatchingFontFaces(const nsAString& aFont, + const nsAString& aText, + nsTArray& aFontFaces, + mozilla::ErrorResult& aRv); + nsRefPtr mUserFontSet; // The document this is a FontFaceSet for.