blob: b6792a261ae995de70cdc7e0572cb1f10d3627ff [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/animation/CSSImageSliceInterpolationType.h"
#include "core/animation/CSSLengthInterpolationType.h"
#include "core/animation/ImageSlicePropertyFunctions.h"
#include "core/css/CSSBorderImageSliceValue.h"
#include "core/css/resolver/StyleResolverState.h"
#include "wtf/PtrUtil.h"
#include <memory>
namespace blink {
namespace {
enum SideIndex : unsigned {
SideTop,
SideRight,
SideBottom,
SideLeft,
SideIndexCount,
};
struct SliceTypes {
explicit SliceTypes(const ImageSlice& slice) {
isNumber[SideTop] = slice.slices.top().isFixed();
isNumber[SideRight] = slice.slices.right().isFixed();
isNumber[SideBottom] = slice.slices.bottom().isFixed();
isNumber[SideLeft] = slice.slices.left().isFixed();
fill = slice.fill;
}
explicit SliceTypes(const CSSBorderImageSliceValue& slice) {
isNumber[SideTop] = slice.slices().top()->isPrimitiveValue() &&
toCSSPrimitiveValue(slice.slices().top())->isNumber();
isNumber[SideRight] =
slice.slices().right()->isPrimitiveValue() &&
toCSSPrimitiveValue(slice.slices().right())->isNumber();
isNumber[SideBottom] =
slice.slices().bottom()->isPrimitiveValue() &&
toCSSPrimitiveValue(slice.slices().bottom())->isNumber();
isNumber[SideLeft] = slice.slices().left()->isPrimitiveValue() &&
toCSSPrimitiveValue(slice.slices().left())->isNumber();
fill = slice.fill();
}
bool operator==(const SliceTypes& other) const {
for (size_t i = 0; i < SideIndexCount; i++) {
if (isNumber[i] != other.isNumber[i])
return false;
}
return fill == other.fill;
}
bool operator!=(const SliceTypes& other) const { return !(*this == other); }
// If a side is not a number then it is a percentage.
bool isNumber[SideIndexCount];
bool fill;
};
} // namespace
class CSSImageSliceNonInterpolableValue : public NonInterpolableValue {
public:
static PassRefPtr<CSSImageSliceNonInterpolableValue> create(
const SliceTypes& types) {
return adoptRef(new CSSImageSliceNonInterpolableValue(types));
}
const SliceTypes& types() const { return m_types; }
DECLARE_NON_INTERPOLABLE_VALUE_TYPE();
private:
CSSImageSliceNonInterpolableValue(const SliceTypes& types) : m_types(types) {}
const SliceTypes m_types;
};
DEFINE_NON_INTERPOLABLE_VALUE_TYPE(CSSImageSliceNonInterpolableValue);
DEFINE_NON_INTERPOLABLE_VALUE_TYPE_CASTS(CSSImageSliceNonInterpolableValue);
namespace {
class UnderlyingSliceTypesChecker
: public InterpolationType::ConversionChecker {
public:
static std::unique_ptr<UnderlyingSliceTypesChecker> create(
const SliceTypes& underlyingTypes) {
return wrapUnique(new UnderlyingSliceTypesChecker(underlyingTypes));
}
static SliceTypes getUnderlyingSliceTypes(
const InterpolationValue& underlying) {
return toCSSImageSliceNonInterpolableValue(*underlying.nonInterpolableValue)
.types();
}
private:
UnderlyingSliceTypesChecker(const SliceTypes& underlyingTypes)
: m_underlyingTypes(underlyingTypes) {}
bool isValid(const InterpolationEnvironment&,
const InterpolationValue& underlying) const final {
return m_underlyingTypes == getUnderlyingSliceTypes(underlying);
}
const SliceTypes m_underlyingTypes;
};
class InheritedSliceTypesChecker : public InterpolationType::ConversionChecker {
public:
static std::unique_ptr<InheritedSliceTypesChecker> create(
CSSPropertyID property,
const SliceTypes& inheritedTypes) {
return wrapUnique(new InheritedSliceTypesChecker(property, inheritedTypes));
}
private:
InheritedSliceTypesChecker(CSSPropertyID property,
const SliceTypes& inheritedTypes)
: m_property(property), m_inheritedTypes(inheritedTypes) {}
bool isValid(const InterpolationEnvironment& environment,
const InterpolationValue& underlying) const final {
return m_inheritedTypes ==
SliceTypes(ImageSlicePropertyFunctions::getImageSlice(
m_property, *environment.state().parentStyle()));
}
const CSSPropertyID m_property;
const SliceTypes m_inheritedTypes;
};
InterpolationValue convertImageSlice(const ImageSlice& slice, double zoom) {
std::unique_ptr<InterpolableList> list =
InterpolableList::create(SideIndexCount);
const Length* sides[SideIndexCount] = {};
sides[SideTop] = &slice.slices.top();
sides[SideRight] = &slice.slices.right();
sides[SideBottom] = &slice.slices.bottom();
sides[SideLeft] = &slice.slices.left();
for (size_t i = 0; i < SideIndexCount; i++) {
const Length& side = *sides[i];
list->set(i, InterpolableNumber::create(
side.isFixed() ? side.pixels() / zoom : side.percent()));
}
return InterpolationValue(
std::move(list),
CSSImageSliceNonInterpolableValue::create(SliceTypes(slice)));
}
} // namespace
InterpolationValue CSSImageSliceInterpolationType::maybeConvertNeutral(
const InterpolationValue& underlying,
ConversionCheckers& conversionCheckers) const {
SliceTypes underlyingTypes =
UnderlyingSliceTypesChecker::getUnderlyingSliceTypes(underlying);
conversionCheckers.append(
UnderlyingSliceTypesChecker::create(underlyingTypes));
LengthBox zeroBox(
Length(0, underlyingTypes.isNumber[SideTop] ? Fixed : Percent),
Length(0, underlyingTypes.isNumber[SideRight] ? Fixed : Percent),
Length(0, underlyingTypes.isNumber[SideBottom] ? Fixed : Percent),
Length(0, underlyingTypes.isNumber[SideLeft] ? Fixed : Percent));
return convertImageSlice(ImageSlice(zeroBox, underlyingTypes.fill), 1);
}
InterpolationValue CSSImageSliceInterpolationType::maybeConvertInitial(
const StyleResolverState&,
ConversionCheckers& conversionCheckers) const {
return convertImageSlice(
ImageSlicePropertyFunctions::getInitialImageSlice(cssProperty()), 1);
}
InterpolationValue CSSImageSliceInterpolationType::maybeConvertInherit(
const StyleResolverState& state,
ConversionCheckers& conversionCheckers) const {
const ImageSlice& inheritedImageSlice =
ImageSlicePropertyFunctions::getImageSlice(cssProperty(),
*state.parentStyle());
conversionCheckers.append(InheritedSliceTypesChecker::create(
cssProperty(), SliceTypes(inheritedImageSlice)));
return convertImageSlice(inheritedImageSlice,
state.parentStyle()->effectiveZoom());
}
InterpolationValue CSSImageSliceInterpolationType::maybeConvertValue(
const CSSValue& value,
const StyleResolverState&,
ConversionCheckers&) const {
if (!value.isBorderImageSliceValue())
return nullptr;
const CSSBorderImageSliceValue& slice = toCSSBorderImageSliceValue(value);
std::unique_ptr<InterpolableList> list =
InterpolableList::create(SideIndexCount);
const CSSValue* sides[SideIndexCount];
sides[SideTop] = slice.slices().top();
sides[SideRight] = slice.slices().right();
sides[SideBottom] = slice.slices().bottom();
sides[SideLeft] = slice.slices().left();
for (size_t i = 0; i < SideIndexCount; i++) {
const CSSPrimitiveValue& side = *toCSSPrimitiveValue(sides[i]);
DCHECK(side.isNumber() || side.isPercentage());
list->set(i, InterpolableNumber::create(side.getDoubleValue()));
}
return InterpolationValue(
std::move(list),
CSSImageSliceNonInterpolableValue::create(SliceTypes(slice)));
}
InterpolationValue CSSImageSliceInterpolationType::maybeConvertUnderlyingValue(
const InterpolationEnvironment& environment) const {
const ComputedStyle& style = *environment.state().style();
return convertImageSlice(
ImageSlicePropertyFunctions::getImageSlice(cssProperty(), style),
style.effectiveZoom());
}
PairwiseInterpolationValue CSSImageSliceInterpolationType::maybeMergeSingles(
InterpolationValue&& start,
InterpolationValue&& end) const {
const SliceTypes& startSliceTypes =
toCSSImageSliceNonInterpolableValue(*start.nonInterpolableValue).types();
const SliceTypes& endSliceTypes =
toCSSImageSliceNonInterpolableValue(*end.nonInterpolableValue).types();
if (startSliceTypes != endSliceTypes)
return nullptr;
return PairwiseInterpolationValue(std::move(start.interpolableValue),
std::move(end.interpolableValue),
start.nonInterpolableValue.release());
}
void CSSImageSliceInterpolationType::composite(
UnderlyingValueOwner& underlyingValueOwner,
double underlyingFraction,
const InterpolationValue& value,
double interpolationFraction) const {
const SliceTypes& underlyingTypes =
toCSSImageSliceNonInterpolableValue(
*underlyingValueOwner.value().nonInterpolableValue)
.types();
const SliceTypes& types =
toCSSImageSliceNonInterpolableValue(*value.nonInterpolableValue).types();
if (underlyingTypes == types)
underlyingValueOwner.mutableValue().interpolableValue->scaleAndAdd(
underlyingFraction, *value.interpolableValue);
else
underlyingValueOwner.set(*this, value);
}
void CSSImageSliceInterpolationType::apply(
const InterpolableValue& interpolableValue,
const NonInterpolableValue* nonInterpolableValue,
InterpolationEnvironment& environment) const {
ComputedStyle& style = *environment.state().style();
const InterpolableList& list = toInterpolableList(interpolableValue);
const SliceTypes& types =
toCSSImageSliceNonInterpolableValue(nonInterpolableValue)->types();
const auto& convertSide = [&types, &list, &style](size_t index) {
float value =
clampTo<float>(toInterpolableNumber(list.get(index))->value(), 0);
return types.isNumber[index] ? Length(value * style.effectiveZoom(), Fixed)
: Length(value, Percent);
};
LengthBox box(convertSide(SideTop), convertSide(SideRight),
convertSide(SideBottom), convertSide(SideLeft));
ImageSlicePropertyFunctions::setImageSlice(cssProperty(), style,
ImageSlice(box, types.fill));
}
} // namespace blink