| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * Copyright (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) |
| * Copyright (C) 2006, 2007 Nicholas Shanks (webkit@nickshanks.com) |
| * Copyright (C) 2005-2019 Apple Inc. All rights reserved. |
| * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org> |
| * Copyright (C) 2007, 2008 Eric Seidel <eric@webkit.org> |
| * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) |
| * Copyright (c) 2011, Code Aurora Forum. All rights reserved. |
| * Copyright (C) Research In Motion Limited 2011. All rights reserved. |
| * Copyright (C) 2012, 2013 Google Inc. All rights reserved. |
| * Copyright (C) 2014 Igalia S.L. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include "StyleBuilderState.h" |
| |
| #include "CSSCrossfadeValue.h" |
| #include "CSSCursorImageValue.h" |
| #include "CSSFilterImageValue.h" |
| #include "CSSFontSelector.h" |
| #include "CSSFunctionValue.h" |
| #include "CSSGradientValue.h" |
| #include "CSSImageSetValue.h" |
| #include "CSSImageValue.h" |
| #include "CSSShadowValue.h" |
| #include "Document.h" |
| #include "ElementInlines.h" |
| #include "FontCache.h" |
| #include "HTMLElement.h" |
| #include "RenderTheme.h" |
| #include "SVGElementTypeHelpers.h" |
| #include "SVGSVGElement.h" |
| #include "Settings.h" |
| #include "StyleBuilder.h" |
| #include "StyleCachedImage.h" |
| #include "StyleCursorImage.h" |
| #include "StyleFontSizeFunctions.h" |
| #include "StyleGeneratedImage.h" |
| #include "StyleImageSet.h" |
| #include "TransformFunctions.h" |
| |
| namespace WebCore { |
| namespace Style { |
| |
| BuilderState::BuilderState(Builder& builder, RenderStyle& style, BuilderContext&& context) |
| : m_builder(builder) |
| , m_styleMap(*this) |
| , m_style(style) |
| , m_context(WTFMove(context)) |
| , m_cssToLengthConversionData(style, m_context) |
| { |
| } |
| |
| // SVG handles zooming in a different way compared to CSS. The whole document is scaled instead |
| // of each individual length value in the render style / tree. CSSPrimitiveValue::computeLength*() |
| // multiplies each resolved length with the zoom multiplier - so for SVG we need to disable that. |
| // Though all CSS values that can be applied to outermost <svg> elements (width/height/border/padding...) |
| // need to respect the scaling. RenderBox (the parent class of LegacyRenderSVGRoot) grabs values like |
| // width/height/border/padding/... from the RenderStyle -> for SVG these values would never scale, |
| // if we'd pass a 1.0 zoom factor everyhwere. So we only pass a zoom factor of 1.0 for specific |
| // properties that are NOT allowed to scale within a zoomed SVG document (letter/word-spacing/font-size). |
| bool BuilderState::useSVGZoomRules() const |
| { |
| return is<SVGElement>(element()); |
| } |
| |
| bool BuilderState::useSVGZoomRulesForLength() const |
| { |
| return is<SVGElement>(element()) && !(is<SVGSVGElement>(*element()) && element()->parentNode()); |
| } |
| |
| Ref<CSSValue> BuilderState::resolveImageStyles(CSSValue& value) |
| { |
| if (is<CSSCrossfadeValue>(value)) |
| return downcast<CSSCrossfadeValue>(value).valueWithStylesResolved(*this); |
| if (is<CSSCursorImageValue>(value)) |
| return downcast<CSSCursorImageValue>(value).valueWithStylesResolved(*this); |
| if (is<CSSFilterImageValue>(value)) |
| return downcast<CSSFilterImageValue>(value).valueWithStylesResolved(*this); |
| if (is<CSSGradientValue>(value)) |
| return downcast<CSSGradientValue>(value).valueWithStylesResolved(*this); |
| if (is<CSSImageSetValue>(value)) |
| return downcast<CSSImageSetValue>(value).valueWithStylesResolved(*this); |
| if (is<CSSImageValue>(value)) |
| return downcast<CSSImageValue>(value).valueWithStylesResolved(*this); |
| return value; |
| } |
| |
| RefPtr<StyleImage> BuilderState::createStyleImage(CSSValue& value) |
| { |
| if (is<CSSImageValue>(value)) |
| return StyleCachedImage::create(downcast<CSSImageValue>(resolveImageStyles(value).get())); |
| if (is<CSSCursorImageValue>(value)) |
| return StyleCursorImage::create(downcast<CSSCursorImageValue>(resolveImageStyles(value).get())); |
| if (is<CSSImageGeneratorValue>(value)) |
| return StyleGeneratedImage::create(downcast<CSSImageGeneratorValue>(resolveImageStyles(value).get())); |
| if (is<CSSImageSetValue>(value)) |
| return StyleImageSet::create(downcast<CSSImageSetValue>(resolveImageStyles(value).get())); |
| return nullptr; |
| } |
| |
| static FilterOperation::OperationType filterOperationForType(CSSValueID type) |
| { |
| switch (type) { |
| case CSSValueUrl: |
| return FilterOperation::REFERENCE; |
| case CSSValueGrayscale: |
| return FilterOperation::GRAYSCALE; |
| case CSSValueSepia: |
| return FilterOperation::SEPIA; |
| case CSSValueSaturate: |
| return FilterOperation::SATURATE; |
| case CSSValueHueRotate: |
| return FilterOperation::HUE_ROTATE; |
| case CSSValueInvert: |
| return FilterOperation::INVERT; |
| case CSSValueAppleInvertLightness: |
| return FilterOperation::APPLE_INVERT_LIGHTNESS; |
| case CSSValueOpacity: |
| return FilterOperation::OPACITY; |
| case CSSValueBrightness: |
| return FilterOperation::BRIGHTNESS; |
| case CSSValueContrast: |
| return FilterOperation::CONTRAST; |
| case CSSValueBlur: |
| return FilterOperation::BLUR; |
| case CSSValueDropShadow: |
| return FilterOperation::DROP_SHADOW; |
| default: |
| break; |
| } |
| ASSERT_NOT_REACHED(); |
| return FilterOperation::NONE; |
| } |
| |
| bool BuilderState::createFilterOperations(const CSSValue& inValue, FilterOperations& outOperations) |
| { |
| // FIXME: Move this code somewhere else. |
| |
| ASSERT(outOperations.isEmpty()); |
| |
| if (is<CSSPrimitiveValue>(inValue)) { |
| auto& primitiveValue = downcast<CSSPrimitiveValue>(inValue); |
| if (primitiveValue.valueID() == CSSValueNone) |
| return true; |
| } |
| |
| if (!is<CSSValueList>(inValue)) |
| return false; |
| |
| FilterOperations operations; |
| for (auto& currentValue : downcast<CSSValueList>(inValue)) { |
| if (is<CSSPrimitiveValue>(currentValue)) { |
| auto& primitiveValue = downcast<CSSPrimitiveValue>(currentValue.get()); |
| if (!primitiveValue.isURI()) |
| continue; |
| |
| auto filterURL = primitiveValue.stringValue(); |
| auto fragment = document().completeURL(filterURL).fragmentIdentifier().toAtomString(); |
| operations.operations().append(ReferenceFilterOperation::create(filterURL, WTFMove(fragment))); |
| continue; |
| } |
| |
| if (!is<CSSFunctionValue>(currentValue)) |
| continue; |
| |
| auto& filterValue = downcast<CSSFunctionValue>(currentValue.get()); |
| FilterOperation::OperationType operationType = filterOperationForType(filterValue.name()); |
| |
| // Check that all parameters are primitive values, with the |
| // exception of drop shadow which has a CSSShadowValue parameter. |
| const CSSPrimitiveValue* firstValue = nullptr; |
| if (operationType != FilterOperation::DROP_SHADOW) { |
| bool haveNonPrimitiveValue = false; |
| for (unsigned j = 0; j < filterValue.length(); ++j) { |
| if (!is<CSSPrimitiveValue>(*filterValue.itemWithoutBoundsCheck(j))) { |
| haveNonPrimitiveValue = true; |
| break; |
| } |
| } |
| if (haveNonPrimitiveValue) |
| continue; |
| if (filterValue.length()) |
| firstValue = downcast<CSSPrimitiveValue>(filterValue.itemWithoutBoundsCheck(0)); |
| } |
| |
| switch (operationType) { |
| case FilterOperation::GRAYSCALE: |
| case FilterOperation::SEPIA: |
| case FilterOperation::SATURATE: { |
| double amount = 1; |
| if (filterValue.length() == 1) { |
| amount = firstValue->doubleValue(); |
| if (firstValue->isPercentage()) |
| amount /= 100; |
| } |
| |
| operations.operations().append(BasicColorMatrixFilterOperation::create(amount, operationType)); |
| break; |
| } |
| case FilterOperation::HUE_ROTATE: { |
| double angle = 0; |
| if (filterValue.length() == 1) |
| angle = firstValue->computeDegrees(); |
| |
| operations.operations().append(BasicColorMatrixFilterOperation::create(angle, operationType)); |
| break; |
| } |
| case FilterOperation::INVERT: |
| case FilterOperation::BRIGHTNESS: |
| case FilterOperation::CONTRAST: |
| case FilterOperation::OPACITY: { |
| double amount = 1; |
| if (filterValue.length() == 1) { |
| amount = firstValue->doubleValue(); |
| if (firstValue->isPercentage()) |
| amount /= 100; |
| } |
| |
| operations.operations().append(BasicComponentTransferFilterOperation::create(amount, operationType)); |
| break; |
| } |
| case FilterOperation::APPLE_INVERT_LIGHTNESS: { |
| operations.operations().append(InvertLightnessFilterOperation::create()); |
| break; |
| } |
| case FilterOperation::BLUR: { |
| Length stdDeviation = Length(0, LengthType::Fixed); |
| if (filterValue.length() >= 1) |
| stdDeviation = convertToFloatLength(firstValue, cssToLengthConversionData()); |
| if (stdDeviation.isUndefined()) |
| return false; |
| |
| operations.operations().append(BlurFilterOperation::create(stdDeviation)); |
| break; |
| } |
| case FilterOperation::DROP_SHADOW: { |
| if (filterValue.length() != 1) |
| return false; |
| |
| const auto* cssValue = filterValue.itemWithoutBoundsCheck(0); |
| if (!is<CSSShadowValue>(cssValue)) |
| continue; |
| |
| const auto& item = downcast<CSSShadowValue>(*cssValue); |
| int x = item.x->computeLength<int>(cssToLengthConversionData()); |
| int y = item.y->computeLength<int>(cssToLengthConversionData()); |
| IntPoint location(x, y); |
| int blur = item.blur ? item.blur->computeLength<int>(cssToLengthConversionData()) : 0; |
| auto color = item.color ? colorFromPrimitiveValueWithResolvedCurrentColor(*item.color) : m_style.color(); |
| |
| operations.operations().append(DropShadowFilterOperation::create(location, blur, color.isValid() ? color : Color::transparentBlack)); |
| break; |
| } |
| default: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| } |
| |
| outOperations = operations; |
| return true; |
| } |
| |
| bool BuilderState::isColorFromPrimitiveValueDerivedFromElement(const CSSPrimitiveValue& value) |
| { |
| switch (value.valueID()) { |
| case CSSValueWebkitText: |
| case CSSValueWebkitLink: |
| case CSSValueWebkitActivelink: |
| case CSSValueCurrentcolor: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| Color BuilderState::colorFromPrimitiveValue(const CSSPrimitiveValue& value, ForVisitedLink forVisitedLink) const |
| { |
| if (value.isRGBColor()) |
| return value.color(); |
| |
| auto identifier = value.valueID(); |
| switch (identifier) { |
| case CSSValueWebkitText: |
| return document().textColor(); |
| case CSSValueWebkitLink: |
| return (element() && element()->isLink() && forVisitedLink == ForVisitedLink::Yes) ? document().visitedLinkColor() : document().linkColor(); |
| case CSSValueWebkitActivelink: |
| return document().activeLinkColor(); |
| case CSSValueWebkitFocusRingColor: |
| return RenderTheme::singleton().focusRingColor(document().styleColorOptions(&m_style)); |
| case CSSValueCurrentcolor: |
| return RenderStyle::currentColor(); |
| default: |
| return StyleColor::colorFromKeyword(identifier, document().styleColorOptions(&m_style)); |
| } |
| } |
| |
| Color BuilderState::colorFromPrimitiveValueWithResolvedCurrentColor(const CSSPrimitiveValue& value) const |
| { |
| // FIXME: 'currentcolor' should be resolved at use time to make it inherit correctly. https://bugs.webkit.org/show_bug.cgi?id=210005 |
| if (value.valueID() == CSSValueCurrentcolor) { |
| // Color is an inherited property so depending on it effectively makes the property inherited. |
| m_style.setHasExplicitlyInheritedProperties(); |
| m_style.setDisallowsFastPathInheritance(); |
| return m_style.color(); |
| } |
| |
| return colorFromPrimitiveValue(value); |
| } |
| |
| void BuilderState::registerContentAttribute(const AtomString& attributeLocalName) |
| { |
| if (style().styleType() == PseudoId::Before || style().styleType() == PseudoId::After) |
| m_registeredContentAttributes.append(attributeLocalName); |
| } |
| |
| void BuilderState::adjustStyleForInterCharacterRuby() |
| { |
| if (m_style.rubyPosition() != RubyPosition::InterCharacter || !element() || !element()->hasTagName(HTMLNames::rtTag)) |
| return; |
| |
| m_style.setTextAlign(TextAlignMode::Center); |
| if (m_style.isHorizontalWritingMode()) |
| m_style.setWritingMode(WritingMode::LeftToRight); |
| } |
| |
| void BuilderState::updateFont() |
| { |
| auto& fontSelector = const_cast<Document&>(document()).fontSelector(); |
| |
| auto needsUpdate = [&] { |
| if (m_fontDirty) |
| return true; |
| auto* fonts = m_style.fontCascade().fonts(); |
| if (!fonts) |
| return true; |
| return false; |
| }; |
| |
| if (!needsUpdate()) |
| return; |
| |
| #if ENABLE(TEXT_AUTOSIZING) |
| updateFontForTextSizeAdjust(); |
| #endif |
| updateFontForGenericFamilyChange(); |
| updateFontForZoomChange(); |
| updateFontForOrientationChange(); |
| |
| m_style.fontCascade().update(&fontSelector); |
| |
| m_fontDirty = false; |
| } |
| |
| #if ENABLE(TEXT_AUTOSIZING) |
| void BuilderState::updateFontForTextSizeAdjust() |
| { |
| if (m_style.textSizeAdjust().isAuto() |
| || !document().settings().textAutosizingEnabled() |
| || (document().settings().textAutosizingUsesIdempotentMode() |
| && !m_style.textSizeAdjust().isNone() |
| && !document().settings().idempotentModeAutosizingOnlyHonorsPercentages())) |
| return; |
| |
| auto newFontDescription = m_style.fontDescription(); |
| if (!m_style.textSizeAdjust().isNone()) |
| newFontDescription.setComputedSize(newFontDescription.specifiedSize() * m_style.textSizeAdjust().multiplier()); |
| else |
| newFontDescription.setComputedSize(newFontDescription.specifiedSize()); |
| |
| m_style.setFontDescription(WTFMove(newFontDescription)); |
| } |
| #endif |
| |
| void BuilderState::updateFontForZoomChange() |
| { |
| if (m_style.effectiveZoom() == parentStyle().effectiveZoom() && m_style.textZoom() == parentStyle().textZoom()) |
| return; |
| |
| const auto& childFont = m_style.fontDescription(); |
| auto newFontDescription = childFont; |
| setFontSize(newFontDescription, childFont.specifiedSize()); |
| |
| m_style.setFontDescription(WTFMove(newFontDescription)); |
| } |
| |
| void BuilderState::updateFontForGenericFamilyChange() |
| { |
| const auto& childFont = m_style.fontDescription(); |
| |
| if (childFont.isAbsoluteSize()) |
| return; |
| |
| const auto& parentFont = parentStyle().fontDescription(); |
| if (childFont.useFixedDefaultSize() == parentFont.useFixedDefaultSize()) |
| return; |
| |
| // We know the parent is monospace or the child is monospace, and that font |
| // size was unspecified. We want to scale our font size as appropriate. |
| // If the font uses a keyword size, then we refetch from the table rather than |
| // multiplying by our scale factor. |
| float size = [&] { |
| if (CSSValueID sizeIdentifier = childFont.keywordSizeAsIdentifier()) |
| return Style::fontSizeForKeyword(sizeIdentifier, childFont.useFixedDefaultSize(), document()); |
| |
| auto fixedSize = document().settings().defaultFixedFontSize(); |
| auto defaultSize = document().settings().defaultFontSize(); |
| float fixedScaleFactor = (fixedSize && defaultSize) ? static_cast<float>(fixedSize) / defaultSize : 1; |
| return parentFont.useFixedDefaultSize() ? childFont.specifiedSize() / fixedScaleFactor : childFont.specifiedSize() * fixedScaleFactor; |
| }(); |
| |
| auto newFontDescription = childFont; |
| setFontSize(newFontDescription, size); |
| m_style.setFontDescription(WTFMove(newFontDescription)); |
| } |
| |
| void BuilderState::updateFontForOrientationChange() |
| { |
| auto [fontOrientation, glyphOrientation] = m_style.fontAndGlyphOrientation(); |
| |
| const auto& fontDescription = m_style.fontDescription(); |
| if (fontDescription.orientation() == fontOrientation && fontDescription.nonCJKGlyphOrientation() == glyphOrientation) |
| return; |
| |
| auto newFontDescription = fontDescription; |
| newFontDescription.setNonCJKGlyphOrientation(glyphOrientation); |
| newFontDescription.setOrientation(fontOrientation); |
| m_style.setFontDescription(WTFMove(newFontDescription)); |
| } |
| |
| void BuilderState::setFontSize(FontCascadeDescription& fontDescription, float size) |
| { |
| fontDescription.setSpecifiedSize(size); |
| fontDescription.setComputedSize(Style::computedFontSizeFromSpecifiedSize(size, fontDescription.isAbsoluteSize(), useSVGZoomRules(), &style(), document())); |
| } |
| |
| } |
| } |