/* -*- 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 "TextAttrs.h" #include "AccAttributes.h" #include "nsAccUtils.h" #include "nsCoreUtils.h" #include "StyleInfo.h" #include "gfxTextRun.h" #include "nsFontMetrics.h" #include "nsLayoutUtils.h" #include "nsContainerFrame.h" #include "HyperTextAccessible.h" #include "mozilla/AppUnits.h" using namespace mozilla; using namespace mozilla::a11y; //////////////////////////////////////////////////////////////////////////////// // TextAttrsMgr //////////////////////////////////////////////////////////////////////////////// void TextAttrsMgr::GetAttributes(AccAttributes* aAttributes) { MOZ_ASSERT( // 1. Hyper text accessible and attributes list must always be specified. mHyperTextAcc && aAttributes && ( // 2. If text attributes for a child of a container are being // requested, the offset accessible must be specified and it must // be text. (mOffsetAcc && mOffsetAcc->IsText()) || // 3. If only default text attributes for a container are being // requested, the offset accessible must not be specified, but the // include default text attributes flag must be specified. (!mOffsetAcc && mIncludeDefAttrs)), "Wrong usage of TextAttrsMgr!"); // Get the content and frame of the accessible. In the case of document // accessible it's role content and root frame. nsIContent* hyperTextElm = mHyperTextAcc->GetContent(); if (!hyperTextElm) { return; // XXX: we don't support text attrs on document with no body } nsIFrame* rootFrame = mHyperTextAcc->GetFrame(); if (!rootFrame) { return; } nsIContent *offsetNode = nullptr, *offsetElm = nullptr; nsIFrame* frame = nullptr; if (mOffsetAcc) { offsetNode = mOffsetAcc->GetContent(); offsetElm = nsCoreUtils::GetDOMElementFor(offsetNode); MOZ_ASSERT(offsetElm, "No element for offset accessible!"); if (!offsetElm) return; frame = offsetElm->GetPrimaryFrame(); } // "language" text attribute LangTextAttr langTextAttr(mHyperTextAcc, hyperTextElm, offsetNode); // "aria-invalid" text attribute InvalidTextAttr invalidTextAttr(hyperTextElm, offsetNode); // "background-color" text attribute BGColorTextAttr bgColorTextAttr(rootFrame, frame); // "color" text attribute ColorTextAttr colorTextAttr(rootFrame, frame); // "font-family" text attribute FontFamilyTextAttr fontFamilyTextAttr(rootFrame, frame); // "font-size" text attribute FontSizeTextAttr fontSizeTextAttr(rootFrame, frame); // "font-style" text attribute FontStyleTextAttr fontStyleTextAttr(rootFrame, frame); // "font-weight" text attribute FontWeightTextAttr fontWeightTextAttr(rootFrame, frame); // "auto-generated" text attribute AutoGeneratedTextAttr autoGenTextAttr(mHyperTextAcc, mOffsetAcc); // "text-underline(line-through)-style(color)" text attributes TextDecorTextAttr textDecorTextAttr(rootFrame, frame); // "text-position" text attribute TextPosTextAttr textPosTextAttr(rootFrame, frame, hyperTextElm, offsetNode); TextAttr* attrArray[] = { &langTextAttr, &invalidTextAttr, &bgColorTextAttr, &colorTextAttr, &fontFamilyTextAttr, &fontSizeTextAttr, &fontStyleTextAttr, &fontWeightTextAttr, &autoGenTextAttr, &textDecorTextAttr, &textPosTextAttr}; // Expose text attributes. for (TextAttr* attr : attrArray) { attr->Expose(aAttributes, mIncludeDefAttrs); } } //////////////////////////////////////////////////////////////////////////////// // LangTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::LangTextAttr::LangTextAttr(HyperTextAccessible* aRoot, nsIContent* aRootElm, nsIContent* aElm) : TTextAttr(!aElm), mRootContent(aRootElm) { aRoot->Language(mRootNativeValue); mIsRootDefined = !mRootNativeValue.IsEmpty(); if (aElm) { nsCoreUtils::GetLanguageFor(aElm, mRootContent, mNativeValue); mIsDefined = !mNativeValue.IsEmpty(); } } TextAttrsMgr::LangTextAttr::~LangTextAttr() {} void TextAttrsMgr::LangTextAttr::ExposeValue(AccAttributes* aAttributes, const nsString& aValue) { RefPtr lang = NS_Atomize(aValue); aAttributes->SetAttribute(nsGkAtoms::language, lang); } //////////////////////////////////////////////////////////////////////////////// // InvalidTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::InvalidTextAttr::InvalidTextAttr(nsIContent* aRootElm, nsIContent* aElm) : TTextAttr(!aElm), mRootElm(aRootElm) { mIsRootDefined = GetValue(mRootElm, &mRootNativeValue); if (aElm) mIsDefined = GetValue(aElm, &mNativeValue); } void TextAttrsMgr::InvalidTextAttr::ExposeValue(AccAttributes* aAttributes, const uint32_t& aValue) { switch (aValue) { case eFalse: aAttributes->SetAttribute(nsGkAtoms::invalid, nsGkAtoms::_false); break; case eGrammar: aAttributes->SetAttribute(nsGkAtoms::invalid, nsGkAtoms::grammar); break; case eSpelling: aAttributes->SetAttribute(nsGkAtoms::invalid, nsGkAtoms::spelling); break; case eTrue: aAttributes->SetAttribute(nsGkAtoms::invalid, nsGkAtoms::_true); break; } } bool TextAttrsMgr::InvalidTextAttr::GetValue(nsIContent* aElm, uint32_t* aValue) { nsIContent* elm = aElm; do { if (nsAccUtils::HasDefinedARIAToken(elm, nsGkAtoms::aria_invalid)) { static dom::Element::AttrValuesArray tokens[] = { nsGkAtoms::_false, nsGkAtoms::grammar, nsGkAtoms::spelling, nullptr}; int32_t idx = nsAccUtils::FindARIAAttrValueIn( elm->AsElement(), nsGkAtoms::aria_invalid, tokens, eCaseMatters); switch (idx) { case 0: *aValue = eFalse; return true; case 1: *aValue = eGrammar; return true; case 2: *aValue = eSpelling; return true; default: *aValue = eTrue; return true; } } } while ((elm = elm->GetParent()) && elm != mRootElm); return false; } //////////////////////////////////////////////////////////////////////////////// // BGColorTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::BGColorTextAttr::BGColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame), mRootFrame(aRootFrame) { mIsRootDefined = GetColor(mRootFrame, &mRootNativeValue); if (aFrame) mIsDefined = GetColor(aFrame, &mNativeValue); } void TextAttrsMgr::BGColorTextAttr::ExposeValue(AccAttributes* aAttributes, const nscolor& aValue) { aAttributes->SetAttribute(nsGkAtoms::backgroundColor, Color{aValue}); } bool TextAttrsMgr::BGColorTextAttr::GetColor(nsIFrame* aFrame, nscolor* aColor) { nscolor backgroundColor = aFrame->StyleBackground()->BackgroundColor(aFrame); if (NS_GET_A(backgroundColor) > 0) { *aColor = backgroundColor; return true; } nsContainerFrame* parentFrame = aFrame->GetParent(); if (!parentFrame) { *aColor = aFrame->PresContext()->DefaultBackgroundColor(); return true; } // Each frame of parents chain for the initially passed 'aFrame' has // transparent background color. So background color isn't changed from // 'mRootFrame' to initially passed 'aFrame'. if (parentFrame == mRootFrame) return false; return GetColor(parentFrame, aColor); } //////////////////////////////////////////////////////////////////////////////// // ColorTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::ColorTextAttr::ColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame) { mRootNativeValue = aRootFrame->StyleText()->mColor.ToColor(); mIsRootDefined = true; if (aFrame) { mNativeValue = aFrame->StyleText()->mColor.ToColor(); mIsDefined = true; } } void TextAttrsMgr::ColorTextAttr::ExposeValue(AccAttributes* aAttributes, const nscolor& aValue) { aAttributes->SetAttribute(nsGkAtoms::color, Color{aValue}); } //////////////////////////////////////////////////////////////////////////////// // FontFamilyTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::FontFamilyTextAttr::FontFamilyTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame) { mIsRootDefined = GetFontFamily(aRootFrame, mRootNativeValue); if (aFrame) mIsDefined = GetFontFamily(aFrame, mNativeValue); } void TextAttrsMgr::FontFamilyTextAttr::ExposeValue(AccAttributes* aAttributes, const nsString& aValue) { RefPtr family = NS_Atomize(aValue); aAttributes->SetAttribute(nsGkAtoms::font_family, family); } bool TextAttrsMgr::FontFamilyTextAttr::GetFontFamily(nsIFrame* aFrame, nsString& aFamily) { RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(aFrame, 1.0f); gfxFontGroup* fontGroup = fm->GetThebesFontGroup(); RefPtr font = fontGroup->GetFirstValidFont(); gfxFontEntry* fontEntry = font->GetFontEntry(); aFamily.Append(NS_ConvertUTF8toUTF16(fontEntry->FamilyName())); return true; } //////////////////////////////////////////////////////////////////////////////// // FontSizeTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::FontSizeTextAttr::FontSizeTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame) { mDC = aRootFrame->PresContext()->DeviceContext(); mRootNativeValue = aRootFrame->StyleFont()->mSize.ToAppUnits(); mIsRootDefined = true; if (aFrame) { mNativeValue = aFrame->StyleFont()->mSize.ToAppUnits(); mIsDefined = true; } } void TextAttrsMgr::FontSizeTextAttr::ExposeValue(AccAttributes* aAttributes, const nscoord& aValue) { // Convert from nscoord to pt. // // Note: according to IA2, "The conversion doesn't have to be exact. // The intent is to give the user a feel for the size of the text." // // ATK does not specify a unit and will likely follow IA2 here. // // XXX todo: consider sharing this code with layout module? (bug 474621) float px = NSAppUnitsToFloatPixels(aValue, mozilla::AppUnitsPerCSSPixel()); // Each pt is 4/3 of a CSS pixel. FontSize fontSize{NS_lround(px * 3 / 4)}; aAttributes->SetAttribute(nsGkAtoms::font_size, fontSize); } //////////////////////////////////////////////////////////////////////////////// // FontStyleTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::FontStyleTextAttr::FontStyleTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame) { mRootNativeValue = aRootFrame->StyleFont()->mFont.style; mIsRootDefined = true; if (aFrame) { mNativeValue = aFrame->StyleFont()->mFont.style; mIsDefined = true; } } void TextAttrsMgr::FontStyleTextAttr::ExposeValue( AccAttributes* aAttributes, const FontSlantStyle& aValue) { if (aValue.IsNormal()) { aAttributes->SetAttribute(nsGkAtoms::font_style, nsGkAtoms::normal); } else if (aValue.IsItalic()) { RefPtr atom = NS_Atomize("italic"); aAttributes->SetAttribute(nsGkAtoms::font_style, atom); } else { nsAutoCString s; aValue.ToString(s); nsString wide; CopyUTF8toUTF16(s, wide); aAttributes->SetAttribute(nsGkAtoms::font_style, std::move(wide)); } } //////////////////////////////////////////////////////////////////////////////// // FontWeightTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::FontWeightTextAttr::FontWeightTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame) { mRootNativeValue = GetFontWeight(aRootFrame); mIsRootDefined = true; if (aFrame) { mNativeValue = GetFontWeight(aFrame); mIsDefined = true; } } void TextAttrsMgr::FontWeightTextAttr::ExposeValue(AccAttributes* aAttributes, const FontWeight& aValue) { int value = aValue.ToIntRounded(); aAttributes->SetAttribute(nsGkAtoms::fontWeight, value); } FontWeight TextAttrsMgr::FontWeightTextAttr::GetFontWeight(nsIFrame* aFrame) { // nsFont::width isn't suitable here because it's necessary to expose real // value of font weight (used font might not have some font weight values). RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(aFrame, 1.0f); gfxFontGroup* fontGroup = fm->GetThebesFontGroup(); RefPtr font = fontGroup->GetFirstValidFont(); // When there doesn't exist a bold font in the family and so the rendering of // a non-bold font face is changed so that the user sees what looks like a // bold font, i.e. synthetic bolding is used. (Simply returns false on any // platforms that don't use the multi-strike synthetic bolding.) if (font->ApplySyntheticBold()) { return FontWeight::BOLD; } // On Windows, font->GetStyle()->weight will give the same weight as // fontEntry->Weight(), the weight of the first font in the font group, // which may not be the weight of the font face used to render the // characters. On Mac, font->GetStyle()->weight will just give the same // number as getComputedStyle(). fontEntry->Weight() will give the weight // range supported by the font face used, so we clamp the weight that was // requested by style to what is actually supported by the font. gfxFontEntry* fontEntry = font->GetFontEntry(); return fontEntry->Weight().Clamp(font->GetStyle()->weight); } //////////////////////////////////////////////////////////////////////////////// // AutoGeneratedTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::AutoGeneratedTextAttr::AutoGeneratedTextAttr( HyperTextAccessible* aHyperTextAcc, LocalAccessible* aAccessible) : TTextAttr(!aAccessible) { mRootNativeValue = false; mIsRootDefined = false; if (aAccessible) { mIsDefined = mNativeValue = ((aAccessible->NativeRole() == roles::STATICTEXT) || (aAccessible->NativeRole() == roles::LISTITEM_MARKER)); } } void TextAttrsMgr::AutoGeneratedTextAttr::ExposeValue( AccAttributes* aAttributes, const bool& aValue) { aAttributes->SetAttribute(nsGkAtoms::auto_generated, aValue); } //////////////////////////////////////////////////////////////////////////////// // TextDecorTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::TextDecorValue::TextDecorValue(nsIFrame* aFrame) { const nsStyleTextReset* textReset = aFrame->StyleTextReset(); mStyle = textReset->mTextDecorationStyle; mColor = textReset->mTextDecorationColor.CalcColor(aFrame); mLine = textReset->mTextDecorationLine & (StyleTextDecorationLine::UNDERLINE | StyleTextDecorationLine::LINE_THROUGH); } TextAttrsMgr::TextDecorTextAttr::TextDecorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame) { mRootNativeValue = TextDecorValue(aRootFrame); mIsRootDefined = mRootNativeValue.IsDefined(); if (aFrame) { mNativeValue = TextDecorValue(aFrame); mIsDefined = mNativeValue.IsDefined(); } } void TextAttrsMgr::TextDecorTextAttr::ExposeValue( AccAttributes* aAttributes, const TextDecorValue& aValue) { if (aValue.IsUnderline()) { RefPtr underlineStyle = StyleInfo::TextDecorationStyleToAtom(aValue.Style()); aAttributes->SetAttribute(nsGkAtoms::textUnderlineStyle, underlineStyle); aAttributes->SetAttribute(nsGkAtoms::textUnderlineColor, Color{aValue.Color()}); return; } if (aValue.IsLineThrough()) { RefPtr lineThroughStyle = StyleInfo::TextDecorationStyleToAtom(aValue.Style()); aAttributes->SetAttribute(nsGkAtoms::textLineThroughStyle, lineThroughStyle); aAttributes->SetAttribute(nsGkAtoms::textLineThroughColor, Color{aValue.Color()}); } } //////////////////////////////////////////////////////////////////////////////// // TextPosTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::TextPosTextAttr::TextPosTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame, nsIContent* aRootElm, nsIContent* aElm) : TTextAttr>(!aFrame && !aElm), mRootElm(aRootElm) { // Get the text-position values for the roots and children. // If we find an ARIA text-position value on a DOM element - searching up // from the supplied root DOM element - use the associated frame as the root // frame. This ensures that we're using the proper root frame for comparison. nsIFrame* ariaFrame = nullptr; Maybe rootAria = GetAriaTextPosValue(aRootElm, ariaFrame); if (rootAria && ariaFrame) { aRootFrame = ariaFrame; } Maybe rootLayout = GetLayoutTextPosValue(aRootFrame); Maybe childLayout; Maybe childAria; if (aFrame) { childLayout = GetLayoutTextPosValue(aFrame); } if (aElm) { childAria = GetAriaTextPosValue(aElm); } // Aria values take precedence over layout values. mIsRootDefined = rootAria || rootLayout; mRootNativeValue = rootAria ? rootAria : rootLayout; mIsDefined = childAria || childLayout; mNativeValue = childAria ? childAria : childLayout; // If there's no child text-position information from ARIA, and the child // layout info is equivalent to the root layout info (i.e., it's inherited), // then we should prefer the root information. if (!childAria && childLayout == rootLayout) { mIsDefined = false; } } void TextAttrsMgr::TextPosTextAttr::ExposeValue( AccAttributes* aAttributes, const Maybe& aValue) { if (aValue.isNothing()) { return; } RefPtr atom = nullptr; switch (*aValue) { case eTextPosBaseline: atom = nsGkAtoms::baseline; break; case eTextPosSub: atom = nsGkAtoms::sub; break; case eTextPosSuper: atom = NS_Atomize("super"); break; } if (atom) { aAttributes->SetAttribute(nsGkAtoms::textPosition, atom); } } Maybe TextAttrsMgr::TextPosTextAttr::GetAriaTextPosValue(nsIContent* aElm) const { nsIFrame* ariaFrame = nullptr; return GetAriaTextPosValue(aElm, ariaFrame); } Maybe TextAttrsMgr::TextPosTextAttr::GetAriaTextPosValue(nsIContent* aElm, nsIFrame*& ariaFrame) const { // Search for the superscript and subscript roles that imply text-position. const nsIContent* elm = aElm; do { if (elm->IsElement()) { const mozilla::dom::Element* domElm = elm->AsElement(); static const dom::Element::AttrValuesArray tokens[] = { nsGkAtoms::subscript, nsGkAtoms::superscript, nullptr}; const int32_t valueIdx = domElm->FindAttrValueIn( kNameSpaceID_None, nsGkAtoms::role, tokens, eCaseMatters); ariaFrame = domElm->GetPrimaryFrame(); if (valueIdx == 0) { return Some(eTextPosSub); } if (valueIdx == 1) { return Some(eTextPosSuper); } } } while ((elm = elm->GetParent()) && elm != mRootElm); ariaFrame = nullptr; return Nothing{}; } Maybe TextAttrsMgr::TextPosTextAttr::GetLayoutTextPosValue(nsIFrame* aFrame) const { const auto& verticalAlign = aFrame->StyleDisplay()->mVerticalAlign; if (verticalAlign.IsKeyword()) { switch (verticalAlign.AsKeyword()) { case StyleVerticalAlignKeyword::Baseline: return Some(eTextPosBaseline); case StyleVerticalAlignKeyword::Sub: return Some(eTextPosSub); case StyleVerticalAlignKeyword::Super: return Some(eTextPosSuper); // No good guess for the rest, so do not expose value of text-position // attribute. default: return Nothing{}; } } const auto& length = verticalAlign.AsLength(); if (length.ConvertsToPercentage()) { const float percentValue = length.ToPercentage(); return percentValue > 0 ? Some(eTextPosSuper) : (percentValue < 0 ? Some(eTextPosSub) : Some(eTextPosBaseline)); } if (length.ConvertsToLength()) { const nscoord coordValue = length.ToLength(); return coordValue > 0 ? Some(eTextPosSuper) : (coordValue < 0 ? Some(eTextPosSub) : Some(eTextPosBaseline)); } if (const nsIContent* content = aFrame->GetContent()) { if (content->IsHTMLElement(nsGkAtoms::sup)) return Some(eTextPosSuper); if (content->IsHTMLElement(nsGkAtoms::sub)) return Some(eTextPosSub); } return Nothing{}; }