blob: e7a14b9af0a1eb4f5483066da1d418b97d06003d [file] [log] [blame]
/*
* Copyright (C) 2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "TextFlags.h"
#include <algorithm>
#include <tuple>
#include <wtf/Hasher.h>
#include <wtf/text/TextStream.h>
namespace WebCore {
// Unclamped, unchecked, signed fixed-point number representing a value used for font variations.
// Sixteen bits in total, one sign bit, two fractional bits, smallest positive value is 0.25,
// maximum value is 8191.75, and minimum value is -8192.
class FontSelectionValue {
public:
using BackingType = int16_t;
FontSelectionValue() = default;
// Explicit because it won't work correctly for values outside the representable range.
explicit constexpr FontSelectionValue(int);
// Explicit because it won't work correctly for values outside the representable range and because precision can be lost.
explicit constexpr FontSelectionValue(float);
// Precision can be lost, but value will be clamped to the representable range.
static constexpr FontSelectionValue clampFloat(float);
// Since floats have 23 mantissa bits, every value can be represented losslessly.
constexpr operator float() const;
static constexpr FontSelectionValue maximumValue();
static constexpr FontSelectionValue minimumValue();
friend constexpr FontSelectionValue operator+(FontSelectionValue, FontSelectionValue);
friend constexpr FontSelectionValue operator-(FontSelectionValue, FontSelectionValue);
friend constexpr FontSelectionValue operator*(FontSelectionValue, FontSelectionValue);
friend constexpr FontSelectionValue operator/(FontSelectionValue, FontSelectionValue);
friend constexpr FontSelectionValue operator-(FontSelectionValue);
constexpr BackingType rawValue() const { return m_backing; }
template<class Encoder>
void encode(Encoder&) const;
template<class Decoder>
static std::optional<FontSelectionValue> decode(Decoder&);
private:
enum class RawTag { RawTag };
constexpr FontSelectionValue(int, RawTag);
static constexpr int fractionalEntropy = 4;
BackingType m_backing { 0 };
};
template<class Encoder>
void FontSelectionValue::encode(Encoder& encoder) const
{
encoder << m_backing;
}
template<class Decoder>
std::optional<FontSelectionValue> FontSelectionValue::decode(Decoder& decoder)
{
std::optional<FontSelectionValue::BackingType> backing;
decoder >> backing;
if (!backing)
return std::nullopt;
FontSelectionValue result;
result.m_backing = *backing;
return result;
}
constexpr FontSelectionValue::FontSelectionValue(int x)
: m_backing(x * fractionalEntropy)
{
// FIXME: Should we assert the passed in value was in range?
}
constexpr FontSelectionValue::FontSelectionValue(float x)
: m_backing(x * fractionalEntropy)
{
// FIXME: Should we assert the passed in value was in range?
}
constexpr FontSelectionValue::operator float() const
{
return m_backing / static_cast<float>(fractionalEntropy);
}
constexpr FontSelectionValue FontSelectionValue::maximumValue()
{
return { std::numeric_limits<BackingType>::max(), RawTag::RawTag };
}
constexpr FontSelectionValue FontSelectionValue::minimumValue()
{
return { std::numeric_limits<BackingType>::min(), RawTag::RawTag };
}
constexpr FontSelectionValue FontSelectionValue::clampFloat(float value)
{
return FontSelectionValue { std::max<float>(minimumValue(), std::min<float>(value, maximumValue())) };
}
constexpr FontSelectionValue::FontSelectionValue(int rawValue, RawTag)
: m_backing(rawValue)
{
}
constexpr FontSelectionValue operator+(FontSelectionValue a, FontSelectionValue b)
{
return { a.m_backing + b.m_backing, FontSelectionValue::RawTag::RawTag };
}
constexpr FontSelectionValue operator-(FontSelectionValue a, FontSelectionValue b)
{
return { a.m_backing - b.m_backing, FontSelectionValue::RawTag::RawTag };
}
constexpr FontSelectionValue operator*(FontSelectionValue a, FontSelectionValue b)
{
return { a.m_backing * b.m_backing / FontSelectionValue::fractionalEntropy, FontSelectionValue::RawTag::RawTag };
}
constexpr FontSelectionValue operator/(FontSelectionValue a, FontSelectionValue b)
{
return { a.m_backing * FontSelectionValue::fractionalEntropy / b.m_backing, FontSelectionValue::RawTag::RawTag };
}
constexpr FontSelectionValue operator-(FontSelectionValue value)
{
return { -value.m_backing, FontSelectionValue::RawTag::RawTag };
}
constexpr bool operator==(FontSelectionValue a, FontSelectionValue b)
{
return a.rawValue() == b.rawValue();
}
constexpr bool operator!=(FontSelectionValue a, FontSelectionValue b)
{
return a.rawValue() != b.rawValue();
}
constexpr bool operator<(FontSelectionValue a, FontSelectionValue b)
{
return a.rawValue() < b.rawValue();
}
constexpr bool operator<=(FontSelectionValue a, FontSelectionValue b)
{
return a.rawValue() <= b.rawValue();
}
constexpr bool operator>(FontSelectionValue a, FontSelectionValue b)
{
return a.rawValue() > b.rawValue();
}
constexpr bool operator>=(FontSelectionValue a, FontSelectionValue b)
{
return a.rawValue() >= b.rawValue();
}
constexpr FontSelectionValue italicThreshold()
{
return FontSelectionValue { 20 };
}
constexpr bool isItalic(std::optional<FontSelectionValue> fontWeight)
{
return fontWeight && fontWeight.value() >= italicThreshold();
}
constexpr FontSelectionValue normalItalicValue()
{
return FontSelectionValue { 0 };
}
constexpr FontSelectionValue italicValue()
{
return FontSelectionValue { 20 };
}
constexpr FontSelectionValue boldThreshold()
{
return FontSelectionValue { 600 };
}
constexpr FontSelectionValue boldWeightValue()
{
return FontSelectionValue { 700 };
}
constexpr FontSelectionValue normalWeightValue()
{
return FontSelectionValue { 400 };
}
constexpr FontSelectionValue lightWeightValue()
{
return FontSelectionValue { 200 };
}
constexpr bool isFontWeightBold(FontSelectionValue fontWeight)
{
return fontWeight >= boldThreshold();
}
constexpr FontSelectionValue lowerWeightSearchThreshold()
{
return FontSelectionValue { 400 };
}
constexpr FontSelectionValue upperWeightSearchThreshold()
{
return FontSelectionValue { 500 };
}
constexpr FontSelectionValue ultraCondensedStretchValue()
{
return FontSelectionValue { 50 };
}
constexpr FontSelectionValue extraCondensedStretchValue()
{
return FontSelectionValue { 62.5f };
}
constexpr FontSelectionValue condensedStretchValue()
{
return FontSelectionValue { 75 };
}
constexpr FontSelectionValue semiCondensedStretchValue()
{
return FontSelectionValue { 87.5f };
}
constexpr FontSelectionValue normalStretchValue()
{
return FontSelectionValue { 100 };
}
constexpr FontSelectionValue semiExpandedStretchValue()
{
return FontSelectionValue { 112.5f };
}
constexpr FontSelectionValue expandedStretchValue()
{
return FontSelectionValue { 125 };
}
constexpr FontSelectionValue extraExpandedStretchValue()
{
return FontSelectionValue { 150 };
}
constexpr FontSelectionValue ultraExpandedStretchValue()
{
return FontSelectionValue { 200 };
}
inline void add(Hasher& hasher, const FontSelectionValue& value)
{
add(hasher, value.rawValue());
}
// [Inclusive, Inclusive]
struct FontSelectionRange {
using Value = FontSelectionValue;
constexpr FontSelectionRange(Value minimum, Value maximum)
: minimum(minimum)
, maximum(maximum)
{
}
explicit constexpr FontSelectionRange(Value value)
: minimum(value)
, maximum(value)
{
}
constexpr bool operator==(const FontSelectionRange& other) const
{
return WTF::tie(minimum, maximum) == WTF::tie(other.minimum, other.maximum);
}
constexpr bool isValid() const
{
return minimum <= maximum;
}
void expand(const FontSelectionRange& other)
{
ASSERT(other.isValid());
if (!isValid())
*this = other;
else {
minimum = std::min(minimum, other.minimum);
maximum = std::max(maximum, other.maximum);
}
ASSERT(isValid());
}
constexpr bool includes(Value target) const
{
return target >= minimum && target <= maximum;
}
template<class Encoder>
void encode(Encoder&) const;
template<class Decoder>
static std::optional<FontSelectionRange> decode(Decoder&);
Value minimum { 1 };
Value maximum { 0 };
};
template<class Encoder>
void FontSelectionRange::encode(Encoder& encoder) const
{
encoder << minimum;
encoder << maximum;
}
template<class Decoder>
std::optional<FontSelectionRange> FontSelectionRange::decode(Decoder& decoder)
{
std::optional<FontSelectionRange::Value> minimum;
decoder >> minimum;
if (!minimum)
return std::nullopt;
std::optional<FontSelectionRange::Value> maximum;
decoder >> maximum;
if (!maximum)
return std::nullopt;
return {{ *minimum, *maximum }};
}
inline void add(Hasher& hasher, const FontSelectionRange& range)
{
add(hasher, range.minimum, range.maximum);
}
struct FontSelectionRequest {
using Value = FontSelectionValue;
Value weight;
Value width;
// FIXME: We are using an optional here to be able to distinguish between an explicit
// or implicit slope (for "italic" and "oblique") and the "normal" value which has no
// slope. The "italic" and "oblique" values can be distinguished by looking at the
// "fontStyleAxis" on the FontDescription. We should come up with a tri-state member
// so that it's a lot clearer whether we're dealing with a "normal", "italic" or explicit
// "oblique" font style. See webkit.org/b/187774.
std::optional<Value> slope;
std::tuple<Value, Value, std::optional<Value>> tied() const
{
return WTF::tie(weight, width, slope);
}
};
inline TextStream& operator<<(TextStream& ts, const FontSelectionValue& fontSelectionValue)
{
ts << TextStream::FormatNumberRespectingIntegers(fontSelectionValue.rawValue());
return ts;
}
inline TextStream& operator<<(TextStream& ts, const std::optional<FontSelectionValue>& optionalFontSelectionValue)
{
ts << optionalFontSelectionValue.value_or(normalItalicValue());
return ts;
}
inline bool operator==(const FontSelectionRequest& a, const FontSelectionRequest& b)
{
return a.tied() == b.tied();
}
inline bool operator!=(const FontSelectionRequest& a, const FontSelectionRequest& b)
{
return !(a == b);
}
inline void add(Hasher& hasher, const FontSelectionRequest& request)
{
add(hasher, request.tied());
}
struct FontSelectionCapabilities {
using Range = FontSelectionRange;
constexpr std::tuple<Range, Range, Range> tied() const
{
return WTF::tie(weight, width, slope);
}
void expand(const FontSelectionCapabilities& capabilities)
{
weight.expand(capabilities.weight);
width.expand(capabilities.width);
slope.expand(capabilities.slope);
}
Range weight { normalWeightValue() };
Range width { normalStretchValue() };
Range slope { normalItalicValue() };
};
constexpr bool operator==(const FontSelectionCapabilities& a, const FontSelectionCapabilities& b)
{
return a.tied() == b.tied();
}
constexpr bool operator!=(const FontSelectionCapabilities& a, const FontSelectionCapabilities& b)
{
return !(a == b);
}
struct FontSelectionSpecifiedCapabilities {
using Capabilities = FontSelectionCapabilities;
using Range = FontSelectionRange;
using OptionalRange = std::optional<Range>;
constexpr Capabilities computeFontSelectionCapabilities() const
{
return { computeWeight(), computeWidth(), computeSlope() };
}
constexpr std::tuple<OptionalRange&, OptionalRange&, OptionalRange&> tied()
{
return WTF::tie(weight, width, slope);
}
constexpr std::tuple<const OptionalRange&, const OptionalRange&, const OptionalRange&> tied() const
{
return WTF::tie(weight, width, slope);
}
FontSelectionSpecifiedCapabilities& operator=(const Capabilities& other)
{
tied() = other.tied();
return *this;
}
constexpr Range computeWeight() const
{
return weight.value_or(Range { normalWeightValue() });
}
constexpr Range computeWidth() const
{
return width.value_or(Range { normalStretchValue() });
}
constexpr Range computeSlope() const
{
return slope.value_or(Range { normalItalicValue() });
}
template<class Encoder>
void encode(Encoder&) const;
template<class Decoder>
static std::optional<FontSelectionSpecifiedCapabilities> decode(Decoder&);
OptionalRange weight;
OptionalRange width;
OptionalRange slope;
};
template<class Encoder>
void FontSelectionSpecifiedCapabilities::encode(Encoder& encoder) const
{
encoder << weight;
encoder << width;
encoder << slope;
}
template<class Decoder>
std::optional<FontSelectionSpecifiedCapabilities> FontSelectionSpecifiedCapabilities::decode(Decoder& decoder)
{
std::optional<OptionalRange> weight;
decoder >> weight;
if (!weight)
return std::nullopt;
std::optional<OptionalRange> width;
decoder >> width;
if (!width)
return std::nullopt;
std::optional<OptionalRange> slope;
decoder >> slope;
if (!slope)
return std::nullopt;
return {{ *weight, *width, *slope }};
}
constexpr bool operator==(const FontSelectionSpecifiedCapabilities& a, const FontSelectionSpecifiedCapabilities& b)
{
return a.tied() == b.tied();
}
constexpr bool operator!=(const FontSelectionSpecifiedCapabilities& a, const FontSelectionSpecifiedCapabilities& b)
{
return !(a == b);
}
class FontSelectionAlgorithm {
public:
using Capabilities = FontSelectionCapabilities;
FontSelectionAlgorithm() = delete;
FontSelectionAlgorithm(FontSelectionRequest, const Vector<Capabilities>&, std::optional<Capabilities> capabilitiesBounds = std::nullopt);
struct DistanceResult {
FontSelectionValue distance;
FontSelectionValue value;
};
DistanceResult stretchDistance(Capabilities) const;
DistanceResult styleDistance(Capabilities) const;
DistanceResult weightDistance(Capabilities) const;
size_t indexOfBestCapabilities();
private:
using DistanceFunction = DistanceResult (FontSelectionAlgorithm::*)(Capabilities) const;
using CapabilitiesRange = FontSelectionRange Capabilities::*;
FontSelectionValue bestValue(const bool eliminated[], DistanceFunction) const;
void filterCapability(bool eliminated[], DistanceFunction, CapabilitiesRange);
FontSelectionRequest m_request;
Capabilities m_capabilitiesBounds;
const Vector<Capabilities>& m_capabilities;
};
}