Raw File
FontPropertyTypes.h
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* font specific types shared by both thebes and layout */

#ifndef GFX_FONT_PROPERTY_TYPES_H
#define GFX_FONT_PROPERTY_TYPES_H

#include <algorithm>
#include <cstdint>
#include <cmath>
#include <utility>

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "mozilla/Assertions.h"
#include "mozilla/TextUtils.h"
#include "nsString.h"

/*
 * This file is separate from gfxFont.h so that layout can include it
 * without bringing in gfxFont.h and everything it includes.
 */

namespace mozilla {

/**
 * Generic template for font property type classes that use a fixed-point
 * internal representation.
 * Template parameters:
 *   InternalType - the integer type to use as the internal representation (e.g.
 *                  uint16_t)
 *       * NOTE that T must NOT be plain /int/, as that would result in
 *         ambiguity between constructors from /int/ and /T/, which mean
 *         different things.
 *   FractionBits - number of bits to use for the fractional part
 *   Min, Max - [inclusive] limits to the range of values that may be stored
 * Values are constructed from and exposed as floating-point, but stored
 * internally as fixed point, so there will be a quantization effect on
 * fractional values, depending on the number of fractional bits used.
 * Using (16-bit) fixed-point types rather than floats for these style
 * attributes reduces the memory footprint of gfxFontEntry and gfxFontStyle;
 * it will also tend to reduce the number of distinct font instances that
 * get created, particularly when styles are animated or set to arbitrary
 * values (e.g. by sliders in the UI), which should reduce pressure on
 * graphics resources and improve cache hit rates.
 */
template <class InternalType, unsigned FractionBits, int Min, int Max>
class FontPropertyValue {
 public:
  // Initialize to the minimum value by default.
  constexpr FontPropertyValue() : FontPropertyValue(Min) {}

  explicit FontPropertyValue(const FontPropertyValue& aOther) = default;
  FontPropertyValue& operator=(const FontPropertyValue& aOther) = default;

  bool operator==(const FontPropertyValue& aOther) const {
    return mValue == aOther.mValue;
  }
  bool operator!=(const FontPropertyValue& aOther) const {
    return mValue != aOther.mValue;
  }
  bool operator<(const FontPropertyValue& aOther) const {
    return mValue < aOther.mValue;
  }
  bool operator>(const FontPropertyValue& aOther) const {
    return mValue > aOther.mValue;
  }
  bool operator<=(const FontPropertyValue& aOther) const {
    return mValue <= aOther.mValue;
  }
  bool operator>=(const FontPropertyValue& aOther) const {
    return mValue >= aOther.mValue;
  }

  // The difference between two values, returned as a raw floating-point number
  // (which might not be a valid property value in its own right).
  float operator-(const FontPropertyValue& aOther) const {
    return (mValue - aOther.mValue) * kInverseScale;
  }

  /// Return the raw internal representation, for purposes of hashing.
  /// (Do not try to interpret the numeric value of this.)
  uint16_t ForHash() const { return uint16_t(mValue); }

  static constexpr const float kMin = float(Min);
  static constexpr const float kMax = float(Max);

 protected:
  // Construct from a floating-point or integer value, checking that it is
  // within the allowed range and converting to fixed-point representation.
  explicit constexpr FontPropertyValue(float aValue)
      : mValue(std::round(aValue * kScale)) {
    MOZ_ASSERT(aValue >= kMin && aValue <= kMax);
  }
  explicit constexpr FontPropertyValue(int aValue)
      : mValue(static_cast<InternalType>(aValue * kScale)) {
    MOZ_ASSERT(aValue >= Min && aValue <= Max);
  }

  // Construct directly from a fixed-point value of type T, with no check;
  // note that there may be special "flag" values that are outside the normal
  // min/max range (e.g. for font-style:italic, distinct from oblique angle).
  explicit constexpr FontPropertyValue(InternalType aValue) : mValue(aValue) {}

  // This is protected as it may not be the most appropriate accessor for a
  // given instance to expose. It's up to each individual property to provide
  // public accessors that forward to this as required.
  float ToFloat() const { return mValue * kInverseScale; }
  int ToIntRounded() const { return (mValue + kPointFive) >> FractionBits; }

  static constexpr int kScale = 1 << FractionBits;
  static constexpr float kInverseScale = 1.0f / kScale;
  static const unsigned kFractionBits = FractionBits;

