nsFontFaceUtils.cpp (8877B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "nsFontFaceUtils.h" 8 9 #include "gfxTextRun.h" 10 #include "gfxUserFontSet.h" 11 #include "mozilla/PresShell.h" 12 #include "mozilla/RestyleManager.h" 13 #include "nsFontMetrics.h" 14 #include "nsIFrame.h" 15 #include "nsLayoutUtils.h" 16 #include "nsPlaceholderFrame.h" 17 #include "nsTArray.h" 18 19 using namespace mozilla; 20 21 enum class FontUsageKind { 22 // The frame did not use the given font. 23 None = 0, 24 // The frame uses the given font, but doesn't use font-metric-dependent units, 25 // which means that its style doesn't depend on this font. 26 Frame = 1 << 0, 27 // The frame uses has some font-metric-dependent units on this font. 28 // This means that its style depends on this font, and we need to restyle the 29 // element the frame came from. 30 FontMetrics = 1 << 1, 31 32 Max = Frame | FontMetrics, 33 }; 34 35 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(FontUsageKind); 36 37 static bool IsFontReferenced(const ComputedStyle& aStyle, 38 const nsAString& aFamilyName) { 39 for (const auto& family : 40 aStyle.StyleFont()->mFont.family.families.list.AsSpan()) { 41 if (family.IsNamedFamily(aFamilyName)) { 42 return true; 43 } 44 } 45 return false; 46 } 47 48 static FontUsageKind StyleFontUsage(nsIFrame* aFrame, ComputedStyle* aStyle, 49 nsPresContext* aPresContext, 50 const gfxUserFontEntry* aFont, 51 const nsAString& aFamilyName, 52 bool aIsExtraStyle) { 53 MOZ_ASSERT(NS_ConvertUTF8toUTF16(aFont->FamilyName()) == aFamilyName); 54 55 auto FontIsUsed = [&aFont, &aPresContext, 56 &aFamilyName](ComputedStyle* aStyle) { 57 if (!IsFontReferenced(*aStyle, aFamilyName)) { 58 return false; 59 } 60 61 // family name is in the fontlist, check to see if the font group 62 // associated with the frame includes the specific userfont. 63 // 64 // TODO(emilio): Is this check really useful? I guess it's useful for 65 // different font faces of the same font? 66 RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForComputedStyle( 67 aStyle, aPresContext, 1.0f); 68 return fm->GetThebesFontGroup()->ContainsUserFont(aFont); 69 }; 70 71 auto usage = FontUsageKind::None; 72 73 if (FontIsUsed(aStyle)) { 74 usage |= FontUsageKind::Frame; 75 if (aStyle->DependsOnSelfFontMetrics()) { 76 usage |= FontUsageKind::FontMetrics; 77 } 78 } 79 80 if (aStyle->DependsOnInheritedFontMetrics() && 81 !(usage & FontUsageKind::FontMetrics)) { 82 ComputedStyle* parentStyle = nullptr; 83 if (aIsExtraStyle) { 84 parentStyle = aFrame->Style(); 85 } else { 86 nsIFrame* provider = nullptr; 87 parentStyle = aFrame->GetParentComputedStyle(&provider); 88 } 89 90 if (parentStyle && FontIsUsed(parentStyle)) { 91 usage |= FontUsageKind::FontMetrics; 92 } 93 } 94 95 return usage; 96 } 97 98 static FontUsageKind FrameFontUsage(nsIFrame* aFrame, 99 nsPresContext* aPresContext, 100 const gfxUserFontEntry* aFont, 101 const nsAString& aFamilyName) { 102 // check the style of the frame 103 FontUsageKind kind = StyleFontUsage(aFrame, aFrame->Style(), aPresContext, 104 aFont, aFamilyName, /* extra = */ false); 105 if (kind == FontUsageKind::Max) { 106 return kind; 107 } 108 109 // check additional styles 110 int32_t contextIndex = 0; 111 for (ComputedStyle* extraContext; 112 (extraContext = aFrame->GetAdditionalComputedStyle(contextIndex)); 113 ++contextIndex) { 114 kind |= StyleFontUsage(aFrame, extraContext, aPresContext, aFont, 115 aFamilyName, /* extra = */ true); 116 if (kind == FontUsageKind::Max) { 117 break; 118 } 119 } 120 121 return kind; 122 } 123 124 // TODO(emilio): Can we use the restyle-hint machinery instead of this? 125 static void ScheduleReflow(PresShell* aPresShell, nsIFrame* aFrame) { 126 nsIFrame* f = aFrame; 127 if (f->IsSVGFrame() || f->IsInSVGTextSubtree()) { 128 // SVG frames (and the non-SVG descendants of an SVGTextFrame) need special 129 // reflow handling. We need to search upwards for the first displayed 130 // SVGOuterSVGFrame or non-SVG frame, which is the frame we can call 131 // FrameNeedsReflow on. (This logic is based on 132 // SVGUtils::ScheduleReflowSVG and 133 // SVGTextFrame::ScheduleReflowSVGNonDisplayText.) 134 if (f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 135 while (f) { 136 if (!f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 137 if (f->IsSubtreeDirty()) { 138 // This is a displayed frame, so if it is already dirty, we 139 // will be reflowed soon anyway. No need to call 140 // FrameNeedsReflow again, then. 141 return; 142 } 143 if (!f->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) || 144 !f->IsInSVGTextSubtree()) { 145 break; 146 } 147 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); 148 } 149 f = f->GetParent(); 150 } 151 MOZ_ASSERT(f, "should have found an ancestor frame to reflow"); 152 } 153 } 154 155 aPresShell->FrameNeedsReflow(f, IntrinsicDirty::FrameAncestorsAndDescendants, 156 NS_FRAME_IS_DIRTY); 157 } 158 159 enum class ReflowAlreadyScheduled { 160 No, 161 Yes, 162 }; 163 164 /* static */ 165 void nsFontFaceUtils::MarkDirtyForFontChange(nsIFrame* aSubtreeRoot, 166 const gfxUserFontEntry* aFont) { 167 MOZ_ASSERT(aFont); 168 AutoTArray<nsIFrame*, 4> subtrees; 169 subtrees.AppendElement(aSubtreeRoot); 170 171 nsPresContext* pc = aSubtreeRoot->PresContext(); 172 PresShell* presShell = pc->PresShell(); 173 174 const bool usesMetricsFromStyle = pc->StyleSet()->UsesFontMetrics(); 175 const bool usesRootMetricsFromStyle = pc->StyleSet()->UsesRootFontMetrics(); 176 177 // StyleSingleFontFamily::IsNamedFamily expects a UTF-16 string. Convert it 178 // once here rather than on each call. 179 NS_ConvertUTF8toUTF16 familyName(aFont->FamilyName()); 180 181 // check descendants, iterating over subtrees that may include 182 // additional subtrees associated with placeholders 183 do { 184 nsIFrame* subtreeRoot = subtrees.PopLastElement(); 185 186 // Check all descendants to see if they use the font 187 AutoTArray<std::pair<nsIFrame*, ReflowAlreadyScheduled>, 32> stack; 188 stack.AppendElement( 189 std::make_pair(subtreeRoot, ReflowAlreadyScheduled::No)); 190 191 do { 192 auto pair = stack.PopLastElement(); 193 nsIFrame* f = pair.first; 194 ReflowAlreadyScheduled alreadyScheduled = pair.second; 195 196 // if this frame uses the font, mark its descendants dirty 197 // and skip checking its children 198 FontUsageKind kind = FrameFontUsage(f, pc, aFont, familyName); 199 if (kind != FontUsageKind::None) { 200 if ((kind & FontUsageKind::Frame) && 201 alreadyScheduled == ReflowAlreadyScheduled::No) { 202 ScheduleReflow(presShell, f); 203 alreadyScheduled = ReflowAlreadyScheduled::Yes; 204 } 205 206 // If the updated font is used for font metrics, then styles need to be 207 // recomputed. This can occur if the current frame directly uses the 208 // font's metrics (ex/ch/...). However, if there are any elements in the 209 // document using root element relative font metrics (rex/rch/...) and 210 // the root element itself used the updated font, then the entire 211 // subtree needs to be restyled. 212 const bool shouldRestyleForFontMetrics = 213 (kind & FontUsageKind::FontMetrics) || 214 (usesRootMetricsFromStyle && f->Style()->IsRootElementStyle()); 215 216 if (shouldRestyleForFontMetrics) { 217 MOZ_ASSERT(f->GetContent() && f->GetContent()->IsElement(), 218 "How could we target a non-element with selectors?"); 219 f->PresContext()->RestyleManager()->PostRestyleEvent( 220 dom::Element::FromNode(f->GetContent()), 221 RestyleHint::RECASCADE_SELF, nsChangeHint(0)); 222 } 223 } 224 225 if (alreadyScheduled == ReflowAlreadyScheduled::No || 226 usesMetricsFromStyle) { 227 if (f->IsPlaceholderFrame()) { 228 nsIFrame* oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f); 229 if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) { 230 // We have another distinct subtree we need to mark. 231 subtrees.AppendElement(oof); 232 } 233 } 234 235 for (const auto& childList : f->ChildLists()) { 236 for (nsIFrame* kid : childList.mList) { 237 stack.AppendElement(std::make_pair(kid, alreadyScheduled)); 238 } 239 } 240 } 241 } while (!stack.IsEmpty()); 242 } while (!subtrees.IsEmpty()); 243 }