407 lines
12 KiB
C++
407 lines
12 KiB
C++
/*
|
|
* 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 <wtf/ASCIICType.h>
|
|
#include <wtf/HashMap.h>
|
|
#include <wtf/MallocPtr.h>
|
|
#include <wtf/NeverDestroyed.h>
|
|
#include <wtf/StdLibExtras.h>
|
|
#include <wtf/text/StringToIntegerConversion.h>
|
|
#include <wtf/text/StringView.h>
|
|
#include <wtf/text/TextStream.h>
|
|
|
|
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<int>({ 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<Length> 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<Length>(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<Length> newLengthArray(const String& string, int& len)
|
|
{
|
|
RefPtr<StringImpl> str = string.impl()->simplifyWhiteSpace();
|
|
if (!str->length()) {
|
|
len = 1;
|
|
return nullptr;
|
|
}
|
|
|
|
len = countCharacter(*str, ',') + 1;
|
|
auto r = makeUniqueArray<Length>(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<CalculationValue>&&);
|
|
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<unsigned, Entry> 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<CalculationValue>&& 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<CalculationValue> value { adoptRef(*it->value.value) };
|
|
|
|
m_map.remove(it);
|
|
}
|
|
|
|
static CalculationValueMap& calculationValues()
|
|
{
|
|
static NeverDestroyed<CalculationValueMap> map;
|
|
return map;
|
|
}
|
|
|
|
Length::Length(Ref<CalculationValue>&& 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<std::unique_ptr<CalcExpressionNode>> lengths;
|
|
lengths.reserveInitialCapacity(2);
|
|
lengths.uncheckedAppend(makeUnique<CalcExpressionLength>(Length(100, LengthType::Percent)));
|
|
lengths.uncheckedAppend(makeUnique<CalcExpressionLength>(length));
|
|
auto op = makeUnique<CalcExpressionOperation>(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<CalcExpressionBlendLength>(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
|