  // Constant representing 0.5 in the internal representation (note this
  // assumes that kFractionBits is greater than zero!)
  static const InternalType kPointFive = 1u << (kFractionBits - 1);

  InternalType mValue;
};

/**
 * font-weight: range 1..1000, fractional values permitted; keywords
 * 'normal', 'bold' aliased to 400, 700 respectively; relative keywords
 * 'lighter', 'bolder' (not currently handled here).
 *
 * We use an unsigned 10.6 fixed-point value (range 0.0 - 1023.984375)
 */
class FontWeight final : public FontPropertyValue<uint16_t, 6, 1, 1000> {
 public:
  constexpr FontWeight() = default;

  explicit constexpr FontWeight(float aValue) : FontPropertyValue(aValue) {}

  /**
   * CSS font weights can have fractional values, but this constructor exists
   * for convenience when writing constants such as FontWeight(700) in code.
   */
  explicit constexpr FontWeight(int aValue) : FontPropertyValue(aValue) {}

  static constexpr FontWeight Normal() { return FontWeight(kNormal); }

  static constexpr FontWeight Thin() { return FontWeight(kThin); }

  static constexpr FontWeight Bold() { return FontWeight(kBold); }

  bool IsNormal() const { return mValue == kNormal; }
  bool IsBold() const { return mValue >= kBoldThreshold; }

  float ToFloat() const { return FontPropertyValue::ToFloat(); }
  int ToIntRounded() const { return FontPropertyValue::ToIntRounded(); }

  typedef uint16_t InternalType;

 private:
  friend class WeightRange;

  explicit constexpr FontWeight(InternalType aValue)
      : FontPropertyValue(aValue) {}

  static const InternalType kNormal = 400u << kFractionBits;
  static const InternalType kBold = 700u << kFractionBits;
  static const InternalType kBoldThreshold = 600u << kFractionBits;
  static const InternalType kThin = 100u << kFractionBits;
  static const InternalType kExtraBold = 900u << kFractionBits;
};

/**
 * font-stretch is represented as a percentage relative to 'normal'.
 *
 * css-fonts says the value must be >= 0%, and normal is 100%. Keywords
 * from ultra-condensed to ultra-expanded are aliased to percentages
 * from 50% to 200%; values outside that range are unlikely to be common,
 * but could occur.
 *
 * Like font-weight, we use an unsigned 10.6 fixed-point value (range
 * 0.0 - 1023.984375).
 *
 * We arbitrarily limit here to 1000%. (If that becomes a problem, we
 * could reduce the number of fractional bits and increase the limit.)
 */
class FontStretch final : public FontPropertyValue<uint16_t, 6, 0, 1000> {
 public:
  constexpr FontStretch() = default;

  explicit constexpr FontStretch(float aPercent)
      : FontPropertyValue(aPercent) {}

  static constexpr FontStretch Normal() { return FontStretch(kNormal); }
  static constexpr FontStretch UltraCondensed() {
    return FontStretch(kUltraCondensed);
  }
  static constexpr FontStretch ExtraCondensed() {
    return FontStretch(kExtraCondensed);
  }
  static constexpr FontStretch Condensed() { return FontStretch(kCondensed); }
  static constexpr FontStretch SemiCondensed() {
    return FontStretch(kSemiCondensed);
  }
  static constexpr FontStretch SemiExpanded() {
    return FontStretch(kSemiExpanded);
  }
  static constexpr FontStretch Expanded() { return FontStretch(kExpanded); }
  static constexpr FontStretch ExtraExpanded() {
    return FontStretch(kExtraExpanded);
  }
  static constexpr FontStretch UltraExpanded() {
    return FontStretch(kUltraExpanded);
  }

  // The style system represents percentages in the 0.0..1.0 range, and
  // FontStretch does it in the 0.0..100.0 range.
  //
  // TODO(emilio): We should consider changing this class to deal with the same
  // range as the style system.
  static FontStretch FromStyle(float aStylePercentage) {
    return FontStretch(std::min(aStylePercentage * 100.0f, float(kMax)));
  }

  bool IsNormal() const { return mValue == kNormal; }
  float Percentage() const { return ToFloat(); }

  typedef uint16_t InternalType;

 private:
  friend class StretchRange;

  explicit constexpr FontStretch(InternalType aValue)
      : FontPropertyValue(aValue) {}

