/* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Dirk Mueller ( mueller@kde.org ) * Copyright (C) 2003-2021 Apple Inc. All rights reserved. * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net) * * 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 "Length.h" #include "AnimationUtilities.h" #include "CalcExpressionBlendLength.h" #include "CalcExpressionLength.h" #include "CalcExpressionOperation.h" #include "CalculationValue.h" #include #include #include #include #include #include #include #include namespace WebCore { static Length parseLength(const UChar* data, unsigned length) { if (length == 0) return Length(1, LengthType::Relative); unsigned i = 0; while (i < length && isSpaceOrNewline(data[i])) ++i; if (i < length && (data[i] == '+' || data[i] == '-')) ++i; while (i < length && isASCIIDigit(data[i])) ++i; unsigned intLength = i; while (i < length && (isASCIIDigit(data[i]) || data[i] == '.')) ++i; unsigned doubleLength = i; // IE quirk: Skip whitespace between the number and the % character (20 % => 20%). while (i < length && isSpaceOrNewline(data[i])) ++i; bool ok; UChar next = (i < length) ? data[i] : ' '; if (next == '%') { // IE quirk: accept decimal fractions for percentages. double r = charactersToDouble(data, doubleLength, &ok); if (ok) return Length(r, LengthType::Percent); return Length(1, LengthType::Relative); } auto r = parseInteger({ data, intLength }); if (next == '*') return Length(r.value_or(1), LengthType::Relative); if (r) return Length(*r, LengthType::Fixed); return Length(0, LengthType::Relative); } static unsigned countCharacter(StringImpl& string, UChar character) { unsigned count = 0; unsigned length = string.length(); for (unsigned i = 0; i < length; ++i) count += string[i] == character; return count; } UniqueArray newCoordsArray(const String& string, int& len) { unsigned length = string.length(); LChar* spacifiedCharacters; auto str = StringImpl::createUninitialized(length, spacifiedCharacters); for (unsigned i = 0; i < length; i++) { UChar cc = string[i]; if (cc > '9' || (cc < '0' && cc != '-' && cc != '*' && cc != '.')) spacifiedCharacters[i] = ' '; else spacifiedCharacters[i] = cc; } str = str->simplifyWhiteSpace(); len = countCharacter(str, ' ') + 1; auto r = makeUniqueArray(len); int i = 0; unsigned pos = 0; size_t pos2; while ((pos2 = str->find(' ', pos)) != notFound) { r[i++] = parseLength(str->characters16() + pos, pos2 - pos); pos = pos2+1; } r[i] = parseLength(str->characters16() + pos, str->length() - pos); ASSERT(i == len - 1); return r; } UniqueArray newLengthArray(const String& string, int& len) { RefPtr str = string.impl()->simplifyWhiteSpace(); if (!str->length()) { len = 1; return nullptr; } len = countCharacter(*str, ',') + 1; auto r = makeUniqueArray(len); int i = 0; unsigned pos = 0; size_t pos2; auto upconvertedCharacters = StringView(str.get()).upconvertedCharacters(); while ((pos2 = str->find(',', pos)) != notFound) { r[i++] = parseLength(upconvertedCharacters + pos, pos2 - pos); pos = pos2+1; } ASSERT(i == len - 1); // IE Quirk: If the last comma is the last char skip it and reduce len by one. if (str->length()-pos > 0) r[i] = parseLength(upconvertedCharacters + pos, str->length() - pos); else len--; return r; } class CalculationValueMap { public: CalculationValueMap(); unsigned insert(Ref&&); void ref(unsigned handle); void deref(unsigned handle); CalculationValue& get(unsigned handle) const; private: struct Entry { uint64_t referenceCountMinusOne; CalculationValue* value; Entry(); Entry(CalculationValue&); }; unsigned m_nextAvailableHandle; HashMap m_map; }; inline CalculationValueMap::Entry::Entry() : referenceCountMinusOne(0) , value(nullptr) { } inline CalculationValueMap::Entry::Entry(CalculationValue& value) : referenceCountMinusOne(0) , value(&value) { } inline CalculationValueMap::CalculationValueMap() : m_nextAvailableHandle(1) { } inline unsigned CalculationValueMap::insert(Ref&& value) { ASSERT(m_nextAvailableHandle); // The leakRef below is balanced by the adoptRef in the deref member function. Entry leakedValue = value.leakRef(); // FIXME: This monotonically increasing handle generation scheme is potentially wasteful // of the handle space. Consider reusing empty handles. https://bugs.webkit.org/show_bug.cgi?id=80489 while (!m_map.isValidKey(m_nextAvailableHandle) || !m_map.add(m_nextAvailableHandle, leakedValue).isNewEntry) ++m_nextAvailableHandle; return m_nextAvailableHandle++; } inline CalculationValue& CalculationValueMap::get(unsigned handle) const { ASSERT(m_map.contains(handle)); return *m_map.find(handle)->value.value; } inline void CalculationValueMap::ref(unsigned handle) { ASSERT(m_map.contains(handle)); ++m_map.find(handle)->value.referenceCountMinusOne; } inline void CalculationValueMap::deref(unsigned handle) { ASSERT(m_map.contains(handle)); auto it = m_map.find(handle); if (it->value.referenceCountMinusOne) { --it->value.referenceCountMinusOne; return; } // The adoptRef here is balanced by the leakRef in the insert member function. Ref value { adoptRef(*it->value.value) }; m_map.remove(it); } static CalculationValueMap& calculationValues() { static NeverDestroyed map; return map; } Length::Length(Ref&& value) : m_hasQuirk(false) , m_type(LengthType::Calculated) , m_isFloat(false) { m_calculationValueHandle = calculationValues().insert(WTFMove(value)); } CalculationValue& Length::calculationValue() const { ASSERT(isCalculated()); return calculationValues().get(m_calculationValueHandle); } void Length::ref() const { ASSERT(isCalculated()); calculationValues().ref(m_calculationValueHandle); } void Length::deref() const { ASSERT(isCalculated()); calculationValues().deref(m_calculationValueHandle); } float Length::nonNanCalculatedValue(float maxValue) const { ASSERT(isCalculated()); float result = calculationValue().evaluate(maxValue); if (std::isnan(result)) return 0; return result; } bool Length::isCalculatedEqual(const Length& other) const { return calculationValue() == other.calculationValue(); } Length convertTo100PercentMinusLength(const Length& length) { if (length.isPercent()) return Length(100 - length.value(), LengthType::Percent); // Turn this into a calc expression: calc(100% - length) Vector> lengths; lengths.reserveInitialCapacity(2); lengths.uncheckedAppend(makeUnique(Length(100, LengthType::Percent))); lengths.uncheckedAppend(makeUnique(length)); auto op = makeUnique(WTFMove(lengths), CalcOperator::Subtract); return Length(CalculationValue::create(WTFMove(op), ValueRange::All)); } static Length blendMixedTypes(const Length& from, const Length& to, const BlendingContext& context) { if (!to.isCalculated() && !from.isPercent() && (context.progress == 1 || from.isZero())) return blend(Length(0, to.type()), to, context); if (!from.isCalculated() && !to.isPercent() && (!context.progress || to.isZero())) return blend(from, Length(0, from.type()), context); auto blend = makeUnique(from, to, context.progress); return Length(CalculationValue::create(WTFMove(blend), ValueRange::All)); } Length blend(const Length& from, const Length& to, const BlendingContext& context) { if ((from.isAuto() || to.isAuto()) || (from.isUndefined() || to.isUndefined())) return context.progress < 0.5 ? from : to; if (from.isCalculated() || to.isCalculated() || (from.type() != to.type())) return blendMixedTypes(from, to, context); if (!context.progress) return from; if (context.progress == 1) return to; LengthType resultType = to.type(); if (to.isZero()) resultType = from.type(); if (resultType == LengthType::Percent) { float fromPercent = from.isZero() ? 0 : from.percent(); float toPercent = to.isZero() ? 0 : to.percent(); return Length(WebCore::blend(fromPercent, toPercent, context), LengthType::Percent); } float fromValue = from.isZero() ? 0 : from.value(); float toValue = to.isZero() ? 0 : to.value(); return Length(WebCore::blend(fromValue, toValue, context), resultType); } Length blend(const Length& from, const Length& to, const BlendingContext& context, ValueRange valueRange) { auto blended = blend(from, to, context); if (valueRange == ValueRange::NonNegative && blended.isNegative()) return { 0, from.isZero () ? to.type() : from.type() }; return blended; } struct SameSizeAsLength { int32_t value; int32_t metaData; }; COMPILE_ASSERT(sizeof(Length) == sizeof(SameSizeAsLength), length_should_stay_small); static TextStream& operator<<(TextStream& ts, LengthType type) { switch (type) { case LengthType::Auto: ts << "auto"; break; case LengthType::Relative: ts << "relative"; break; case LengthType::Percent: ts << "percent"; break; case LengthType::Fixed: ts << "fixed"; break; case LengthType::Intrinsic: ts << "intrinsic"; break; case LengthType::MinIntrinsic: ts << "min-intrinsic"; break; case LengthType::MinContent: ts << "min-content"; break; case LengthType::MaxContent: ts << "max-content"; break; case LengthType::FillAvailable: ts << "fill-available"; break; case LengthType::FitContent: ts << "fit-content"; break; case LengthType::Calculated: ts << "calc"; break; case LengthType::Undefined: ts << "undefined"; break; } return ts; } TextStream& operator<<(TextStream& ts, Length length) { switch (length.type()) { case LengthType::Auto: case LengthType::Undefined: ts << length.type(); break; case LengthType::Fixed: ts << TextStream::FormatNumberRespectingIntegers(length.value()) << "px"; break; case LengthType::Relative: case LengthType::Intrinsic: case LengthType::MinIntrinsic: case LengthType::MinContent: case LengthType::MaxContent: case LengthType::FillAvailable: case LengthType::FitContent: ts << length.type() << " " << TextStream::FormatNumberRespectingIntegers(length.value()); break; case LengthType::Percent: ts << TextStream::FormatNumberRespectingIntegers(length.percent()) << "%"; break; case LengthType::Calculated: ts << length.calculationValue(); break; } if (length.hasQuirk()) ts << " has-quirk"; return ts; } } // namespace WebCore