  static const InternalType kUltraCondensed = 50u << kFractionBits;
  static const InternalType kExtraCondensed =
      (62u << kFractionBits) + kPointFive;
  static const InternalType kCondensed = 75u << kFractionBits;
  static const InternalType kSemiCondensed =
      (87u << kFractionBits) + kPointFive;
  static const InternalType kNormal = 100u << kFractionBits;
  static const InternalType kSemiExpanded =
      (112u << kFractionBits) + kPointFive;
  static const InternalType kExpanded = 125u << kFractionBits;
  static const InternalType kExtraExpanded = 150u << kFractionBits;
  static const InternalType kUltraExpanded = 200u << kFractionBits;
};

/**
 * font-style: normal | italic | oblique <angle>?
 * values of <angle> below -90 or above 90 not permitted
 * - Use a signed 8.8 fixed-point value
 *   (representable range -128.0 - 127.99609375)
 * - Define min value (-128.0) as meaning 'normal'
 * - Define max value (127.99609375) as 'italic'
 * - Other values represent 'oblique <angle>'
 * - Note that 'oblique 0deg' is distinct from 'normal' (should it be?)
 */
class FontSlantStyle final : public FontPropertyValue<int16_t, 8, -90, 90> {
 public:
  const static constexpr float kDefaultAngle = 14.0;

  constexpr FontSlantStyle() = default;

  static constexpr FontSlantStyle Normal() { return FontSlantStyle(kNormal); }

  static constexpr FontSlantStyle Italic() { return FontSlantStyle(kItalic); }

  static constexpr FontSlantStyle Oblique(float aAngle = kDefaultAngle) {
    return FontSlantStyle(aAngle);
  }

  // Create from a string as generated by ToString. This is for internal use
  // when serializing/deserializing entries for the startupcache, and is not
  // intended to parse arbitrary (untrusted) strings.
  static FontSlantStyle FromString(const char* aString) {
    if (strcmp(aString, "normal") == 0) {
      return Normal();
    }
    if (strcmp(aString, "italic") == 0) {
      return Italic();
    }
    if (mozilla::IsAsciiDigit(aString[0]) && strstr(aString, "deg")) {
      float angle = strtof(aString, nullptr);
      return Oblique(angle);
    }
    // Not recognized as an oblique angle; maybe it's from a startup-cache
    // created by an older version. The style field there used a simple 0/1
    // for normal/italic respectively.
    return aString[0] == '0' ? Normal() : Italic();
  }

  bool IsNormal() const { return mValue == kNormal; }
  bool IsItalic() const { return mValue == kItalic; }
  bool IsOblique() const { return mValue != kItalic && mValue != kNormal; }

  float ObliqueAngle() const {
    // It's not meaningful to get the oblique angle from a style that is
    // actually 'normal' or 'italic'.
    MOZ_ASSERT(IsOblique());
    return ToFloat();
  }

  /**
   * Write a string representation of the value to aOutString.
   *
   * NOTE that this APPENDS to the output string, it does not replace
   * any existing contents.
   */
  void ToString(nsACString& aOutString) const {
    if (IsNormal()) {
      aOutString.Append("normal");
    } else if (IsItalic()) {
      aOutString.Append("italic");
    } else {
      aOutString.AppendPrintf("%gdeg", ObliqueAngle());
    }
  }

  typedef int16_t InternalType;

 private:
  friend class SlantStyleRange;

  explicit constexpr FontSlantStyle(InternalType aConstant)
      : FontPropertyValue(aConstant) {}

  explicit constexpr FontSlantStyle(float aAngle) : FontPropertyValue(aAngle) {}

  static const InternalType kNormal = INT16_MIN;
  static const InternalType kItalic = INT16_MAX;
};

/**
 * Convenience type to hold a <min, max> pair representing a range of values.
 *
 * The min and max are both inclusive, so when min == max the range represents
 * a single value (not an empty range).
 */
template <class T>
class FontPropertyRange {
  // This implementation assumes the underlying property type is a 16-bit value
  // (see FromScalar and AsScalar below).
  static_assert(sizeof(T) == 2, "FontPropertyValue should be a 16-bit type!");

 public:
  /**
   * Construct a range from given minimum and maximum values (inclusive).
   */
  FontPropertyRange(T aMin, T aMax) : mValues(aMin, aMax) {
    MOZ_ASSERT(aMin <= aMax);
  }

  /**
   * Construct a range representing a single value (min==max).
   */
  explicit FontPropertyRange(T aValue) : mValues(aValue, aValue) {}

  explicit FontPropertyRange(const FontPropertyRange& aOther) = default;
  FontPropertyRange& operator=(const FontPropertyRange& aOther) = default;

  T Min() const { return mValues.first; }
  T Max() const { return mValues.second; }

  /**
   * Clamp the given value to this range.
   *
   * (We can't use mozilla::Clamp here because it only accepts integral types.)
   */
  T Clamp(T aValue) const {
    return aValue <= Min() ? Min() : (aValue >= Max() ? Max() : aValue);
  }

  /**
   * Return whether the range consists of a single unique value.
   */
  bool IsSingle() const { return Min() == Max(); }

  bool operator==(const FontPropertyRange& aOther) const {
    return mValues == aOther.mValues;
  }
  bool operator!=(const FontPropertyRange& aOther) const {
    return mValues != aOther.mValues;
  }

  /**
   * Conversion of the property range to/from a single 32-bit scalar value,
   * suitable for IPC serialization, hashing, caching.
   *
   * No assumptions should be made about the numeric value of the scalar.
   *
   * This depends on the underlying property type being a 16-bit value!
   */
  typedef uint32_t ScalarType;

  ScalarType AsScalar() const {
    return (mValues.first.ForHash() << 16) | mValues.second.ForHash();
  }

  /*
   * FIXME:
   * FromScalar is defined in each individual subclass, because I can't
   * persuade the compiler to accept a definition here in the template. :\
   *
  static FontPropertyRange FromScalar(ScalarType aScalar)
  {
    return FontPropertyRange(T(typename T::InternalType(aScalar >> 16)),
                             T(typename T::InternalType(aScalar & 0xffff)));
  }
   */

 protected:
  std::pair<T, T> mValues;
};

class WeightRange : public FontPropertyRange<FontWeight> {
 public:
  WeightRange(FontWeight aMin, FontWeight aMax)
      : FontPropertyRange(aMin, aMax) {}

  explicit WeightRange(FontWeight aWeight) : FontPropertyRange(aWeight) {}

  WeightRange(const WeightRange& aOther) = default;

  void ToString(nsACString& aOutString, const char* aDelim = "..") const {
    aOutString.AppendFloat(Min().ToFloat());
    if (!IsSingle()) {
      aOutString.Append(aDelim);
      aOutString.AppendFloat(Max().ToFloat());
    }
  }

  static WeightRange FromScalar(ScalarType aScalar) {
    return WeightRange(FontWeight(FontWeight::InternalType(aScalar >> 16)),
                       FontWeight(FontWeight::InternalType(aScalar & 0xffff)));
  }
};

class StretchRange : public FontPropertyRange<FontStretch> {
 public:
  StretchRange(FontStretch aMin, FontStretch aMax)
      : FontPropertyRange(aMin, aMax) {}

  explicit StretchRange(FontStretch aStretch) : FontPropertyRange(aStretch) {}

  StretchRange(const StretchRange& aOther) = default;

  void ToString(nsACString& aOutString, const char* aDelim = "..") const {
    aOutString.AppendFloat(Min().Percentage());
    if (!IsSingle()) {
      aOutString.Append(aDelim);
      aOutString.AppendFloat(Max().Percentage());
    }
  }

  static StretchRange FromScalar(ScalarType aScalar) {
    return StretchRange(
        FontStretch(FontStretch::InternalType(aScalar >> 16)),
        FontStretch(FontStretch::InternalType(aScalar & 0xffff)));
  }
};

class SlantStyleRange : public FontPropertyRange<FontSlantStyle> {
 public:
  SlantStyleRange(FontSlantStyle aMin, FontSlantStyle aMax)
      : FontPropertyRange(aMin, aMax) {}

  explicit SlantStyleRange(FontSlantStyle aStyle) : FontPropertyRange(aStyle) {}

  SlantStyleRange(const SlantStyleRange& aOther) = default;

  void ToString(nsACString& aOutString, const char* aDelim = "..") const {
    Min().ToString(aOutString);
    if (!IsSingle()) {
      aOutString.Append(aDelim);
      Max().ToString(aOutString);
    }
  }

  static SlantStyleRange FromScalar(ScalarType aScalar) {
    return SlantStyleRange(
        FontSlantStyle(FontSlantStyle::InternalType(aScalar >> 16)),
        FontSlantStyle(FontSlantStyle::InternalType(aScalar & 0xffff)));
  }
};

}  // namespace mozilla

#endif  // GFX_FONT_PROPERTY_TYPES_H
back to top