This commit is contained in:
Johan Dahlin 2024-11-20 21:28:57 -05:00 committed by GitHub
commit a7231f874a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 2405 additions and 231 deletions

View file

@ -8,5 +8,6 @@ Libraries/LibJS/Tests/modules/top-level-dispose.mjs
Libraries/LibJS/Tests/using-declaration.js Libraries/LibJS/Tests/using-declaration.js
Libraries/LibJS/Tests/using-for-loops.js Libraries/LibJS/Tests/using-for-loops.js
Tests/LibWeb/Ref/data
Tests/LibWeb/Ref/input/wpt-import Tests/LibWeb/Ref/input/wpt-import
Tests/LibWeb/Text/input/wpt-import Tests/LibWeb/Text/input/wpt-import

View file

@ -16,6 +16,7 @@ set(SOURCES
Font/Font.cpp Font/Font.cpp
Font/FontData.cpp Font/FontData.cpp
Font/FontDatabase.cpp Font/FontDatabase.cpp
Font/FontVariant.cpp
Font/PathFontProvider.cpp Font/PathFontProvider.cpp
Font/ScaledFont.cpp Font/ScaledFont.cpp
Font/ScaledFontSkia.cpp Font/ScaledFontSkia.cpp

View file

@ -0,0 +1,176 @@
/*
* Copyright (c) 2024, Johan Dahlin <jdahlin@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <LibGfx/Font/FontVariant.h>
namespace Gfx {
StringView font_variant_alternates_to_string(FontVariantAlternates value)
{
if (value.normal && value.historical_forms)
return "normal historical-forms"sv;
if (value.normal)
return "normal"sv;
if (value.historical_forms)
return "historical-forms"sv;
return {};
}
StringView font_variant_ligatures_to_string(FontVariantLigatures ligatures)
{
if (ligatures.normal)
return "normal"sv;
if (ligatures.none)
return "none"sv;
Vector<StringView> values;
switch (ligatures.common) {
case FontVariantLigatures::Common::Common:
values.append("common-ligatures"sv);
break;
case FontVariantLigatures::Common::NoCommon:
values.append("no-common-ligatures"sv);
break;
case FontVariantLigatures::Common::Unset:
break;
}
switch (ligatures.discretionary) {
case FontVariantLigatures::Discretionary::Discretionary:
values.append("discretionary-ligatures"sv);
break;
case FontVariantLigatures::Discretionary::NoDiscretionary:
values.append("no-discretionary-ligatures"sv);
break;
case FontVariantLigatures::Discretionary::Unset:
break;
}
switch (ligatures.historical) {
case FontVariantLigatures::Historical::Historical:
values.append("historical-ligatures"sv);
break;
case FontVariantLigatures::Historical::NoHistorical:
values.append("no-historical-ligatures"sv);
break;
case FontVariantLigatures::Historical::Unset:
break;
}
switch (ligatures.contextual) {
case FontVariantLigatures::Contextual::Contextual:
values.append("contextual"sv);
break;
case FontVariantLigatures::Contextual::NoContextual:
values.append("no-contextual"sv);
break;
case FontVariantLigatures::Contextual::Unset:
break;
}
StringBuilder builder;
builder.join(' ', values);
return MUST(builder.to_string());
}
StringView font_variant_east_asian_to_string(FontVariantEastAsian value)
{
Vector<StringView> values;
switch (value.variant) {
case FontVariantEastAsian::Variant::Unset:
break;
case FontVariantEastAsian::Variant::Jis78:
values.append("jis78"sv);
break;
case FontVariantEastAsian::Variant::Jis83:
values.append("jis83"sv);
break;
case FontVariantEastAsian::Variant::Jis90:
values.append("jis90"sv);
break;
case FontVariantEastAsian::Variant::Jis04:
values.append("jis04"sv);
break;
case FontVariantEastAsian::Variant::Simplified:
values.append("simplified"sv);
break;
case FontVariantEastAsian::Variant::Traditional:
values.append("traditional"sv);
break;
}
switch (value.width) {
case FontVariantEastAsian::Width::Unset:
break;
case FontVariantEastAsian::Width::FullWidth:
values.append("full-width"sv);
break;
case FontVariantEastAsian::Width::Proportional:
values.append("proportional-width"sv);
break;
}
if (value.ruby)
values.append("ruby"sv);
StringBuilder builder;
builder.join(' ', values);
return MUST(builder.to_string());
}
StringView font_variant_numeric_to_string(FontVariantNumeric value)
{
Vector<StringView> values;
if (value.normal)
values.append("normal"sv);
if (value.ordinal)
values.append("ordinal"sv);
if (value.slashed_zero)
values.append("slashed-zero"sv);
switch (value.figure) {
case FontVariantNumeric::Figure::Unset:
break;
case FontVariantNumeric::Figure::Lining:
values.append("lining-nums"sv);
break;
case FontVariantNumeric::Figure::Oldstyle:
values.append("oldstyle-nums"sv);
break;
}
switch (value.spacing) {
case FontVariantNumeric::Spacing::Unset:
break;
case FontVariantNumeric::Spacing::Proportional:
values.append("proportional-nums"sv);
break;
case FontVariantNumeric::Spacing::Tabular:
values.append("tabular-nums"sv);
break;
}
switch (value.fraction) {
case FontVariantNumeric::Fraction::Unset:
break;
case FontVariantNumeric::Fraction::Diagonal:
values.append("diagonal-fractions"sv);
break;
case FontVariantNumeric::Fraction::Stacked:
values.append("stacked-fractions"sv);
break;
}
StringBuilder builder;
builder.join(' ', values);
return MUST(builder.to_string());
}
}

View file

@ -0,0 +1,90 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Optional.h>
#include <AK/StringView.h>
#include <AK/Vector.h>
#pragma once
namespace Gfx {
using FontFeatureName = StringView;
class FontVariantAlternates {
public:
bool normal { false };
bool historical_forms { false };
};
class FontVariantEastAsian {
public:
enum class Variant { Unset,
Jis78,
Jis83,
Jis90,
Jis04,
Simplified,
Traditional };
enum class Width { Unset,
Proportional,
FullWidth };
bool normal = false;
bool ruby = false;
Variant variant { Variant::Unset };
Width width { Width::Unset };
};
class FontVariantLigatures {
public:
enum class Common { Unset,
Common,
NoCommon };
enum class Discretionary { Unset,
Discretionary,
NoDiscretionary };
enum class Historical { Unset,
Historical,
NoHistorical };
enum class Contextual { Unset,
Contextual,
NoContextual };
bool normal = false;
bool none = false;
Common common { Common::Unset };
Discretionary discretionary { Discretionary::Unset };
Historical historical { Historical::Unset };
Contextual contextual { Contextual::Unset };
};
class FontVariantNumeric {
public:
enum class Figure { Unset,
Lining,
Oldstyle };
enum class Spacing { Unset,
Proportional,
Tabular };
enum class Fraction { Unset,
Diagonal,
Stacked };
bool normal = false;
bool ordinal = false;
bool slashed_zero = false;
Figure figure { Figure::Unset };
Spacing spacing { Spacing::Unset };
Fraction fraction { Fraction::Unset };
};
extern StringView font_variant_alternates_to_string(FontVariantAlternates);
extern StringView font_variant_east_asian_to_string(FontVariantEastAsian);
extern StringView font_variant_ligatures_to_string(FontVariantLigatures);
extern StringView font_variant_numeric_to_string(FontVariantNumeric);
}

View file

@ -58,13 +58,13 @@ ScaledFontMetrics ScaledFont::metrics() const
return metrics; return metrics;
} }
float ScaledFont::width(StringView view) const { return measure_text_width(Utf8View(view), *this); } float ScaledFont::width(StringView view) const { return measure_text_width(Utf8View(view), *this, {}); }
float ScaledFont::width(Utf8View const& view) const { return measure_text_width(view, *this); } float ScaledFont::width(Utf8View const& view) const { return measure_text_width(view, *this, {}); }
float ScaledFont::glyph_width(u32 code_point) const float ScaledFont::glyph_width(u32 code_point) const
{ {
auto string = String::from_code_point(code_point); auto string = String::from_code_point(code_point);
return measure_text_width(Utf8View(string), *this); return measure_text_width(Utf8View(string), *this, {});
} }
NonnullRefPtr<ScaledFont> ScaledFont::scaled_with_size(float point_size) const NonnullRefPtr<ScaledFont> ScaledFont::scaled_with_size(float point_size) const

View file

@ -12,7 +12,7 @@
namespace Gfx { namespace Gfx {
RefPtr<GlyphRun> shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType text_type) RefPtr<GlyphRun> shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType text_type, ShapeFeatures const& features)
{ {
hb_buffer_t* buffer = hb_buffer_create(); hb_buffer_t* buffer = hb_buffer_create();
ScopeGuard destroy_buffer = [&]() { hb_buffer_destroy(buffer); }; ScopeGuard destroy_buffer = [&]() { hb_buffer_destroy(buffer); };
@ -24,7 +24,22 @@ RefPtr<GlyphRun> shape_text(FloatPoint baseline_start, float letter_spacing, Utf
Vector<hb_glyph_info_t> const input_glyph_info({ glyph_info, glyph_count }); Vector<hb_glyph_info_t> const input_glyph_info({ glyph_info, glyph_count });
auto* hb_font = font.harfbuzz_font(); auto* hb_font = font.harfbuzz_font();
hb_shape(hb_font, buffer, nullptr, 0); const hb_feature_t* hb_features_data = nullptr;
Vector<hb_feature_t> hb_features;
if (!features.is_empty()) {
hb_features.ensure_capacity(features.size());
for (auto const& feature : features) {
hb_features.append({
.tag = HB_TAG(feature.tag[0], feature.tag[1], feature.tag[2], feature.tag[3]),
.value = feature.value,
.start = 0,
.end = static_cast<unsigned int>(-1),
});
}
hb_features_data = hb_features.data();
}
hb_shape(hb_font, buffer, hb_features_data, features.size());
glyph_info = hb_buffer_get_glyph_infos(buffer, &glyph_count); glyph_info = hb_buffer_get_glyph_infos(buffer, &glyph_count);
auto* positions = hb_buffer_get_glyph_positions(buffer, &glyph_count); auto* positions = hb_buffer_get_glyph_positions(buffer, &glyph_count);
@ -45,12 +60,14 @@ RefPtr<GlyphRun> shape_text(FloatPoint baseline_start, float letter_spacing, Utf
point.translate_by(letter_spacing, 0); point.translate_by(letter_spacing, 0);
} }
hb_buffer_reset(buffer);
return adopt_ref(*new Gfx::GlyphRun(move(glyph_run), font, text_type, point.x())); return adopt_ref(*new Gfx::GlyphRun(move(glyph_run), font, text_type, point.x()));
} }
float measure_text_width(Utf8View const& string, Gfx::Font const& font) float measure_text_width(Utf8View const& string, Gfx::Font const& font, ShapeFeatures const& features)
{ {
auto glyph_run = shape_text({}, 0, string, font, GlyphRun::TextType::Common); auto glyph_run = shape_text({}, 0, string, font, GlyphRun::TextType::Common, features);
return glyph_run->width(); return glyph_run->width();
} }

View file

@ -26,6 +26,13 @@ struct DrawGlyph {
} }
}; };
typedef struct ShapeFeature {
char tag[4];
u32 value;
} ShapeFeature;
using ShapeFeatures = Vector<ShapeFeature, 4>;
class GlyphRun : public RefCounted<GlyphRun> { class GlyphRun : public RefCounted<GlyphRun> {
public: public:
enum class TextType { enum class TextType {
@ -60,7 +67,7 @@ private:
float m_width { 0 }; float m_width { 0 };
}; };
RefPtr<GlyphRun> shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType); RefPtr<GlyphRun> shape_text(FloatPoint baseline_start, float letter_spacing, Utf8View string, Gfx::Font const& font, GlyphRun::TextType, ShapeFeatures const& features);
float measure_text_width(Utf8View const& string, Gfx::Font const& font); float measure_text_width(Utf8View const& string, Gfx::Font const& font, ShapeFeatures const& features);
} }

View file

@ -481,4 +481,165 @@ int CSSStyleValue::to_font_width() const
return width; return width;
} }
Gfx::FontVariantAlternates CSSStyleValue::to_font_variant_alternates() const
{
VERIFY(is_keyword());
switch (as_keyword().keyword()) {
case Keyword::Normal:
return Gfx::FontVariantAlternates { .normal = true };
case Keyword::HistoricalForms:
return Gfx::FontVariantAlternates { .historical_forms = true };
default:
VERIFY_NOT_REACHED();
}
}
Optional<FontVariantCaps> CSSStyleValue::to_font_variant_caps() const
{
VERIFY(is_keyword());
return keyword_to_font_variant_caps(as_keyword().keyword());
}
Gfx::FontVariantEastAsian CSSStyleValue::to_font_variant_east_asian() const
{
VERIFY(is_value_list());
auto& list = as_value_list();
Gfx::FontVariantEastAsian east_asian {};
for (auto& value : list.values()) {
VERIFY(value->is_keyword());
switch (value->as_keyword().keyword()) {
case Keyword::Normal:
east_asian.normal = true;
return east_asian;
case Keyword::Jis78:
east_asian.variant = Gfx::FontVariantEastAsian::Variant::Jis78;
break;
case Keyword::Jis83:
east_asian.variant = Gfx::FontVariantEastAsian::Variant::Jis83;
break;
case Keyword::Jis90:
east_asian.variant = Gfx::FontVariantEastAsian::Variant::Jis90;
break;
case Keyword::Jis04:
east_asian.variant = Gfx::FontVariantEastAsian::Variant::Jis04;
break;
case Keyword::Simplified:
east_asian.variant = Gfx::FontVariantEastAsian::Variant::Simplified;
break;
case Keyword::Traditional:
east_asian.variant = Gfx::FontVariantEastAsian::Variant::Traditional;
break;
case Keyword::FullWidth:
east_asian.width = Gfx::FontVariantEastAsian::Width::FullWidth;
break;
case Keyword::ProportionalWidth:
east_asian.width = Gfx::FontVariantEastAsian::Width::Proportional;
break;
case Keyword::Ruby:
east_asian.ruby = true;
break;
default:
VERIFY_NOT_REACHED();
break;
}
}
return east_asian;
}
Gfx::FontVariantLigatures CSSStyleValue::to_font_variant_ligatures() const
{
VERIFY(is_value_list());
auto& list = as_value_list();
Gfx::FontVariantLigatures ligatures;
for (auto& value : list.values()) {
VERIFY(value->is_keyword());
switch (value->as_keyword().keyword()) {
case Keyword::Normal:
ligatures.normal = true;
return ligatures;
case Keyword::None:
ligatures.none = true;
return ligatures;
case Keyword::CommonLigatures:
ligatures.common = Gfx::FontVariantLigatures::Common::Common;
break;
case Keyword::NoCommonLigatures:
ligatures.common = Gfx::FontVariantLigatures::Common::NoCommon;
break;
case Keyword::DiscretionaryLigatures:
ligatures.discretionary = Gfx::FontVariantLigatures::Discretionary::Discretionary;
break;
case Keyword::NoDiscretionaryLigatures:
ligatures.discretionary = Gfx::FontVariantLigatures::Discretionary::NoDiscretionary;
break;
case Keyword::HistoricalLigatures:
ligatures.historical = Gfx::FontVariantLigatures::Historical::Historical;
break;
case Keyword::NoHistoricalLigatures:
ligatures.historical = Gfx::FontVariantLigatures::Historical::NoHistorical;
break;
case Keyword::Contextual:
ligatures.contextual = Gfx::FontVariantLigatures::Contextual::Contextual;
break;
case Keyword::NoContextual:
ligatures.contextual = Gfx::FontVariantLigatures::Contextual::NoContextual;
break;
default:
VERIFY_NOT_REACHED();
break;
}
}
return ligatures;
}
Gfx::FontVariantNumeric CSSStyleValue::to_font_variant_numeric() const
{
VERIFY(is_value_list());
auto& list = as_value_list();
Gfx::FontVariantNumeric numeric {};
for (auto& value : list.values()) {
VERIFY(value->is_keyword());
switch (value->as_keyword().keyword()) {
case Keyword::Normal:
numeric.normal = true;
return numeric;
case Keyword::Ordinal:
numeric.ordinal = true;
break;
case Keyword::SlashedZero:
numeric.slashed_zero = true;
break;
case Keyword::OldstyleNums:
numeric.figure = Gfx::FontVariantNumeric::Figure::Oldstyle;
break;
case Keyword::LiningNums:
numeric.figure = Gfx::FontVariantNumeric::Figure::Lining;
break;
case Keyword::ProportionalNums:
numeric.spacing = Gfx::FontVariantNumeric::Spacing::Proportional;
break;
case Keyword::TabularNums:
numeric.spacing = Gfx::FontVariantNumeric::Spacing::Tabular;
break;
case Keyword::DiagonalFractions:
numeric.fraction = Gfx::FontVariantNumeric::Fraction::Diagonal;
break;
case Keyword::StackedFractions:
numeric.fraction = Gfx::FontVariantNumeric::Fraction::Stacked;
break;
default:
VERIFY_NOT_REACHED();
break;
}
}
return numeric;
}
Optional<FontVariantPosition> CSSStyleValue::to_font_variant_position() const
{
VERIFY(is_keyword());
return keyword_to_font_variant_position(as_keyword().keyword());
}
} }

View file

@ -20,6 +20,7 @@
#include <AK/Vector.h> #include <AK/Vector.h>
#include <AK/WeakPtr.h> #include <AK/WeakPtr.h>
#include <LibGfx/Color.h> #include <LibGfx/Color.h>
#include <LibGfx/Font/FontVariant.h>
#include <LibURL/URL.h> #include <LibURL/URL.h>
#include <LibWeb/CSS/Enums.h> #include <LibWeb/CSS/Enums.h>
#include <LibWeb/CSS/Keyword.h> #include <LibWeb/CSS/Keyword.h>
@ -104,6 +105,7 @@ public:
Edge, Edge,
FilterValueList, FilterValueList,
Flex, Flex,
FontVariant,
Frequency, Frequency,
GridAutoFlow, GridAutoFlow,
GridTemplateArea, GridTemplateArea,
@ -355,6 +357,12 @@ public:
[[nodiscard]] int to_font_weight() const; [[nodiscard]] int to_font_weight() const;
[[nodiscard]] int to_font_slope() const; [[nodiscard]] int to_font_slope() const;
[[nodiscard]] int to_font_width() const; [[nodiscard]] int to_font_width() const;
[[nodiscard]] Gfx::FontVariantAlternates to_font_variant_alternates() const;
[[nodiscard]] Optional<FontVariantCaps> to_font_variant_caps() const;
[[nodiscard]] Gfx::FontVariantEastAsian to_font_variant_east_asian() const;
[[nodiscard]] Gfx::FontVariantLigatures to_font_variant_ligatures() const;
[[nodiscard]] Gfx::FontVariantNumeric to_font_variant_numeric() const;
[[nodiscard]] Optional<FontVariantPosition> to_font_variant_position() const;
virtual bool equals(CSSStyleValue const& other) const = 0; virtual bool equals(CSSStyleValue const& other) const = 0;

View file

@ -97,7 +97,6 @@ public:
static AspectRatio aspect_ratio() { return AspectRatio { true, {} }; } static AspectRatio aspect_ratio() { return AspectRatio { true, {} }; }
static CSSPixels font_size() { return 16; } static CSSPixels font_size() { return 16; }
static int font_weight() { return 400; } static int font_weight() { return 400; }
static CSS::FontVariant font_variant() { return CSS::FontVariant::Normal; }
static CSSPixels line_height() { return 0; } static CSSPixels line_height() { return 0; }
static CSS::Float float_() { return CSS::Float::None; } static CSS::Float float_() { return CSS::Float::None; }
static CSS::Length border_spacing() { return CSS::Length::make_px(0); } static CSS::Length border_spacing() { return CSS::Length::make_px(0); }
@ -511,7 +510,12 @@ public:
Gfx::FontCascadeList const& font_list() const { return *m_inherited.font_list; } Gfx::FontCascadeList const& font_list() const { return *m_inherited.font_list; }
CSSPixels font_size() const { return m_inherited.font_size; } CSSPixels font_size() const { return m_inherited.font_size; }
int font_weight() const { return m_inherited.font_weight; } int font_weight() const { return m_inherited.font_weight; }
CSS::FontVariant font_variant() const { return m_inherited.font_variant; } Gfx::FontVariantAlternates font_variant_alternates() const { return m_inherited.font_variant_alternates; }
FontVariantCaps font_variant_caps() const { return m_inherited.font_variant_caps; }
Gfx::FontVariantEastAsian font_variant_east_asian() const { return m_inherited.font_variant_east_asian; }
Gfx::FontVariantLigatures font_variant_ligatures() const { return m_inherited.font_variant_ligatures; }
Gfx::FontVariantNumeric font_variant_numeric() const { return m_inherited.font_variant_numeric; }
FontVariantPosition font_variant_position() const { return m_inherited.font_variant_position; }
Optional<FlyString> font_language_override() const { return m_inherited.font_language_override; } Optional<FlyString> font_language_override() const { return m_inherited.font_language_override; }
Optional<HashMap<FlyString, IntegerOrCalculated>> font_feature_settings() const { return m_inherited.font_feature_settings; } Optional<HashMap<FlyString, IntegerOrCalculated>> font_feature_settings() const { return m_inherited.font_feature_settings; }
Optional<HashMap<FlyString, NumberOrCalculated>> font_variation_settings() const { return m_inherited.font_variation_settings; } Optional<HashMap<FlyString, NumberOrCalculated>> font_variation_settings() const { return m_inherited.font_variation_settings; }
@ -545,7 +549,12 @@ protected:
RefPtr<Gfx::FontCascadeList> font_list {}; RefPtr<Gfx::FontCascadeList> font_list {};
CSSPixels font_size { InitialValues::font_size() }; CSSPixels font_size { InitialValues::font_size() };
int font_weight { InitialValues::font_weight() }; int font_weight { InitialValues::font_weight() };
CSS::FontVariant font_variant { InitialValues::font_variant() }; Gfx::FontVariantAlternates font_variant_alternates { .normal = true };
FontVariantCaps font_variant_caps { FontVariantCaps::Normal };
Gfx::FontVariantEastAsian font_variant_east_asian { .normal = true };
Gfx::FontVariantLigatures font_variant_ligatures { .normal = true };
Gfx::FontVariantNumeric font_variant_numeric { .normal = true };
FontVariantPosition font_variant_position { FontVariantPosition::Normal };
Optional<FlyString> font_language_override; Optional<FlyString> font_language_override;
Optional<HashMap<FlyString, IntegerOrCalculated>> font_feature_settings; Optional<HashMap<FlyString, IntegerOrCalculated>> font_feature_settings;
Optional<HashMap<FlyString, NumberOrCalculated>> font_variation_settings; Optional<HashMap<FlyString, NumberOrCalculated>> font_variation_settings;
@ -717,7 +726,12 @@ public:
void set_font_list(NonnullRefPtr<Gfx::FontCascadeList> font_list) { m_inherited.font_list = move(font_list); } void set_font_list(NonnullRefPtr<Gfx::FontCascadeList> font_list) { m_inherited.font_list = move(font_list); }
void set_font_size(CSSPixels font_size) { m_inherited.font_size = font_size; } void set_font_size(CSSPixels font_size) { m_inherited.font_size = font_size; }
void set_font_weight(int font_weight) { m_inherited.font_weight = font_weight; } void set_font_weight(int font_weight) { m_inherited.font_weight = font_weight; }
void set_font_variant(CSS::FontVariant font_variant) { m_inherited.font_variant = font_variant; } void set_font_variant_alternates(Gfx::FontVariantAlternates font_variant_alternates) { m_inherited.font_variant_alternates = font_variant_alternates; }
void set_font_variant_caps(FontVariantCaps font_variant_caps) { m_inherited.font_variant_caps = font_variant_caps; }
void set_font_variant_east_asian(Gfx::FontVariantEastAsian font_variant_east_asian) { m_inherited.font_variant_east_asian = font_variant_east_asian; }
void set_font_variant_ligatures(Gfx::FontVariantLigatures font_variant_ligatures) { m_inherited.font_variant_ligatures = font_variant_ligatures; }
void set_font_variant_numeric(Gfx::FontVariantNumeric font_variant_numeric) { m_inherited.font_variant_numeric = font_variant_numeric; }
void set_font_variant_position(FontVariantPosition font_variant_position) { m_inherited.font_variant_position = font_variant_position; }
void set_font_language_override(Optional<FlyString> font_language_override) { m_inherited.font_language_override = font_language_override; } void set_font_language_override(Optional<FlyString> font_language_override) { m_inherited.font_language_override = font_language_override; }
void set_font_feature_settings(Optional<HashMap<FlyString, IntegerOrCalculated>> value) { m_inherited.font_feature_settings = move(value); } void set_font_feature_settings(Optional<HashMap<FlyString, IntegerOrCalculated>> value) { m_inherited.font_feature_settings = move(value); }
void set_font_variation_settings(Optional<HashMap<FlyString, NumberOrCalculated>> value) { m_inherited.font_variation_settings = move(value); } void set_font_variation_settings(Optional<HashMap<FlyString, NumberOrCalculated>> value) { m_inherited.font_variation_settings = move(value); }

View file

@ -221,9 +221,64 @@
"fallback", "fallback",
"optional" "optional"
], ],
"font-variant": [ "font-variant-alternates": [
"normal", "normal",
"small-caps" "historical-forms"
],
"font-variant-caps": [
"normal",
"small-caps",
"all-small-caps",
"petite-caps",
"all-petite-caps",
"unicase",
"titling-caps"
],
"font-variant-east-asian": [
"normal",
"ruby",
"jis78",
"jis83",
"jis90",
"jis04",
"simplified",
"traditional",
"full-width",
"proportional-width"
],
"font-variant-emoji": [
"normal",
"text",
"emoji",
"unicode"
],
"font-variant-ligatures": [
"normal",
"none",
"common-ligatures",
"no-common-ligatures",
"discretionary-ligatures",
"no-discretionary-ligatures",
"historical-ligatures",
"no-historical-ligatures",
"contextual",
"no-contextual"
],
"font-variant-numeric": [
"normal",
"ordinal",
"slashed-zero",
"lining-nums",
"oldstyle-nums",
"proportional-nums",
"tabular-nums",
"diagonal-fractions",
"stacked-fractions"
],
"font-variant-position": [
"normal",
"sub",
"super"
], ],
"font-width": [ "font-width": [
"ultra-condensed", "ultra-condensed",

View file

@ -28,7 +28,7 @@ namespace Web::CSS {
GC_DEFINE_ALLOCATOR(FontFaceSet); GC_DEFINE_ALLOCATOR(FontFaceSet);
// https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-fontfaceset // https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-fontfaceset
GC::Ref<FontFaceSet> FontFaceSet::construct_impl(JS::Realm& realm, Vector<GC::Root<FontFace>> const& initial_faces) GC::Ref<FontFaceSet> FontFaceSet::construct_impl(Web::Page& page, JS::Realm& realm, Vector<GC::Root<FontFace>> const& initial_faces)
{ {
auto ready_promise = WebIDL::create_promise(realm); auto ready_promise = WebIDL::create_promise(realm);
auto set_entries = JS::Set::create(realm); auto set_entries = JS::Set::create(realm);
@ -37,16 +37,17 @@ GC::Ref<FontFaceSet> FontFaceSet::construct_impl(JS::Realm& realm, Vector<GC::Ro
for (auto const& face : initial_faces) for (auto const& face : initial_faces)
set_entries->set_add(face); set_entries->set_add(face);
return realm.create<FontFaceSet>(realm, ready_promise, set_entries); return realm.create<FontFaceSet>(page, realm, ready_promise, set_entries);
} }
GC::Ref<FontFaceSet> FontFaceSet::create(JS::Realm& realm) GC::Ref<FontFaceSet> FontFaceSet::create(Web::Page& page, JS::Realm& realm)
{ {
return construct_impl(realm, {}); return construct_impl(page, realm, {});
} }
FontFaceSet::FontFaceSet(JS::Realm& realm, GC::Ref<WebIDL::Promise> ready_promise, GC::Ref<JS::Set> set_entries) FontFaceSet::FontFaceSet(Web::Page& page, JS::Realm& realm, GC::Ref<WebIDL::Promise> ready_promise, GC::Ref<JS::Set> set_entries)
: DOM::EventTarget(realm) : DOM::EventTarget(realm)
, m_page(page)
, m_set_entries(set_entries) , m_set_entries(set_entries)
, m_ready_promise(ready_promise) , m_ready_promise(ready_promise)
, m_status(Bindings::FontFaceSetLoadStatus::Loaded) , m_status(Bindings::FontFaceSetLoadStatus::Loaded)
@ -63,6 +64,7 @@ void FontFaceSet::initialize(JS::Realm& realm)
void FontFaceSet::visit_edges(Cell::Visitor& visitor) void FontFaceSet::visit_edges(Cell::Visitor& visitor)
{ {
Base::visit_edges(visitor); Base::visit_edges(visitor);
visitor.visit(m_page);
visitor.visit(m_set_entries); visitor.visit(m_set_entries);
visitor.visit(m_ready_promise); visitor.visit(m_ready_promise);
visitor.visit(m_loading_fonts); visitor.visit(m_loading_fonts);
@ -289,7 +291,10 @@ GC::Ref<WebIDL::Promise> FontFaceSet::ready() const
void FontFaceSet::resolve_ready_promise() void FontFaceSet::resolve_ready_promise()
{ {
dbgln("FontFaceSet::resolve_ready_promise(): {}", m_page->url());
WebIDL::resolve_promise(realm(), m_ready_promise); WebIDL::resolve_promise(realm(), m_ready_promise);
m_page->client().page_did_finish_loading_page_and_fonts(m_page->url());
} }
} }

View file

@ -13,6 +13,8 @@
#include <LibWeb/Bindings/PlatformObject.h> #include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/CSS/FontFace.h> #include <LibWeb/CSS/FontFace.h>
#include <LibWeb/DOM/EventTarget.h> #include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/Page/Page.h>
namespace Web::CSS { namespace Web::CSS {
@ -21,8 +23,8 @@ class FontFaceSet final : public DOM::EventTarget {
GC_DECLARE_ALLOCATOR(FontFaceSet); GC_DECLARE_ALLOCATOR(FontFaceSet);
public: public:
[[nodiscard]] static GC::Ref<FontFaceSet> construct_impl(JS::Realm&, Vector<GC::Root<FontFace>> const& initial_faces); [[nodiscard]] static GC::Ref<FontFaceSet> construct_impl(Web::Page&, JS::Realm&, Vector<GC::Root<FontFace>> const& initial_faces);
[[nodiscard]] static GC::Ref<FontFaceSet> create(JS::Realm&); [[nodiscard]] static GC::Ref<FontFaceSet> create(Web::Page&, JS::Realm&);
virtual ~FontFaceSet() override = default; virtual ~FontFaceSet() override = default;
GC::Ref<JS::Set> set_entries() const { return m_set_entries; } GC::Ref<JS::Set> set_entries() const { return m_set_entries; }
@ -46,11 +48,12 @@ public:
void resolve_ready_promise(); void resolve_ready_promise();
private: private:
FontFaceSet(JS::Realm&, GC::Ref<WebIDL::Promise> ready_promise, GC::Ref<JS::Set> set_entries); FontFaceSet(Web::Page&, JS::Realm&, GC::Ref<WebIDL::Promise> ready_promise, GC::Ref<JS::Set> set_entries);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;
GC::Ref<Web::Page> m_page;
GC::Ref<JS::Set> m_set_entries; GC::Ref<JS::Set> m_set_entries;
GC::Ref<WebIDL::Promise> m_ready_promise; // [[ReadyPromise]] GC::Ref<WebIDL::Promise> m_ready_promise; // [[ReadyPromise]]

View file

@ -67,7 +67,9 @@
"additive", "additive",
"alias", "alias",
"all", "all",
"all-petite-caps",
"all-scroll", "all-scroll",
"all-small-caps",
"alpha", "alpha",
"alternate", "alternate",
"alternate-reverse", "alternate-reverse",
@ -114,6 +116,7 @@
"collapse", "collapse",
"column", "column",
"column-reverse", "column-reverse",
"common-ligatures",
"compact", "compact",
"condensed", "condensed",
"contain", "contain",
@ -121,6 +124,7 @@
"content-box", "content-box",
"contents", "contents",
"context-menu", "context-menu",
"contextual",
"copy", "copy",
"cover", "cover",
"crisp-edges", "crisp-edges",
@ -133,7 +137,9 @@
"decimal", "decimal",
"decimal-leading-zero", "decimal-leading-zero",
"default", "default",
"diagonal-fractions",
"disc", "disc",
"discretionary-ligatures",
"disclosure-closed", "disclosure-closed",
"disclosure-open", "disclosure-open",
"distribute", "distribute",
@ -147,6 +153,7 @@
"ease-out", "ease-out",
"ellipsis", "ellipsis",
"embed", "embed",
"emoji",
"enabled", "enabled",
"end", "end",
"evenodd", "evenodd",
@ -185,6 +192,8 @@
"high-quality", "high-quality",
"highlight", "highlight",
"highlighttext", "highlighttext",
"historical-forms",
"historical-ligatures",
"horizontal-tb", "horizontal-tb",
"hover", "hover",
"inactiveborder", "inactiveborder",
@ -213,6 +222,10 @@
"isolate", "isolate",
"isolate-override", "isolate-override",
"italic", "italic",
"jis04",
"jis78",
"jis83",
"jis90",
"jump-both", "jump-both",
"jump-end", "jump-end",
"jump-none", "jump-none",
@ -229,6 +242,7 @@
"lighter", "lighter",
"line-through", "line-through",
"linear", "linear",
"lining-nums",
"linktext", "linktext",
"list-item", "list-item",
"listbox", "listbox",
@ -262,8 +276,12 @@
"nearest", "nearest",
"nesw-resize", "nesw-resize",
"no-close-quote", "no-close-quote",
"no-common-ligatures",
"no-contextual",
"no-discretionary-ligatures",
"no-drop", "no-drop",
"no-open-quote", "no-open-quote",
"no-historical-ligatures",
"no-preference", "no-preference",
"no-repeat", "no-repeat",
"none", "none",
@ -276,12 +294,14 @@
"nwse-resize", "nwse-resize",
"oblique", "oblique",
"off", "off",
"oldstyle-nums",
"on", "on",
"opaque", "opaque",
"open-quote", "open-quote",
"optimizequality", "optimizequality",
"optimizespeed", "optimizespeed",
"optional", "optional",
"ordinal",
"outset", "outset",
"outside", "outside",
"overline", "overline",
@ -289,6 +309,7 @@
"padding-box", "padding-box",
"paged", "paged",
"paused", "paused",
"petite-caps",
"pixelated", "pixelated",
"plaintext", "plaintext",
"pointer", "pointer",
@ -299,6 +320,8 @@
"progress", "progress",
"progress-bar", "progress-bar",
"progressive", "progressive",
"proportional-nums",
"proportional-width",
"push-button", "push-button",
"radio", "radio",
"rec2020", "rec2020",
@ -342,6 +365,8 @@
"serif", "serif",
"sideways-lr", "sideways-lr",
"sideways-rl", "sideways-rl",
"simplified",
"slashed-zero",
"slider-horizontal", "slider-horizontal",
"slow", "slow",
"small", "small",
@ -357,6 +382,7 @@
"square-button", "square-button",
"srgb", "srgb",
"stable", "stable",
"stacked-fractions",
"standalone", "standalone",
"standard", "standard",
"start", "start",
@ -378,6 +404,7 @@
"table-header-group", "table-header-group",
"table-row", "table-row",
"table-row-group", "table-row-group",
"tabular-nums",
"text", "text",
"text-bottom", "text-bottom",
"text-top", "text-top",
@ -385,6 +412,8 @@
"textfield", "textfield",
"thick", "thick",
"thin", "thin",
"titling-caps",
"traditional",
"threeddarkshadow", "threeddarkshadow",
"threedface", "threedface",
"threedhighlight", "threedhighlight",
@ -399,6 +428,8 @@
"ultra-condensed", "ultra-condensed",
"ultra-expanded", "ultra-expanded",
"underline", "underline",
"unicase",
"unicode",
"unsafe", "unsafe",
"unset", "unset",
"up", "up",

View file

@ -5903,6 +5903,455 @@ RefPtr<CSSStyleValue> Parser::parse_font_variation_settings_value(TokenStream<Co
return StyleValueList::create(move(axis_tags), StyleValueList::Separator::Comma); return StyleValueList::create(move(axis_tags), StyleValueList::Separator::Comma);
} }
RefPtr<CSSStyleValue> Parser::parse_font_variant(TokenStream<ComponentValue>& tokens)
{
// 6.11 https://drafts.csswg.org/css-fonts/#propdef-font-variant
// normal | none |
// [ [ <common-lig-values> || <discretionary-lig-values> || <historical-lig-values> || <contextual-alt-values> ]
// || [ small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps ] ||
// [ FIXME: stylistic(<feature-value-name>) ||
// historical-forms ||
// FIXME: styleset(<feature-value-name>#) ||
// FIXME: character-variant(<feature-value-name>#) ||
// FIXME: swash(<feature-value-name>) ||
// FIXME: ornaments(<feature-value-name>) ||
// FIXME: annotation(<feature-value-name>) ] ||
// [ <numeric-figure-values> || <numeric-spacing-values> || <numeric-fraction-values> ||
// ordinal || slashed-zero ] || [ <east-asian-variant-values> || <east-asian-width-values> || ruby ] ||
// [ sub | super ] || [ text | emoji | unicode ] ]
bool has_caps = false;
bool has_alternates = false;
bool has_common_ligatures = false;
bool has_discretionary_ligatures = false;
bool has_historical_ligatures = false;
bool has_contextual = false;
bool has_numeric_figures = false;
bool has_numeric_spacing = false;
bool has_numeric_fractions = false;
bool has_numeric_ordinals = false;
bool has_numeric_slashed_zero = false;
bool has_east_asian_variant = false;
bool has_east_asian_width = false;
bool has_east_asian_ruby = false;
bool has_position = false;
Keyword alternates_value = Keyword::Normal;
Keyword caps_value = Keyword::Normal;
Keyword position_value = Keyword::Normal;
StyleValueVector east_asian_values;
StyleValueVector ligatures_values;
StyleValueVector numeric_values;
StyleValueVector position_values;
// normal
if (auto parsed_value = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) {
/* do nothing */
// none
} else if (auto parsed_value = parse_all_as_single_keyword_value(tokens, Keyword::None)) {
// FIXME
} else {
while (tokens.has_next_token()) {
auto maybe_value = parse_keyword_value(tokens);
if (!maybe_value)
break;
auto value = maybe_value.release_nonnull();
if (!value->is_keyword()) {
// FIXME: alternate functions such as stylistic()
return nullptr;
}
auto keyword = value->to_keyword();
switch (keyword) {
// <common-lig-values> = [ common-ligatures | no-common-ligatures ]
case Keyword::CommonLigatures:
case Keyword::NoCommonLigatures:
if (has_common_ligatures)
return nullptr;
ligatures_values.append(value);
has_common_ligatures = true;
break;
// <discretionary-lig-values> = [ discretionary-ligatures | no-discretionary-ligatures ]
case Keyword::DiscretionaryLigatures:
case Keyword::NoDiscretionaryLigatures:
if (has_discretionary_ligatures)
return nullptr;
ligatures_values.append(value);
has_discretionary_ligatures = true;
break;
// <historical-lig-values> = [ historical-ligatures | no-historical-ligatures ]
case Keyword::HistoricalLigatures:
case Keyword::NoHistoricalLigatures:
if (has_historical_ligatures)
return nullptr;
ligatures_values.append(value);
has_historical_ligatures = true;
break;
// <contextual-alt-values> = [ contextual | no-contextual ]
case Keyword::Contextual:
case Keyword::NoContextual:
if (has_contextual)
return nullptr;
ligatures_values.append(value);
has_contextual = true;
break;
// historical-forms
case Keyword::HistoricalForms:
if (has_alternates)
return nullptr;
alternates_value = keyword;
has_alternates = true;
break;
// [ small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps ]
case Keyword::SmallCaps:
case Keyword::AllSmallCaps:
case Keyword::PetiteCaps:
case Keyword::AllPetiteCaps:
case Keyword::Unicase:
case Keyword::TitlingCaps:
if (has_caps)
return nullptr;
caps_value = keyword;
has_caps = true;
break;
// <numeric-figure-values> = [ lining-nums | oldstyle-nums ]
case Keyword::LiningNums:
case Keyword::OldstyleNums:
if (has_numeric_figures)
return nullptr;
numeric_values.append(value);
has_numeric_figures = true;
break;
// <numeric-spacing-values> = [ proportional-nums | tabular-nums ]
case Keyword::ProportionalNums:
case Keyword::TabularNums:
if (has_numeric_spacing)
return nullptr;
numeric_values.append(value);
has_numeric_spacing = true;
break;
// <numeric-fraction-values> = [ diagonal-fractions | stacked-fractions]
case Keyword::DiagonalFractions:
case Keyword::StackedFractions:
if (has_numeric_fractions)
return nullptr;
numeric_values.append(value);
has_numeric_fractions = true;
break;
// ordinal
case Keyword::Ordinal:
if (has_numeric_ordinals)
return nullptr;
numeric_values.append(value);
has_numeric_ordinals = true;
break;
case Keyword::SlashedZero:
if (has_numeric_slashed_zero)
return nullptr;
numeric_values.append(value);
has_numeric_slashed_zero = true;
break;
// <east-asian-variant-values> = [ jis78 | jis83 | jis90 | jis04 | simplified | traditional ]
case Keyword::Jis78:
case Keyword::Jis83:
case Keyword::Jis90:
case Keyword::Jis04:
case Keyword::Simplified:
case Keyword::Traditional:
if (has_east_asian_variant)
return nullptr;
east_asian_values.append(value);
has_east_asian_variant = true;
break;
// <east-asian-width-values> = [ full-width | proportional-width ]
case Keyword::FullWidth:
case Keyword::ProportionalWidth:
if (has_east_asian_width)
return nullptr;
east_asian_values.append(value);
has_east_asian_width = true;
break;
// ruby
case Keyword::Ruby:
if (has_east_asian_ruby)
return nullptr;
east_asian_values.append(value);
has_east_asian_ruby = true;
break;
// sub | super
case Keyword::Sub:
case Keyword::Super:
if (has_position)
return nullptr;
position_value = keyword;
has_position = true;
break;
default:
break;
}
}
}
if (ligatures_values.is_empty())
ligatures_values.append(CSSKeywordValue::create(Keyword::Normal));
if (numeric_values.is_empty())
numeric_values.append(CSSKeywordValue::create(Keyword::Normal));
if (east_asian_values.is_empty())
east_asian_values.append(CSSKeywordValue::create(Keyword::Normal));
return ShorthandStyleValue::create(PropertyID::FontVariant,
{ PropertyID::FontVariantAlternates,
PropertyID::FontVariantCaps,
PropertyID::FontVariantEastAsian,
PropertyID::FontVariantLigatures,
PropertyID::FontVariantNumeric,
PropertyID::FontVariantPosition },
{
CSSKeywordValue::create(alternates_value),
CSSKeywordValue::create(caps_value),
StyleValueList::create(move(east_asian_values), StyleValueList::Separator::Space),
StyleValueList::create(move(ligatures_values), StyleValueList::Separator::Space),
StyleValueList::create(move(numeric_values), StyleValueList::Separator::Space),
CSSKeywordValue::create(position_value),
});
}
RefPtr<CSSStyleValue> Parser::parse_font_variant_alternates_value(TokenStream<ComponentValue>& tokens)
{
// 6.8 https://drafts.csswg.org/css-fonts/#font-variant-alternates-prop
// normal |
// [ FIXME: stylistic(<feature-value-name>) ||
// historical-forms ||
// FIXME: styleset(<feature-value-name>#) ||
// FIXME: character-variant(<feature-value-name>#) ||
// FIXME: swash(<feature-value-name>) ||
// FIXME: ornaments(<feature-value-name>) ||
// FIXME: annotation(<feature-value-name>) ]
// normal
if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal))
return normal;
// historical-forms
// FIXME: Support this together with other values when we parse them.
if (auto historical_forms = parse_all_as_single_keyword_value(tokens, Keyword::HistoricalForms))
return historical_forms;
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @font-variant-alternate: parsing {} not implemented.", tokens.next_token().to_debug_string());
return nullptr;
}
RefPtr<CSSStyleValue> Parser::parse_font_variant_caps_value(TokenStream<ComponentValue>& tokens)
{
// https://drafts.csswg.org/css-fonts/#propdef-font-variant-caps
// normal | small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps
bool has_token = false;
while (tokens.has_next_token()) {
if (has_token)
return nullptr;
auto maybe_value = parse_keyword_value(tokens);
if (!maybe_value)
break;
auto value = maybe_value.release_nonnull();
auto font_variant = keyword_to_font_variant_caps(value->to_keyword());
if (font_variant.has_value()) {
return value;
}
return nullptr;
}
return nullptr;
}
RefPtr<CSSStyleValue> Parser::parse_font_variant_east_asian_value(TokenStream<ComponentValue>& tokens)
{
// 6.10 https://drafts.csswg.org/css-fonts/#propdef-font-variant-east-asian
// normal | [ <east-asian-variant-values> || <east-asian-width-values> || ruby ]
// <east-asian-variant-values> = [ jis78 | jis83 | jis90 | jis04 | simplified | traditional ]
// <east-asian-width-values> = [ full-width | proportional-width ]
StyleValueVector value_list;
bool has_ruby = false;
bool has_variant = false;
bool has_width = false;
// normal | ...
if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) {
value_list.append(normal.release_nonnull());
} else {
while (tokens.has_next_token()) {
auto maybe_value = parse_keyword_value(tokens);
if (!maybe_value)
break;
auto value = maybe_value.release_nonnull();
auto font_variant = keyword_to_font_variant_east_asian(value->to_keyword());
if (!font_variant.has_value()) {
return nullptr;
}
auto keyword = value->to_keyword();
if (keyword == Keyword::Ruby) {
if (has_ruby)
return nullptr;
has_ruby = true;
} else if (keyword == Keyword::FullWidth || keyword == Keyword::ProportionalWidth) {
if (has_width)
return nullptr;
has_width = true;
} else {
if (has_variant)
return nullptr;
has_variant = true;
}
value_list.append(value);
}
}
if (value_list.is_empty())
return nullptr;
return StyleValueList::create(move(value_list), StyleValueList::Separator::Space);
}
RefPtr<CSSStyleValue> Parser::parse_font_variant_ligatures_value(TokenStream<ComponentValue>& tokens)
{
// 6.4 https://drafts.csswg.org/css-fonts/#propdef-font-variant-ligatures
// normal | none | [ <common-lig-values> || <discretionary-lig-values> || <historical-lig-values> || <contextual-alt-values> ]
// <common-lig-values> = [ common-ligatures | no-common-ligatures ]
// <discretionary-lig-values> = [ discretionary-ligatures | no-discretionary-ligatures ]
// <historical-lig-values> = [ historical-ligatures | no-historical-ligatures ]
// <contextual-alt-values> = [ contextual | no-contextual ]
StyleValueVector value_list;
bool has_common_ligatures = false;
bool has_discretionary_ligatures = false;
bool has_historical_ligatures = false;
bool has_contextual = false;
// normal | ...
if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) {
value_list.append(normal.release_nonnull());
// none | ...
} else if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None)) {
value_list.append(none.release_nonnull());
} else {
while (tokens.has_next_token()) {
auto maybe_value = parse_keyword_value(tokens);
if (!maybe_value)
break;
auto value = maybe_value.release_nonnull();
switch (value->to_keyword()) {
// <common-lig-values> = [ common-ligatures | no-common-ligatures ]
case Keyword::CommonLigatures:
case Keyword::NoCommonLigatures:
if (has_common_ligatures)
return nullptr;
has_common_ligatures = true;
break;
// <discretionary-lig-values> = [ discretionary-ligatures | no-discretionary-ligatures ]
case Keyword::DiscretionaryLigatures:
case Keyword::NoDiscretionaryLigatures:
if (has_discretionary_ligatures)
return nullptr;
has_discretionary_ligatures = true;
break;
// <historical-lig-values> = [ historical-ligatures | no-historical-ligatures ]
case Keyword::HistoricalLigatures:
case Keyword::NoHistoricalLigatures:
if (has_historical_ligatures)
return nullptr;
has_historical_ligatures = true;
break;
// <contextual-alt-values> = [ contextual | no-contextual ]
case Keyword::Contextual:
case Keyword::NoContextual:
if (has_contextual)
return nullptr;
has_contextual = true;
break;
default:
return nullptr;
}
value_list.append(value);
}
}
if (value_list.is_empty())
return nullptr;
return StyleValueList::create(move(value_list), StyleValueList::Separator::Space);
}
RefPtr<CSSStyleValue> Parser::parse_font_variant_numeric_value(TokenStream<ComponentValue>& tokens)
{
// 6.7 https://drafts.csswg.org/css-fonts/#propdef-font-variant-numeric
// normal | [ <numeric-figure-values> || <numeric-spacing-values> || <numeric-fraction-values> || ordinal || slashed-zero]
// <numeric-figure-values> = [ lining-nums | oldstyle-nums ]
// <numeric-spacing-values> = [ proportional-nums | tabular-nums ]
// <numeric-fraction-values> = [ diagonal-fractions | stacked-fractions ]
StyleValueVector value_list;
bool has_figures = false;
bool has_spacing = false;
bool has_fractions = false;
bool has_ordinals = false;
bool has_slashed_zero = false;
// normal | ...
if (auto normal = parse_all_as_single_keyword_value(tokens, Keyword::Normal)) {
value_list.append(normal.release_nonnull());
} else {
while (tokens.has_next_token()) {
auto maybe_value = parse_keyword_value(tokens);
if (!maybe_value)
break;
auto value = maybe_value.release_nonnull();
switch (value->to_keyword()) {
// ... || ordinal
case Keyword::Ordinal:
if (has_ordinals)
return nullptr;
has_ordinals = true;
break;
// ... || slashed-zero
case Keyword::SlashedZero:
if (has_slashed_zero)
return nullptr;
has_slashed_zero = true;
break;
// <numeric-figure-values> = [ lining-nums | oldstyle-nums ]
case Keyword::LiningNums:
case Keyword::OldstyleNums:
if (has_figures)
return nullptr;
has_figures = true;
break;
// <numeric-spacing-values> = [ proportional-nums | tabular-nums ]
case Keyword::ProportionalNums:
case Keyword::TabularNums:
if (has_spacing)
return nullptr;
has_spacing = true;
break;
// <numeric-fraction-values> = [ diagonal-fractions | stacked-fractions ]
case Keyword::DiagonalFractions:
case Keyword::StackedFractions:
if (has_fractions)
return nullptr;
has_fractions = true;
break;
default:
return nullptr;
}
value_list.append(value);
}
}
if (value_list.is_empty())
return nullptr;
return StyleValueList::create(move(value_list), StyleValueList::Separator::Space);
}
Vector<ParsedFontFace::Source> Parser::parse_as_font_face_src() Vector<ParsedFontFace::Source> Parser::parse_as_font_face_src()
{ {
return parse_font_face_src(m_token_stream); return parse_font_face_src(m_token_stream);
@ -7789,6 +8238,30 @@ Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue>> Parser::parse_css_value(Prope
if (auto parsed_value = parse_font_variation_settings_value(tokens); parsed_value && !tokens.has_next_token()) if (auto parsed_value = parse_font_variation_settings_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull(); return parsed_value.release_nonnull();
return ParseError::SyntaxError; return ParseError::SyntaxError;
case PropertyID::FontVariant:
if (auto parsed_value = parse_font_variant(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
case PropertyID::FontVariantAlternates:
if (auto parsed_value = parse_font_variant_alternates_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
case PropertyID::FontVariantCaps:
if (auto parsed_value = parse_font_variant_caps_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
case PropertyID::FontVariantEastAsian:
if (auto parsed_value = parse_font_variant_east_asian_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
case PropertyID::FontVariantLigatures:
if (auto parsed_value = parse_font_variant_ligatures_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
case PropertyID::FontVariantNumeric:
if (auto parsed_value = parse_font_variant_numeric_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
case PropertyID::GridArea: case PropertyID::GridArea:
if (auto parsed_value = parse_grid_area_shorthand_value(tokens); parsed_value && !tokens.has_next_token()) if (auto parsed_value = parse_grid_area_shorthand_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull(); return parsed_value.release_nonnull();

View file

@ -322,6 +322,12 @@ private:
RefPtr<CSSStyleValue> parse_font_language_override_value(TokenStream<ComponentValue>&); RefPtr<CSSStyleValue> parse_font_language_override_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_font_feature_settings_value(TokenStream<ComponentValue>&); RefPtr<CSSStyleValue> parse_font_feature_settings_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_font_variation_settings_value(TokenStream<ComponentValue>&); RefPtr<CSSStyleValue> parse_font_variation_settings_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_font_variant(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_font_variant_alternates_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_font_variant_caps_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_font_variant_east_asian_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_font_variant_ligatures_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_font_variant_numeric_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_list_style_value(TokenStream<ComponentValue>&); RefPtr<CSSStyleValue> parse_list_style_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_math_depth_value(TokenStream<ComponentValue>&); RefPtr<CSSStyleValue> parse_math_depth_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_overflow_value(TokenStream<ComponentValue>&); RefPtr<CSSStyleValue> parse_overflow_value(TokenStream<ComponentValue>&);

View file

@ -1247,7 +1247,69 @@
"inherited": true, "inherited": true,
"initial": "normal", "initial": "normal",
"valid-types": [ "valid-types": [
"font-variant" "font-variant-alternates",
"font-variant-caps",
"font-variant-east-asian",
"font-variant-emoji",
"font-variant-ligatures",
"font-variant-numeric",
"font-variant-position"
]
},
"font-variant-alternates": {
"animation-type": "discrete",
"inherited": true,
"initial": "normal",
"valid-types": [
"font-variant-alternates"
]
},
"font-variant-caps": {
"animation-type": "discrete",
"inherited": true,
"initial": "normal",
"valid-types": [
"font-variant-caps"
]
},
"font-variant-east-asian": {
"animation-type": "discrete",
"inherited": true,
"initial": "normal",
"valid-types": [
"font-variant-east-asian"
]
},
"font-variant-emoji": {
"animation-type": "discrete",
"inherited": true,
"initial": "normal",
"valid-types": [
"font-variant-emoji"
]
},
"font-variant-ligatures": {
"animation-type": "discrete",
"inherited": true,
"initial": "normal",
"valid-types": [
"font-variant-ligatures"
]
},
"font-variant-numeric": {
"animation-type": "discrete",
"inherited": true,
"initial": "normal",
"valid-types": [
"font-variant-numeric"
]
},
"font-variant-position": {
"animation-type": "discrete",
"inherited": true,
"initial": "normal",
"valid-types": [
"font-variant-position"
] ]
}, },
"font-variation-settings": { "font-variation-settings": {

View file

@ -260,7 +260,7 @@ ErrorOr<NonnullRefPtr<Gfx::Typeface>> FontLoader::try_load_font()
mime_type = MimeSniff::Resource::sniff(resource()->encoded_data(), Web::MimeSniff::SniffingConfiguration { .sniffing_context = Web::MimeSniff::SniffingContext::Font }); mime_type = MimeSniff::Resource::sniff(resource()->encoded_data(), Web::MimeSniff::SniffingConfiguration { .sniffing_context = Web::MimeSniff::SniffingContext::Font });
} }
if (mime_type.has_value()) { if (mime_type.has_value()) {
if (mime_type->essence() == "font/ttf"sv || mime_type->essence() == "application/x-font-ttf"sv) { if (mime_type->essence() == "font/ttf"sv || mime_type->essence() == "application/x-font-ttf"sv || mime_type->essence() == "font/otf"sv) {
if (auto result = Gfx::Typeface::try_load_from_externally_owned_memory(resource()->encoded_data()); !result.is_error()) { if (auto result = Gfx::Typeface::try_load_from_externally_owned_memory(resource()->encoded_data()); !result.is_error()) {
return result; return result;
} }
@ -2111,6 +2111,13 @@ void StyleComputer::compute_font(StyleProperties& style, DOM::Element const* ele
compute_defaulted_property_value(style, element, CSS::PropertyID::FontStyle, pseudo_element); compute_defaulted_property_value(style, element, CSS::PropertyID::FontStyle, pseudo_element);
compute_defaulted_property_value(style, element, CSS::PropertyID::FontWeight, pseudo_element); compute_defaulted_property_value(style, element, CSS::PropertyID::FontWeight, pseudo_element);
compute_defaulted_property_value(style, element, CSS::PropertyID::LineHeight, pseudo_element); compute_defaulted_property_value(style, element, CSS::PropertyID::LineHeight, pseudo_element);
compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariant, pseudo_element);
compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantAlternates, pseudo_element);
compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantCaps, pseudo_element);
compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantEastAsian, pseudo_element);
compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantLigatures, pseudo_element);
compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantNumeric, pseudo_element);
compute_defaulted_property_value(style, element, CSS::PropertyID::FontVariantPosition, pseudo_element);
auto const& font_family = style.property(CSS::PropertyID::FontFamily); auto const& font_family = style.property(CSS::PropertyID::FontFamily);
auto const& font_size = style.property(CSS::PropertyID::FontSize); auto const& font_size = style.property(CSS::PropertyID::FontSize);

View file

@ -1143,12 +1143,6 @@ Variant<CSS::VerticalAlign, CSS::LengthPercentage> StyleProperties::vertical_ali
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
Optional<CSS::FontVariant> StyleProperties::font_variant() const
{
auto const& value = property(CSS::PropertyID::FontVariant);
return keyword_to_font_variant(value.to_keyword());
}
Optional<FlyString> StyleProperties::font_language_override() const Optional<FlyString> StyleProperties::font_language_override() const
{ {
auto const& value = property(CSS::PropertyID::FontLanguageOverride); auto const& value = property(CSS::PropertyID::FontLanguageOverride);
@ -1157,6 +1151,42 @@ Optional<FlyString> StyleProperties::font_language_override() const
return {}; return {};
} }
Gfx::FontVariantAlternates StyleProperties::font_variant_alternates() const
{
auto const& value = property(CSS::PropertyID::FontVariantAlternates);
return value.to_font_variant_alternates();
}
Optional<FontVariantCaps> StyleProperties::font_variant_caps() const
{
auto const& value = property(CSS::PropertyID::FontVariantCaps);
return value.to_font_variant_caps();
}
Gfx::FontVariantEastAsian StyleProperties::font_variant_east_asian() const
{
auto const& value = property(CSS::PropertyID::FontVariantEastAsian);
return value.to_font_variant_east_asian();
}
Gfx::FontVariantLigatures StyleProperties::font_variant_ligatures() const
{
auto const& value = property(CSS::PropertyID::FontVariantLigatures);
return value.to_font_variant_ligatures();
}
Gfx::FontVariantNumeric StyleProperties::font_variant_numeric() const
{
auto const& value = property(CSS::PropertyID::FontVariantNumeric);
return value.to_font_variant_numeric();
}
Optional<FontVariantPosition> StyleProperties::font_variant_position() const
{
auto const& value = property(CSS::PropertyID::FontVariantPosition);
return value.to_font_variant_position();
}
Optional<HashMap<FlyString, IntegerOrCalculated>> StyleProperties::font_feature_settings() const Optional<HashMap<FlyString, IntegerOrCalculated>> StyleProperties::font_feature_settings() const
{ {
auto const& value = property(PropertyID::FontFeatureSettings); auto const& value = property(PropertyID::FontFeatureSettings);

View file

@ -149,7 +149,12 @@ public:
Optional<CSS::BoxSizing> box_sizing() const; Optional<CSS::BoxSizing> box_sizing() const;
Optional<CSS::PointerEvents> pointer_events() const; Optional<CSS::PointerEvents> pointer_events() const;
Variant<CSS::VerticalAlign, CSS::LengthPercentage> vertical_align() const; Variant<CSS::VerticalAlign, CSS::LengthPercentage> vertical_align() const;
Optional<CSS::FontVariant> font_variant() const; Gfx::FontVariantAlternates font_variant_alternates() const;
Optional<FontVariantCaps> font_variant_caps() const;
Gfx::FontVariantEastAsian font_variant_east_asian() const;
Gfx::FontVariantLigatures font_variant_ligatures() const;
Gfx::FontVariantNumeric font_variant_numeric() const;
Optional<FontVariantPosition> font_variant_position() const;
Optional<FlyString> font_language_override() const; Optional<FlyString> font_language_override() const;
Optional<HashMap<FlyString, IntegerOrCalculated>> font_feature_settings() const; Optional<HashMap<FlyString, IntegerOrCalculated>> font_feature_settings() const;
Optional<HashMap<FlyString, NumberOrCalculated>> font_variation_settings() const; Optional<HashMap<FlyString, NumberOrCalculated>> font_variation_settings() const;

View file

@ -119,6 +119,60 @@ String ShorthandStyleValue::to_string() const
longhand(PropertyID::FontSize)->to_string(), longhand(PropertyID::FontSize)->to_string(),
longhand(PropertyID::LineHeight)->to_string(), longhand(PropertyID::LineHeight)->to_string(),
longhand(PropertyID::FontFamily)->to_string())); longhand(PropertyID::FontFamily)->to_string()));
case PropertyID::FontVariant: {
Vector<StringView> values;
auto alternates = longhand(PropertyID::FontVariantAlternates)->to_font_variant_alternates();
if (alternates.historical_forms)
values.append("historical-forms"sv);
auto caps_or_null = longhand(PropertyID::FontVariantCaps)->to_font_variant_caps();
if (caps_or_null.has_value() && caps_or_null.value() != CSS::FontVariantCaps::Normal) {
values.append(CSS::to_string(caps_or_null.release_value()));
}
auto east_asian = longhand(PropertyID::FontVariantEastAsian)->to_font_variant_east_asian();
if (!east_asian.normal) {
if (east_asian.variant != Gfx::FontVariantEastAsian::Variant::Unset)
values.append(Gfx::font_variant_east_asian_to_string(east_asian));
if (east_asian.width != Gfx::FontVariantEastAsian::Width::Unset)
values.append(Gfx::font_variant_east_asian_to_string(east_asian));
}
auto ligatures = longhand(PropertyID::FontVariantLigatures)->to_font_variant_ligatures();
if (!ligatures.normal && !ligatures.none) {
if (ligatures.common != Gfx::FontVariantLigatures::Common::Unset)
values.append(Gfx::font_variant_ligatures_to_string(ligatures));
if (ligatures.discretionary != Gfx::FontVariantLigatures::Discretionary::Unset) {
values.append(Gfx::font_variant_ligatures_to_string(ligatures));
}
if (ligatures.historical != Gfx::FontVariantLigatures::Historical::Unset) {
values.append(Gfx::font_variant_ligatures_to_string(ligatures));
}
if (ligatures.contextual != Gfx::FontVariantLigatures::Contextual::Unset) {
values.append(Gfx::font_variant_ligatures_to_string(ligatures));
}
}
auto numeric = longhand(PropertyID::FontVariantNumeric)->to_font_variant_numeric();
if (!numeric.normal) {
if (numeric.ordinal)
values.append("ordinal"sv);
if (numeric.slashed_zero)
values.append("slashed-zero"sv);
if (numeric.figure != Gfx::FontVariantNumeric::Figure::Unset)
values.append(Gfx::font_variant_numeric_to_string(numeric));
if (numeric.spacing != Gfx::FontVariantNumeric::Spacing::Unset)
values.append(Gfx::font_variant_numeric_to_string(numeric));
if (numeric.fraction != Gfx::FontVariantNumeric::Fraction::Unset)
values.append(Gfx::font_variant_numeric_to_string(numeric));
}
auto position_or_null = longhand(PropertyID::FontVariantPosition)->to_font_variant_position();
if (position_or_null.has_value() && position_or_null.value() != CSS::FontVariantPosition::Normal) {
values.append(CSS::to_string(position_or_null.release_value()));
}
StringBuilder builder;
if (values.is_empty())
builder.append("normal"sv);
else
builder.join(' ', values);
return MUST(builder.to_string());
}
case PropertyID::GridArea: { case PropertyID::GridArea: {
auto& row_start = longhand(PropertyID::GridRowStart)->as_grid_track_placement(); auto& row_start = longhand(PropertyID::GridRowStart)->as_grid_track_placement();
auto& column_start = longhand(PropertyID::GridColumnStart)->as_grid_track_placement(); auto& column_start = longhand(PropertyID::GridColumnStart)->as_grid_track_placement();

View file

@ -1606,7 +1606,7 @@ GC::Ref<HTML::HTMLAllCollection> Document::all()
GC::Ref<CSS::FontFaceSet> Document::fonts() GC::Ref<CSS::FontFaceSet> Document::fonts()
{ {
if (!m_fonts) if (!m_fonts)
m_fonts = CSS::FontFaceSet::create(realm()); m_fonts = CSS::FontFaceSet::create(page(), realm());
return *m_fonts; return *m_fonts;
} }

View file

@ -575,7 +575,7 @@ CanvasRenderingContext2D::PreparedText CanvasRenderingContext2D::prepare_text(By
Gfx::FloatPoint anchor { 0, 0 }; Gfx::FloatPoint anchor { 0, 0 };
auto physical_alignment = Gfx::TextAlignment::CenterLeft; auto physical_alignment = Gfx::TextAlignment::CenterLeft;
auto glyph_run = Gfx::shape_text(anchor, 0, replaced_text.code_points(), *font, Gfx::GlyphRun::TextType::Ltr); auto glyph_run = Gfx::shape_text(anchor, 0, replaced_text.code_points(), *font, Gfx::GlyphRun::TextType::Ltr, {});
// 8. Let result be an array constructed by iterating over each glyph in the inline box from left to right (if any), adding to the array, for each glyph, the shape of the glyph as it is in the inline box, positioned on a coordinate space using CSS pixels with its origin is at the anchor point. // 8. Let result be an array constructed by iterating over each glyph in the inline box from left to right (if any), adding to the array, for each glyph, the shape of the glyph as it is in the inline box, positioned on a coordinate space using CSS pixels with its origin is at the anchor point.
PreparedText prepared_text { glyph_run, physical_alignment, { 0, 0, static_cast<int>(glyph_run->width()), static_cast<int>(height) } }; PreparedText prepared_text { glyph_run, physical_alignment, { 0, 0, static_cast<int>(glyph_run->width()), static_cast<int>(height) } };

View file

@ -161,7 +161,7 @@ ENUMERATE_WORKER_GLOBAL_SCOPE_EVENT_HANDLERS(__ENUMERATE)
GC::Ref<CSS::FontFaceSet> WorkerGlobalScope::fonts() GC::Ref<CSS::FontFaceSet> WorkerGlobalScope::fonts()
{ {
if (!m_fonts) if (!m_fonts)
m_fonts = CSS::FontFaceSet::create(realm()); m_fonts = CSS::FontFaceSet::create(*page(), realm());
return *m_fonts; return *m_fonts;
} }

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <LibGfx/Font/FontVariant.h>
#include <LibWeb/Layout/BreakNode.h> #include <LibWeb/Layout/BreakNode.h>
#include <LibWeb/Layout/InlineFormattingContext.h> #include <LibWeb/Layout/InlineFormattingContext.h>
#include <LibWeb/Layout/InlineLevelIterator.h> #include <LibWeb/Layout/InlineLevelIterator.h>
@ -200,6 +201,255 @@ Gfx::GlyphRun::TextType InlineLevelIterator::resolve_text_direction_from_context
return Gfx::GlyphRun::TextType::ContextDependent; return Gfx::GlyphRun::TextType::ContextDependent;
} }
HashMap<StringView, u8> InlineLevelIterator::shape_features_map() const
{
HashMap<StringView, u8> features;
auto& computed_values = m_current_node->computed_values();
// 6.4 https://drafts.csswg.org/css-fonts/#font-variant-ligatures-prop
auto ligature = computed_values.font_variant_ligatures();
if (ligature.normal) {
// A value of normal specifies that common default features are enabled, as described in detail in the next section.
features.set("liga"sv, 1);
features.set("clig"sv, 1);
} else if (ligature.none) {
/* nothing */
} else {
switch (ligature.common) {
case Gfx::FontVariantLigatures::Common::Common:
// Enables display of common ligatures (OpenType features: liga, clig).
features.set("liga"sv, 1);
features.set("clig"sv, 1);
break;
case Gfx::FontVariantLigatures::Common::NoCommon:
// Disables display of common ligatures (OpenType features: liga, clig).
features.set("liga"sv, 0);
features.set("clig"sv, 0);
break;
case Gfx::FontVariantLigatures::Common::Unset:
break;
}
switch (ligature.discretionary) {
case Gfx::FontVariantLigatures::Discretionary::Discretionary:
// Enables display of discretionary ligatures (OpenType feature: dlig).
features.set("dlig"sv, 1);
break;
case Gfx::FontVariantLigatures::Discretionary::NoDiscretionary:
// Disables display of discretionary ligatures (OpenType feature: dlig).
features.set("dlig"sv, 0);
break;
case Gfx::FontVariantLigatures::Discretionary::Unset:
break;
}
switch (ligature.historical) {
case Gfx::FontVariantLigatures::Historical::Historical:
// Enables display of historical ligatures (OpenType feature: hlig).
features.set("hlig"sv, 1);
break;
case Gfx::FontVariantLigatures::Historical::NoHistorical:
// Disables display of historical ligatures (OpenType feature: hlig).
features.set("hlig"sv, 0);
break;
case Gfx::FontVariantLigatures::Historical::Unset:
break;
}
switch (ligature.contextual) {
case Gfx::FontVariantLigatures::Contextual::Contextual:
// Enables display of contextual ligatures (OpenType feature: calt).
features.set("calt"sv, 1);
break;
case Gfx::FontVariantLigatures::Contextual::NoContextual:
// Disables display of contextual ligatures (OpenType feature: calt).
features.set("calt"sv, 0);
break;
case Gfx::FontVariantLigatures::Contextual::Unset:
break;
}
}
// 6.5 https://drafts.csswg.org/css-fonts/#font-variant-position-prop
switch (computed_values.font_variant_position()) {
case CSS::FontVariantPosition::Normal:
// None of the features listed below are enabled.
break;
case CSS::FontVariantPosition::Sub:
// Enables display of subscripts (OpenType feature: subs).
features.set("subs"sv, 1);
break;
case CSS::FontVariantPosition::Super:
// Enables display of superscripts (OpenType feature: sups).
features.set("sups"sv, 1);
break;
default:
break;
}
// 6.6 https://drafts.csswg.org/css-fonts/#font-variant-caps-prop
switch (computed_values.font_variant_caps()) {
case CSS::FontVariantCaps::Normal:
// None of the features listed below are enabled.
break;
case CSS::FontVariantCaps::SmallCaps:
// Enables display of small capitals (OpenType feature: smcp). Small-caps glyphs typically use the form of uppercase letters but are reduced to the size of lowercase letters.
features.set("smcp"sv, 1);
break;
case CSS::FontVariantCaps::AllSmallCaps:
// Enables display of small capitals for both upper and lowercase letters (OpenType features: c2sc, smcp).
features.set("c2sc"sv, 1);
features.set("smcp"sv, 1);
break;
case CSS::FontVariantCaps::PetiteCaps:
// Enables display of petite capitals (OpenType feature: pcap).
features.set("pcap"sv, 1);
break;
case CSS::FontVariantCaps::AllPetiteCaps:
// Enables display of petite capitals for both upper and lowercase letters (OpenType features: c2pc, pcap).
features.set("c2pc"sv, 1);
features.set("pcap"sv, 1);
break;
case CSS::FontVariantCaps::Unicase:
// Enables display of mixture of small capitals for uppercase letters with normal lowercase letters (OpenType feature: unic).
features.set("unic"sv, 1);
break;
case CSS::FontVariantCaps::TitlingCaps:
// Enables display of titling capitals (OpenType feature: titl).
features.set("titl"sv, 1);
break;
default:
break;
}
// 6.7 https://drafts.csswg.org/css-fonts/#font-variant-numeric-prop
auto numeric = computed_values.font_variant_numeric();
// None of the features listed below are enabled.
if (numeric.normal) {
/* passthrough */
}
if (numeric.figure == Gfx::FontVariantNumeric::Figure::Oldstyle) {
// Enables display of old-style numerals (OpenType feature: onum).
features.set("onum"sv, 1);
} else if (numeric.figure == Gfx::FontVariantNumeric::Figure::Lining) {
// Enables display of lining numerals (OpenType feature: lnum).
features.set("lnum"sv, 1);
}
if (numeric.spacing == Gfx::FontVariantNumeric::Spacing::Proportional) {
// Enables display of proportional numerals (OpenType feature: pnum).
features.set("pnum"sv, 1);
} else if (numeric.spacing == Gfx::FontVariantNumeric::Spacing::Tabular) {
// Enables display of tabular numerals (OpenType feature: tnum).
features.set("tnum"sv, 1);
}
if (numeric.fraction == Gfx::FontVariantNumeric::Fraction::Diagonal) {
// Enables display of diagonal fractions (OpenType feature: frac).
features.set("frac"sv, 1);
} else if (numeric.fraction == Gfx::FontVariantNumeric::Fraction::Stacked) {
// Enables display of stacked fractions (OpenType feature: afrc).
features.set("afrc"sv, 1);
}
if (numeric.ordinal) {
// Enables display of letter forms used with ordinal numbers (OpenType feature: ordn).
features.set("ordn"sv, 1);
}
if (numeric.slashed_zero) {
// Enables display of slashed zeros (OpenType feature: zero).
features.set("zero"sv, 1);
}
// 6.10 https://drafts.csswg.org/css-fonts/#font-variant-east-asian-prop
auto east_asian = computed_values.font_variant_east_asian();
switch (east_asian.variant) {
case Gfx::FontVariantEastAsian::Variant::Jis78:
// Enables display of JIS78 forms (OpenType feature: jp78).
features.set("jp78"sv, 1);
break;
case Gfx::FontVariantEastAsian::Variant::Jis83:
// Enables display of JIS83 forms (OpenType feature: jp83).
features.set("jp83"sv, 1);
break;
case Gfx::FontVariantEastAsian::Variant::Jis90:
// Enables display of JIS90 forms (OpenType feature: jp90).
features.set("jp90"sv, 1);
break;
case Gfx::FontVariantEastAsian::Variant::Jis04:
// Enables display of JIS04 forms (OpenType feature: jp04).
features.set("jp04"sv, 1);
break;
case Gfx::FontVariantEastAsian::Variant::Simplified:
// Enables display of simplified forms (OpenType feature: smpl).
features.set("smpl"sv, 1);
break;
case Gfx::FontVariantEastAsian::Variant::Traditional:
// Enables display of traditional forms (OpenType feature: trad).
features.set("trad"sv, 1);
break;
default:
break;
}
switch (east_asian.width) {
case Gfx::FontVariantEastAsian::Width::FullWidth:
// Enables display of full-width forms (OpenType feature: fwid).
features.set("fwid"sv, 1);
break;
case Gfx::FontVariantEastAsian::Width::Proportional:
// Enables display of proportional-width forms (OpenType feature: pwid).
features.set("pwid"sv, 1);
break;
default:
break;
}
if (east_asian.ruby) {
// Enables display of ruby forms (OpenType feature: ruby).
features.set("ruby"sv, 1);
}
return features;
}
Gfx::ShapeFeatures InlineLevelIterator::create_and_merge_font_features() const
{
HashMap<StringView, u8> merged_features;
auto& computed_values = m_inline_formatting_context.containing_block().computed_values();
// https://www.w3.org/TR/css-fonts-3/#feature-precedence
// FIXME 1. Font features enabled by default, including features required for a given script.
// FIXME 2. If the font is defined via an @font-face rule, the font features implied by the font-feature-settings descriptor in the @font-face rule.
// 3. Font features implied by the value of the font-variant property, the related font-variant subproperties and any other CSS property that uses OpenType features (e.g. the font-kerning property).
for (auto& it : shape_features_map()) {
merged_features.set(it.key, it.value);
}
// FIXME 4. Feature settings determined by properties other than font-variant or font-feature-settings. For example, setting a non-default value for the letter-spacing property disables common ligatures.
// 5. Font features implied by the value of font-feature-settings property.
auto font_feature_settings = computed_values.font_feature_settings();
if (font_feature_settings.has_value()) {
auto const& feature_settings = font_feature_settings.value();
for (auto const& [key, feature_value] : feature_settings) {
merged_features.set(key, feature_value.resolved(*m_current_node.ptr()));
}
}
Gfx::ShapeFeatures shape_features;
shape_features.ensure_capacity(merged_features.size());
for (auto& it : merged_features) {
shape_features.append({ { it.key[0], it.key[1], it.key[2], it.key[3] }, static_cast<u32>(it.value) });
}
return shape_features;
}
Optional<InlineLevelIterator::Item> InlineLevelIterator::next_without_lookahead() Optional<InlineLevelIterator::Item> InlineLevelIterator::next_without_lookahead()
{ {
if (!m_current_node) if (!m_current_node)
@ -293,7 +543,8 @@ Optional<InlineLevelIterator::Item> InlineLevelIterator::next_without_lookahead(
x = tab_stop_dist.to_float(); x = tab_stop_dist.to_float();
} }
auto glyph_run = Gfx::shape_text({ x, 0 }, letter_spacing.to_float(), chunk.view, chunk.font, text_type); auto shape_features = create_and_merge_font_features();
auto glyph_run = Gfx::shape_text({ x, 0 }, letter_spacing.to_float(), chunk.view, chunk.font, text_type, shape_features);
CSSPixels chunk_width = CSSPixels::nearest_value_for(glyph_run->width()); CSSPixels chunk_width = CSSPixels::nearest_value_for(glyph_run->width());

View file

@ -68,6 +68,9 @@ private:
void add_extra_box_model_metrics_to_item(Item&, bool add_leading_metrics, bool add_trailing_metrics); void add_extra_box_model_metrics_to_item(Item&, bool add_leading_metrics, bool add_trailing_metrics);
HashMap<StringView, u8> shape_features_map() const;
Gfx::ShapeFeatures create_and_merge_font_features() const;
Layout::Node const* next_inline_node_in_pre_order(Layout::Node const& current, Layout::Node const* stay_within); Layout::Node const* next_inline_node_in_pre_order(Layout::Node const& current, Layout::Node const* stay_within);
Layout::InlineFormattingContext& m_inline_formatting_context; Layout::InlineFormattingContext& m_inline_formatting_context;

View file

@ -458,10 +458,20 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
if (auto box_sizing = computed_style.box_sizing(); box_sizing.has_value()) if (auto box_sizing = computed_style.box_sizing(); box_sizing.has_value())
computed_values.set_box_sizing(box_sizing.release_value()); computed_values.set_box_sizing(box_sizing.release_value());
if (auto maybe_font_variant = computed_style.font_variant(); maybe_font_variant.has_value())
computed_values.set_font_variant(maybe_font_variant.release_value());
if (auto maybe_font_language_override = computed_style.font_language_override(); maybe_font_language_override.has_value()) if (auto maybe_font_language_override = computed_style.font_language_override(); maybe_font_language_override.has_value())
computed_values.set_font_language_override(maybe_font_language_override.release_value()); computed_values.set_font_language_override(maybe_font_language_override.release_value());
if (auto font_variant_alternates = computed_style.font_variant_alternates(); !font_variant_alternates.normal)
computed_values.set_font_variant_alternates(font_variant_alternates);
if (auto maybe_font_variant_caps = computed_style.font_variant_caps(); maybe_font_variant_caps.has_value())
computed_values.set_font_variant_caps(maybe_font_variant_caps.release_value());
if (auto font_variant_east_asian = computed_style.font_variant_east_asian(); !font_variant_east_asian.normal)
computed_values.set_font_variant_east_asian(font_variant_east_asian);
if (auto font_variant_ligatures = computed_style.font_variant_ligatures(); !font_variant_ligatures.normal)
computed_values.set_font_variant_ligatures(font_variant_ligatures);
if (auto font_variant_numeric = computed_style.font_variant_numeric(); !font_variant_numeric.normal)
computed_values.set_font_variant_numeric(font_variant_numeric);
if (auto maybe_font_variant_position = computed_style.font_variant_position(); maybe_font_variant_position.has_value())
computed_values.set_font_variant_position(maybe_font_variant_position.release_value());
if (auto maybe_font_feature_settings = computed_style.font_feature_settings(); maybe_font_feature_settings.has_value()) if (auto maybe_font_feature_settings = computed_style.font_feature_settings(); maybe_font_feature_settings.has_value())
computed_values.set_font_feature_settings(maybe_font_feature_settings.release_value()); computed_values.set_font_feature_settings(maybe_font_feature_settings.release_value());
if (auto maybe_font_variation_settings = computed_style.font_variation_settings(); maybe_font_variation_settings.has_value()) if (auto maybe_font_variation_settings = computed_style.font_variation_settings(); maybe_font_variation_settings.has_value())

View file

@ -578,6 +578,14 @@ Vector<GC::Root<DOM::Document>> Page::documents_in_active_window() const
return documents; return documents;
} }
URL::URL Page::url() const
{
if (!top_level_traversable_is_initialized())
return {};
return top_level_traversable()->active_document()->url();
}
void Page::clear_selection() void Page::clear_selection()
{ {
for (auto const& document : documents_in_active_window()) { for (auto const& document : documents_in_active_window()) {

View file

@ -212,6 +212,8 @@ public:
FindInPageResult find_in_page_previous_match(); FindInPageResult find_in_page_previous_match();
Optional<FindInPageQuery> last_find_in_page_query() const { return m_last_find_in_page_query; } Optional<FindInPageQuery> last_find_in_page_query() const { return m_last_find_in_page_query; }
URL::URL url() const;
private: private:
explicit Page(GC::Ref<PageClient>); explicit Page(GC::Ref<PageClient>);
virtual void visit_edges(Visitor&) override; virtual void visit_edges(Visitor&) override;
@ -368,6 +370,7 @@ public:
virtual void page_did_request_select_dropdown([[maybe_unused]] Web::CSSPixelPoint content_position, [[maybe_unused]] Web::CSSPixels minimum_width, [[maybe_unused]] Vector<Web::HTML::SelectItem> items) { } virtual void page_did_request_select_dropdown([[maybe_unused]] Web::CSSPixelPoint content_position, [[maybe_unused]] Web::CSSPixels minimum_width, [[maybe_unused]] Vector<Web::HTML::SelectItem> items) { }
virtual void page_did_finish_text_test([[maybe_unused]] String const& text) { } virtual void page_did_finish_text_test([[maybe_unused]] String const& text) { }
virtual void page_did_finish_loading_page_and_fonts([[maybe_unused]] URL::URL const& url) { }
virtual void page_did_change_theme_color(Gfx::Color) { } virtual void page_did_change_theme_color(Gfx::Color) { }

View file

@ -220,7 +220,7 @@ void DisplayListRecorder::draw_text(Gfx::IntRect const& rect, String raw_text, G
if (rect.is_empty()) if (rect.is_empty())
return; return;
auto glyph_run = Gfx::shape_text({}, 0, raw_text.code_points(), font, Gfx::GlyphRun::TextType::Ltr); auto glyph_run = Gfx::shape_text({}, 0, raw_text.code_points(), font, Gfx::GlyphRun::TextType::Ltr, {});
float baseline_x = 0; float baseline_x = 0;
if (alignment == Gfx::TextAlignment::CenterLeft) { if (alignment == Gfx::TextAlignment::CenterLeft) {
baseline_x = rect.x(); baseline_x = rect.x();

View file

@ -231,11 +231,14 @@ public:
Function<void(String const&)> on_inspector_executed_console_script; Function<void(String const&)> on_inspector_executed_console_script;
Function<void(String const&)> on_inspector_exported_inspector_html; Function<void(String const&)> on_inspector_exported_inspector_html;
Function<void()> on_web_content_crashed; Function<void()> on_web_content_crashed;
Function<void(URL::URL const&)> on_loading_page_and_fonts_finish;
virtual Web::DevicePixelSize viewport_size() const = 0; virtual Web::DevicePixelSize viewport_size() const = 0;
virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const = 0; virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const = 0;
virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const = 0; virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const = 0;
u64 page_id() const;
protected: protected:
static constexpr auto ZOOM_MIN_LEVEL = 0.3f; static constexpr auto ZOOM_MIN_LEVEL = 0.3f;
static constexpr auto ZOOM_MAX_LEVEL = 5.0f; static constexpr auto ZOOM_MAX_LEVEL = 5.0f;
@ -245,7 +248,6 @@ protected:
WebContentClient& client(); WebContentClient& client();
WebContentClient const& client() const; WebContentClient const& client() const;
u64 page_id() const;
virtual void update_zoom() = 0; virtual void update_zoom() = 0;
void handle_resize(); void handle_resize();

View file

@ -93,6 +93,15 @@ void WebContentClient::did_finish_text_test(u64 page_id, String const& text)
} }
} }
void WebContentClient::did_finish_loading_page_and_fonts(u64 page_id, URL::URL const& url)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_loading_page_and_fonts_finish)
view->on_loading_page_and_fonts_finish(url);
}
}
void WebContentClient::did_find_in_page(u64 page_id, size_t current_match_index, Optional<size_t> const& total_match_count) void WebContentClient::did_find_in_page(u64 page_id, size_t current_match_index, Optional<size_t> const& total_match_count)
{ {
if (auto view = view_for_page_id(page_id); view.has_value()) { if (auto view = view_for_page_id(page_id); view.has_value()) {

View file

@ -108,6 +108,7 @@ private:
virtual void did_request_select_dropdown(u64 page_id, Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> const& items) override; virtual void did_request_select_dropdown(u64 page_id, Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> const& items) override;
virtual void did_finish_handling_input_event(u64 page_id, Web::EventResult event_result) override; virtual void did_finish_handling_input_event(u64 page_id, Web::EventResult event_result) override;
virtual void did_finish_text_test(u64 page_id, String const& text) override; virtual void did_finish_text_test(u64 page_id, String const& text) override;
virtual void did_finish_loading_page_and_fonts(u64 page_id, URL::URL const& url) override;
virtual void did_find_in_page(u64 page_id, size_t current_match_index, Optional<size_t> const& total_match_count) override; virtual void did_find_in_page(u64 page_id, size_t current_match_index, Optional<size_t> const& total_match_count) override;
virtual void did_change_theme_color(u64 page_id, Gfx::Color color) override; virtual void did_change_theme_color(u64 page_id, Gfx::Color color) override;
virtual void did_insert_clipboard_entry(u64 page_id, String const& data, String const& presentation_style, String const& mime_type) override; virtual void did_insert_clipboard_entry(u64 page_id, String const& data, String const& presentation_style, String const& mime_type) override;

View file

@ -140,6 +140,7 @@ void ConnectionFromClient::update_screen_rects(u64 page_id, Vector<Web::DevicePi
void ConnectionFromClient::load_url(u64 page_id, const URL::URL& url) void ConnectionFromClient::load_url(u64 page_id, const URL::URL& url)
{ {
dbgln("ConnectionFromClient::load_url({}, {})", page_id, url);
auto page = this->page(page_id); auto page = this->page(page_id);
if (!page.has_value()) if (!page.has_value())
return; return;

View file

@ -355,6 +355,12 @@ void PageClient::page_did_finish_text_test(String const& text)
client().async_did_finish_text_test(m_id, text); client().async_did_finish_text_test(m_id, text);
} }
void PageClient::page_did_finish_loading_page_and_fonts(URL::URL const& url)
{
dbgln("PageClient::page_did_finish_loading_page_and_fonts: {}", url);
client().async_did_finish_loading_page_and_fonts(m_id, url);
}
void PageClient::page_did_request_context_menu(Web::CSSPixelPoint content_position) void PageClient::page_did_request_context_menu(Web::CSSPixelPoint content_position)
{ {
client().async_did_request_context_menu(m_id, page().css_to_device_point(content_position).to_type<int>()); client().async_did_request_context_menu(m_id, page().css_to_device_point(content_position).to_type<int>());

View file

@ -155,6 +155,7 @@ private:
virtual void page_did_request_file_picker(Web::HTML::FileFilter accepted_file_types, Web::HTML::AllowMultipleFiles) override; virtual void page_did_request_file_picker(Web::HTML::FileFilter accepted_file_types, Web::HTML::AllowMultipleFiles) override;
virtual void page_did_request_select_dropdown(Web::CSSPixelPoint content_position, Web::CSSPixels minimum_width, Vector<Web::HTML::SelectItem> items) override; virtual void page_did_request_select_dropdown(Web::CSSPixelPoint content_position, Web::CSSPixels minimum_width, Vector<Web::HTML::SelectItem> items) override;
virtual void page_did_finish_text_test(String const& text) override; virtual void page_did_finish_text_test(String const& text) override;
virtual void page_did_finish_loading_page_and_fonts(URL::URL const& url) override;
virtual void page_did_change_theme_color(Gfx::Color color) override; virtual void page_did_change_theme_color(Gfx::Color color) override;
virtual void page_did_insert_clipboard_entry(String data, String presentation_style, String mime_type) override; virtual void page_did_insert_clipboard_entry(String data, String presentation_style, String mime_type) override;
virtual void page_did_change_audio_play_state(Web::HTML::AudioPlayState) override; virtual void page_did_change_audio_play_state(Web::HTML::AudioPlayState) override;

View file

@ -95,7 +95,7 @@ endpoint WebContentClient
did_get_js_console_messages(u64 page_id, i32 start_index, Vector<ByteString> message_types, Vector<ByteString> messages) =| did_get_js_console_messages(u64 page_id, i32 start_index, Vector<ByteString> message_types, Vector<ByteString> messages) =|
did_finish_text_test(u64 page_id, String text) =| did_finish_text_test(u64 page_id, String text) =|
did_finish_loading_page_and_fonts(u64 page_id, URL::URL url) =|
did_find_in_page(u64 page_id, size_t current_match_index, Optional<size_t> total_match_count) =| did_find_in_page(u64 page_id, size_t current_match_index, Optional<size_t> total_match_count) =|
request_worker_agent(u64 page_id) => (IPC::File socket) // FIXME: Add required attributes to select a SharedWorker Agent request_worker_agent(u64 page_id) => (IPC::File socket) // FIXME: Add required attributes to select a SharedWorker Agent

View file

@ -0,0 +1,63 @@
body { margin: 10px; }
@font-face {
font-family: gsub-test;
src: url(gsubtest-lookup3.otf);
}
td.prop {
font-family: Menlo, monospace;
font-weight: normal;
text-align: left;
font-size: 80%;
}
td.features {
font-family: gsub-test;
}
.invalid {
color: red;
}
@font-feature-values gsub-test {
@styleset { ok-alt-a: 1 3 5; ok-alt-b: 19; }
@character-variant { ok-1: 78 2; }
@character-variant { ok-3: 23; }
@character-variant { not-good: 0 2; }
@annotation { ok-4: 1; }
@annotation { bogus-font-doesnt-support: 23; }
@annotation { circled: 1; }
@character-variant { multi-def: 4; }
@annotation { multi-def: 3; }
@styleset { multi-def2: 3 4 5; }
@styleset { MULTI-def2: 2 6; }
@styleset { out-of-bounds1: 0; out-of-bounds2: 100; }
}
@font-feature-values bogus-family {
@styleset { bogus: 3 4 7; }
}
@font-feature-values GSUB-tEsT {
@styleset { mixed-case: 3 4 7; }
}
@font-feature-values gSuB-tEsT {
@styleset { 3blah: 1 3; }
}
@font-feature-values gSuB-tEsT {
@styleset { moxie: 14; }
@styleset { 3blah: 1; }
}
@font-feature-values gSUB-TeST {
@styleset { moxie2: 14; }
@bongo { blah: 1; }
}
@font-feature-values gSUB-TEst {
@bongo { blah2: 1; }
@styleset { moxie3: 14; }
}

View file

@ -0,0 +1,218 @@
// data associated with gsubtest test font for testing font features
// prefix
gPrefix = "";
// equivalent properties
// setting prop: value should match the specific feature settings listed
//
// each of these tests evaluate whether a given feature is enabled as required
// and also whether features that shouldn't be enabled are or not.
var gPropertyData = [
// font-variant-caps
// valid values
{ prop: "font-variant-caps", value: "normal", features: {"smcp": 0} },
{ prop: "font-variant-caps", value: "small-caps", features: {"smcp": 1, "c2sc": 0} },
{ prop: "font-variant-caps", value: "all-small-caps", features: {"smcp": 1, "c2sc": 1, "pcap": 0} },
{ prop: "font-variant-caps", value: "petite-caps", features: {"pcap": 1, "smcp": 0} },
{ prop: "font-variant-caps", value: "all-petite-caps", features: {"c2pc": 1, "pcap": 1, "smcp": 0} },
{ prop: "font-variant-caps", value: "titling-caps", features: {"titl": 1, "smcp": 0} },
{ prop: "font-variant-caps", value: "unicase", features: {"unic": 1, "titl": 0} },
// invalid values
{ prop: "font-variant-caps", value: "normal small-caps", features: {"smcp": 0}, invalid: true },
{ prop: "font-variant-caps", value: "small-caps potato", features: {"smcp": 0}, invalid: true },
{ prop: "font-variant-caps", value: "small-caps petite-caps", features: {"smcp": 0, "pcap": 0}, invalid: true },
{ prop: "font-variant-caps", value: "small-caps all-small-caps", features: {"smcp": 0, "c2sc": 0}, invalid: true },
{ prop: "font-variant-caps", value: "small-cap", features: {"smcp": 0}, invalid: true },
// font-variant-east-asian
// valid values
{ prop: "font-variant-east-asian", value: "jis78", features: {"jp78": 1, "jp04": 0} },
{ prop: "font-variant-east-asian", value: "jis83", features: {"jp83": 1, "jp04": 0} },
{ prop: "font-variant-east-asian", value: "jis90", features: {"jp90": 1, "jp04": 0} },
{ prop: "font-variant-east-asian", value: "jis04", features: {"jp04": 1, "jp78": 0} },
{ prop: "font-variant-east-asian", value: "simplified", features: {"smpl": 1, "jp04": 0} },
{ prop: "font-variant-east-asian", value: "traditional", features: {"trad": 1, "jp04": 0} },
{ prop: "font-variant-east-asian", value: "full-width", features: {"fwid": 1, "jp04": 0} },
{ prop: "font-variant-east-asian", value: "proportional-width", features: {"pwid": 1, "jp04": 0} },
{ prop: "font-variant-east-asian", value: "ruby", features: {"ruby": 1, "jp04": 0} },
{ prop: "font-variant-east-asian", value: "jis78 full-width", features: {"jp78": 1, "fwid": 1, "jp83": 0} },
{ prop: "font-variant-east-asian", value: "jis78 full-width ruby", features: {"jp78": 1, "fwid": 1, "jp83": 0, "ruby": 1} },
{ prop: "font-variant-east-asian", value: "simplified proportional-width", features: {"smpl": 1, "pwid": 1, "jp83": 0} },
{ prop: "font-variant-east-asian", value: "ruby simplified", features: {"ruby": 1, "smpl": 1, "trad": 0} },
// invalid values
{ prop: "font-variant-east-asian", value: "ruby normal", features: {"ruby": 0}, invalid: true },
{ prop: "font-variant-east-asian", value: "jis90 jis04", features: {"jp90": 0, "jp04": 0}, invalid: true },
{ prop: "font-variant-east-asian", value: "simplified traditional", features: {"smpl": 0, "trad": 0}, invalid: true },
{ prop: "font-variant-east-asian", value: "full-width proportional-width", features: {"fwid": 0, "pwid": 0}, invalid: true },
{ prop: "font-variant-east-asian", value: "ruby simplified ruby", features: {"ruby": 0, "smpl": 0, "jp04": 0}, invalid: true },
{ prop: "font-variant-east-asian", value: "jis78 ruby simplified", features: {"ruby": 0, "smpl": 0, "jp78": 0}, invalid: true },
// font-variant-ligatures
// valid values
{ prop: "font-variant-ligatures", value: "normal", features: {"liga": 1, "dlig": 0} },
{ prop: "font-variant-ligatures", value: "common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} },
{ prop: "font-variant-ligatures", value: "no-common-ligatures", features: {"liga": 0, "clig": 0, "dlig": 0, "hlig": 0, "calt": 1} },
{ prop: "font-variant-ligatures", value: "discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 1, "hlig": 0, "calt": 1} },
{ prop: "font-variant-ligatures", value: "no-discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} },
{ prop: "font-variant-ligatures", value: "historical-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 1, "calt": 1} },
{ prop: "font-variant-ligatures", value: "no-historical-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} },
{ prop: "font-variant-ligatures", value: "contextual", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} },
{ prop: "font-variant-ligatures", value: "no-contextual", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 0} },
{ prop: "font-variant-ligatures", value: "common-ligatures no-discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0, "calt": 1} },
{ prop: "font-variant-ligatures", value: "historical-ligatures no-common-ligatures", features: {"clig": 0, "liga": 0, "dlig": 0, "hlig": 1, "calt": 1} },
{ prop: "font-variant-ligatures", value: "no-historical-ligatures discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 1, "hlig": 0, "calt": 1} },
{ prop: "font-variant-ligatures", value: "common-ligatures no-discretionary-ligatures historical-ligatures no-contextual", features: {"clig": 1, "dlig": 0, "hlig": 1, "liga": 1, "calt": 0} },
// invalid values
{ prop: "font-variant-ligatures", value: "common-ligatures normal", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true },
{ prop: "font-variant-ligatures", value: "common-ligatures no-common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true },
{ prop: "font-variant-ligatures", value: "common-ligatures common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true },
{ prop: "font-variant-ligatures", value: "no-historical-ligatures historical-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0}, invalid: true },
{ prop: "font-variant-ligatures", value: "no-contextual contextual", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0}, invalid: true },
{ prop: "font-variant-ligatures", value: "no-discretionary-ligatures discretionary-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true },
{ prop: "font-variant-ligatures", value: "common-ligatures no-discretionary-ligatures no-common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0}, invalid: true },
// font-variant-numeric
// valid values
{ prop: "font-variant-numeric", value: "normal", features: {"lnum": 0, "tnum": 0, "pnum": 0, "onum": 0} },
{ prop: "font-variant-numeric", value: "lining-nums", features: {"lnum": 1, "onum": 0, "pnum": 0} },
{ prop: "font-variant-numeric", value: "oldstyle-nums", features: {"lnum": 0, "onum": 1, "pnum": 0} },
{ prop: "font-variant-numeric", value: "proportional-nums", features: {"lnum": 0, "onum": 0, "pnum": 1, "tnum": 0} },
{ prop: "font-variant-numeric", value: "proportional-nums oldstyle-nums", features: {"lnum": 0, "onum": 1, "pnum": 1, "tnum": 0} },
{ prop: "font-variant-numeric", value: "tabular-nums", features: {"tnum": 1, "onum": 0, "pnum": 0} },
{ prop: "font-variant-numeric", value: "diagonal-fractions", features: {"frac": 1, "afrc": 0, "pnum": 0} },
{ prop: "font-variant-numeric", value: "stacked-fractions", features: {"frac": 0, "afrc": 1, "pnum": 0} },
{ prop: "font-variant-numeric", value: "slashed-zero", features: {"zero": 1, "pnum": 0} },
{ prop: "font-variant-numeric", value: "ordinal", features: {"ordn": 1, "pnum": 0} },
{ prop: "font-variant-numeric", value: "lining-nums diagonal-fractions", features: {"frac": 1, "afrc": 0, "lnum": 1} },
{ prop: "font-variant-numeric", value: "tabular-nums stacked-fractions", features: {"frac": 0, "afrc": 1, "tnum": 1} },
{ prop: "font-variant-numeric", value: "tabular-nums slashed-zero stacked-fractions", features: {"frac": 0, "afrc": 1, "tnum": 1, "zero": 1} },
{ prop: "font-variant-numeric", value: "proportional-nums slashed-zero diagonal-fractions oldstyle-nums ordinal", features: {"frac": 1, "afrc": 0, "tnum": 0, "pnum": 1, "onum": 1, "ordn": 1, "zero": 1} },
// invalid values
{ prop: "font-variant-numeric", value: "lining-nums normal", features: {"lnum": 0, "onum": 0}, invalid: true },
{ prop: "font-variant-numeric", value: "lining-nums oldstyle-nums", features: {"lnum": 0, "onum": 0}, invalid: true },
{ prop: "font-variant-numeric", value: "lining-nums normal slashed-zero ordinal", features: {"lnum": 0, "onum": 0, "zero": 0}, invalid: true },
{ prop: "font-variant-numeric", value: "proportional-nums tabular-nums", features: {"pnum": 0, "tnum": 0}, invalid: true },
{ prop: "font-variant-numeric", value: "diagonal-fractions stacked-fractions", features: {"frac": 0, "afrc": 0}, invalid: true },
{ prop: "font-variant-numeric", value: "slashed-zero diagonal-fractions slashed-zero", features: {"frac": 0, "afrc": 0, "zero": 0}, invalid: true },
{ prop: "font-variant-numeric", value: "lining-nums slashed-zero diagonal-fractions oldstyle-nums", features: {"frac": 0, "afrc": 0, "zero": 0, "onum": 0}, invalid: true },
// font-variant-position
// valid values
{ prop: "font-variant-position", value: "normal", features: {"subs": 0, "sups": 0} },
{ prop: "font-variant-position", value: "super", features: {"subs": 0, "sups": 1} },
{ prop: "font-variant-position", value: "sub", features: {"subs": 1, "sups": 0} },
// invalid values
{ prop: "font-variant-position", value: "super sub", features: {"subs": 0, "sups": 0}, invalid: true },
];
// note: the code below requires an array "gFeatures" from :
// support/fonts/gsubtest-features.js
// The font defines feature lookups for all OpenType features for a
// specific set of PUA codepoints, as listed in the gFeatures array.
// Using these codepoints and feature combinations, tests can be
// constructed to detect when certain features are enabled or not.
// return a created table containing tests for a given property
//
// Ex: { prop: "font-variant-ligatures", value: "common-ligatures", features: {"liga": 1, "clig": 1, "dlig": 0, "hlig": 0} }
//
// This means that for the property 'font-variant-ligatures' with the value 'common-ligatures', the features listed should
// either be explicitly enabled or disabled.
// propData is the prop/value list with corresponding feature assertions
// whichProp is either "all" or a specific subproperty (i.e. "font-variant-position")
// isRef is true when this is the reference
// debug outputs the prop/value pair along with the tests
function createFeatureTestTable(propData, whichProp, isRef, debug)
{
var table = document.createElement("table");
if (typeof(isRef) == "undefined") {
isRef = false;
}
if (typeof(debug) == "undefined") {
debug = false;
}
var doAll = (whichProp == "all");
for (var i in propData) {
var data = propData[i];
if (!doAll && data.prop != whichProp) continue;
var row = document.createElement("tr");
var invalid = false;
if ("invalid" in data) {
invalid = true;
row.className = "invalid";
}
var cell = document.createElement("td");
cell.className = "prop";
var styledecl = gPrefix + data.prop + ": " + data.value + ";";
cell.innerHTML = styledecl;
row.appendChild(cell);
if (debug) {
table.appendChild(row);
}
row = document.createElement("tr");
if (invalid) {
row.className = "invalid";
}
cell = document.createElement("td");
cell.className = "features";
if (!isRef) {
cell.style.cssText = styledecl;
}
for (var f in data.features) {
var feature = data.features[f];
var cp, unsupported = "F".charCodeAt(0);
var basecp = gFeatures[f];
if (typeof(basecp) == "undefined") {
cp = unsupported;
} else {
switch(feature) {
case 0:
cp = basecp;
break;
case 1:
cp = basecp + 1;
break;
case 2:
cp = basecp + 2;
break;
case 3:
cp = basecp + 3;
break;
default:
cp = basecp + 1;
break;
}
}
var span = document.createElement("span");
span.innerHTML = (isRef ? "P " : "&#x" + cp.toString(16) + "; ");
span.title = f + "=" + feature;
cell.appendChild(span);
}
row.appendChild(cell);
table.appendChild(row);
}
return table;
}

View file

@ -0,0 +1,85 @@
/* This file is autogenerated by makegsubfonts.py */
/*
Features defined in gsubtest fonts with associated base
codepoints for each feature:
cp = codepoint for feature featX
cp default PASS
cp featX=1 FAIL
cp featX=2 FAIL
cp+1 default FAIL
cp+1 featX=1 PASS
cp+1 featX=2 FAIL
cp+2 default FAIL
cp+2 featX=1 FAIL
cp+2 featX=2 PASS
*/
var gFeatures = {
"MWL1": 0xe000, "NUM2": 0xe004, "PRIV": 0xe008, "T3ST": 0xe00c,
"TPSP": 0xe010, "abvf": 0xe014, "abvm": 0xe018, "abvs": 0xe01c,
"afrc": 0xe020, "akhn": 0xe024, "blwf": 0xe028, "blwm": 0xe02c,
"blws": 0xe030, "c2pc": 0xe034, "c2sc": 0xe038, "calt": 0xe03c,
"case": 0xe040, "ccmp": 0xe044, "cfar": 0xe048, "cjct": 0xe04c,
"clig": 0xe050, "cpct": 0xe054, "cpsp": 0xe058, "cswh": 0xe05c,
"curs": 0xe060, "cv00": 0xe064, "cv01": 0xe068, "cv02": 0xe06c,
"cv03": 0xe070, "cv04": 0xe074, "cv05": 0xe078, "cv06": 0xe07c,
"cv07": 0xe080, "cv08": 0xe084, "cv09": 0xe088, "cv10": 0xe08c,
"cv11": 0xe090, "cv12": 0xe094, "cv13": 0xe098, "cv14": 0xe09c,
"cv15": 0xe0a0, "cv16": 0xe0a4, "cv17": 0xe0a8, "cv18": 0xe0ac,
"cv19": 0xe0b0, "cv20": 0xe0b4, "cv21": 0xe0b8, "cv22": 0xe0bc,
"cv23": 0xe0c0, "cv24": 0xe0c4, "cv25": 0xe0c8, "cv26": 0xe0cc,
"cv27": 0xe0d0, "cv28": 0xe0d4, "cv29": 0xe0d8, "cv30": 0xe0dc,
"cv31": 0xe0e0, "cv32": 0xe0e4, "cv33": 0xe0e8, "cv34": 0xe0ec,
"cv35": 0xe0f0, "cv36": 0xe0f4, "cv37": 0xe0f8, "cv38": 0xe0fc,
"cv39": 0xe100, "cv40": 0xe104, "cv41": 0xe108, "cv42": 0xe10c,
"cv43": 0xe110, "cv44": 0xe114, "cv45": 0xe118, "cv46": 0xe11c,
"cv47": 0xe120, "cv48": 0xe124, "cv49": 0xe128, "cv50": 0xe12c,
"cv51": 0xe130, "cv52": 0xe134, "cv53": 0xe138, "cv54": 0xe13c,
"cv55": 0xe140, "cv56": 0xe144, "cv57": 0xe148, "cv58": 0xe14c,
"cv59": 0xe150, "cv60": 0xe154, "cv61": 0xe158, "cv62": 0xe15c,
"cv63": 0xe160, "cv64": 0xe164, "cv65": 0xe168, "cv66": 0xe16c,
"cv67": 0xe170, "cv68": 0xe174, "cv69": 0xe178, "cv70": 0xe17c,
"cv71": 0xe180, "cv72": 0xe184, "cv73": 0xe188, "cv74": 0xe18c,
"cv75": 0xe190, "cv76": 0xe194, "cv77": 0xe198, "cv78": 0xe19c,
"cv79": 0xe1a0, "cv80": 0xe1a4, "cv81": 0xe1a8, "cv82": 0xe1ac,
"cv83": 0xe1b0, "cv84": 0xe1b4, "cv85": 0xe1b8, "cv86": 0xe1bc,
"cv87": 0xe1c0, "cv88": 0xe1c4, "cv89": 0xe1c8, "cv90": 0xe1cc,
"cv91": 0xe1d0, "cv92": 0xe1d4, "cv93": 0xe1d8, "cv94": 0xe1dc,
"cv95": 0xe1e0, "cv96": 0xe1e4, "cv97": 0xe1e8, "cv98": 0xe1ec,
"cv99": 0xe1f0, "dist": 0xe1f4, "dlig": 0xe1f8, "dnom": 0xe1fc,
"expt": 0xe200, "falt": 0xe204, "fin2": 0xe208, "fin3": 0xe20c,
"fina": 0xe210, "frac": 0xe214, "fwid": 0xe218, "half": 0xe21c,
"haln": 0xe220, "halt": 0xe224, "hist": 0xe228, "hkna": 0xe22c,
"hlig": 0xe230, "hngl": 0xe234, "hojo": 0xe238, "hwid": 0xe23c,
"init": 0xe240, "isol": 0xe244, "ital": 0xe248, "jalt": 0xe24c,
"jp04": 0xe250, "jp78": 0xe254, "jp83": 0xe258, "jp90": 0xe25c,
"kern": 0xe260, "lfbd": 0xe264, "liga": 0xe268, "ljmo": 0xe26c,
"lnum": 0xe270, "locl": 0xe274, "ltra": 0xe278, "ltrm": 0xe27c,
"mark": 0xe280, "med2": 0xe284, "medi": 0xe288, "mgrk": 0xe28c,
"mkmk": 0xe290, "mset": 0xe294, "nalt": 0xe298, "nlck": 0xe29c,
"nukt": 0xe2a0, "numr": 0xe2a4, "onum": 0xe2a8, "opbd": 0xe2ac,
"ordn": 0xe2b0, "ornm": 0xe2b4, "palt": 0xe2b8, "pcap": 0xe2bc,
"pkna": 0xe2c0, "pnum": 0xe2c4, "pref": 0xe2c8, "pres": 0xe2cc,
"pstf": 0xe2d0, "psts": 0xe2d4, "pwid": 0xe2d8, "qwid": 0xe2dc,
"rand": 0xe2e0, "rkrf": 0xe2e4, "rlig": 0xe2e8, "rphf": 0xe2ec,
"rtbd": 0xe2f0, "rtla": 0xe2f4, "rtlm": 0xe2f8, "ruby": 0xe2fc,
"salt": 0xe300, "sinf": 0xe304, "size": 0xe308, "smcp": 0xe30c,
"smpl": 0xe310, "ss00": 0xe314, "ss01": 0xe318, "ss02": 0xe31c,
"ss03": 0xe320, "ss04": 0xe324, "ss05": 0xe328, "ss06": 0xe32c,
"ss07": 0xe330, "ss08": 0xe334, "ss09": 0xe338, "ss10": 0xe33c,
"ss11": 0xe340, "ss12": 0xe344, "ss13": 0xe348, "ss14": 0xe34c,
"ss15": 0xe350, "ss16": 0xe354, "ss17": 0xe358, "ss18": 0xe35c,
"ss19": 0xe360, "ss20": 0xe364, "ss21": 0xe368, "subs": 0xe36c,
"sups": 0xe370, "swsh": 0xe374, "titl": 0xe378, "tjmo": 0xe37c,
"tnam": 0xe380, "tnum": 0xe384, "trad": 0xe388, "twid": 0xe38c,
"unic": 0xe390, "valt": 0xe394, "vatu": 0xe398, "vert": 0xe39c,
"vhal": 0xe3a0, "vjmo": 0xe3a4, "vkna": 0xe3a8, "vkrn": 0xe3ac,
"vpal": 0xe3b0, "vrt2": 0xe3b4, "zero": 0xe3b8
};

Binary file not shown.

View file

@ -0,0 +1,21 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>CSS Test: feature value matching for font-variant-caps</title>
<link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="../../../../data/gsubtest-features.js"></script>
<script type="text/javascript" src="../../../../data/font-variant-features.js"></script>
<link rel="stylesheet" href="../../../../data/font-variant-features.css" type="text/css"/>
</head>
<body>
<div id="content"></div>
<script type="text/javascript">
document.fonts.ready.then(() => {
internals.finishLoading();
})
internals.disableAutomaticPageFinish();
document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-caps", true, false));
</script>
</body>
</html>

View file

@ -0,0 +1,21 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>CSS Test: feature value matching for font-variant-east-asian</title>
<link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="../../../../data/gsubtest-features.js"></script>
<script type="text/javascript" src="../../../../data/font-variant-features.js"></script>
<link rel="stylesheet" href="../../../../data/font-variant-features.css" type="text/css"/>
</head>
<body>
<div id="content"></div>
<script type="text/javascript">
document.fonts.ready.then(() => {
internals.finishLoading();
})
internals.disableAutomaticPageFinish();
document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-east-asian", true, false));
</script>
</body>
</html>

View file

@ -0,0 +1,21 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>CSS Test: feature value matching for font-variant-ligatures</title>
<link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="../../../../data/gsubtest-features.js"></script>
<script type="text/javascript" src="../../../../data/font-variant-features.js"></script>
<link rel="stylesheet" href="../../../../data/font-variant-features.css" type="text/css"/>
</head>
<body>
<div id="content"></div>
<script type="text/javascript">
document.fonts.ready.then(() => {
internals.finishLoading();
})
internals.disableAutomaticPageFinish();
document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-ligatures", true, false));
</script>
</body>
</html>

View file

@ -0,0 +1,24 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>CSS Test: feature value matching for font-variant-numeric</title>
<link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="../../../../data/gsubtest-features.js"></script>
<script type="text/javascript" src="../../../../data/font-variant-features.js"></script>
<link rel="stylesheet" href="../../../../data/font-variant-features.css" type="text/css"/>
</head>
<body>
<div id="content"></div>
<script type="text/javascript">
window.onload = (event) => {
console.log('expectation onload', event.target.location.href);
}
document.fonts.ready.then(() => {
console.log('exceptation document.fonts.ready');
})
// internals.disableAutomaticPageFinish();
document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-numeric", true, false));
</script>
</body>
</html>

View file

@ -0,0 +1,21 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>CSS Test: feature value matching for font-variant-position</title>
<link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="../../../../data/gsubtest-features.js"></script>
<script type="text/javascript" src="../../../../data/font-variant-features.js"></script>
<link rel="stylesheet" href="../../../../data/font-variant-features.css" type="text/css"/>
</head>
<body>
<div id="content"></div>
<script type="text/javascript">
document.fonts.ready.then(() => {
internals.finishLoading();
})
internals.disableAutomaticPageFinish();
document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-position", true, false));
</script>
</body>
</html>

View file

@ -0,0 +1,24 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>CSS Test: feature value matching for font-variant-caps</title>
<link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"/>
<link rel="help" href="http://www.w3.org/TR/css-fonts-3/#font-variant-caps-prop"/>
<link rel="match" href="../../../../expected/wpt-import/css/css-fonts/font-variant-caps-ref.html"/>
<meta name="assert" content="Values of font-variant-caps should enable specific features without enabling others"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="../../../../data/gsubtest-features.js"></script>
<script type="text/javascript" src="../../../../data/font-variant-features.js"></script>
<link rel="stylesheet" href="../../../../data/font-variant-features.css" type="text/css"/>
</head>
<body>
<div id="content"></div>
<script type="text/javascript">
document.fonts.ready.then(() => {
internals.finishLoading();
})
internals.disableAutomaticPageFinish();
document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-caps", false, false));
</script>
</body>
</html>

View file

@ -0,0 +1,24 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>CSS Test: feature value matching for font-variant-east-asian</title>
<link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"/>
<link rel="help" href="http://www.w3.org/TR/css-fonts-3/#font-variant-east-asian-prop"/>
<link rel="match" href="../../../../expected/wpt-import/css/css-fonts/font-variant-east-asian-ref.html"/>
<meta name="assert" content="Values of font-variant-east-asian should enable specific features without enabling others"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="../../../../data/gsubtest-features.js"></script>
<script type="text/javascript" src="../../../../data/font-variant-features.js"></script>
<link rel="stylesheet" href="../../../../data/font-variant-features.css" type="text/css"/>
</head>
<body>
<div id="content"></div>
<script type="text/javascript">
document.fonts.ready.then(() => {
internals.finishLoading();
})
internals.disableAutomaticPageFinish();
document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-east-asian", false, false));
</script>
</body>
</html>

View file

@ -0,0 +1,24 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>CSS Test: feature value matching for font-variant-ligatures</title>
<link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"/>
<link rel="help" href="http://www.w3.org/TR/css-fonts-3/#font-variant-ligatures-prop"/>
<link rel="match" href="../../../../expected/wpt-import/css/css-fonts/font-variant-ligatures-ref.html"/>
<meta name="assert" content="Values of font-variant-ligatures should enable specific features without enabling others"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="../../../../data/gsubtest-features.js"></script>
<script type="text/javascript" src="../../../../data/font-variant-features.js"></script>
<link rel="stylesheet" href="../../../../data/font-variant-features.css" type="text/css"/>
</head>
<body>
<div id="content"></div>
<script type="text/javascript">
document.fonts.ready.then(() => {
internals.finishLoading();
})
internals.disableAutomaticPageFinish();
document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-ligatures", false, false));
</script>
</body>
</html>

View file

@ -0,0 +1,26 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>CSS Test: feature value matching for font-variant-numeric</title>
<link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"/>
<link rel="help" href="http://www.w3.org/TR/css-fonts-3/#font-variant-numeric-prop"/>
<link rel="match" href="../../../../expected/wpt-import/css/css-fonts/font-variant-numeric-ref.html"/>
<meta name="assert" content="Values of font-variant-numeric should enable specific features without enabling others"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="../../../../data/gsubtest-features.js"></script>
<script type="text/javascript" src="../../../../data/font-variant-features.js"></script>
<link rel="stylesheet" href="../../../../data/font-variant-features.css" type="text/css"/>
</head>
<body>
<div id="content"></div>
<script type="text/javascript">
window.onload = (event) => {
console.log('actual onload', event.target.location.href);
}
document.fonts.ready.then(() => {
console.log('actual document.fonts.ready');
})
document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-numeric", false, false));
</script>
</body>
</html>

View file

@ -0,0 +1,24 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>CSS Test: feature value matching for font-variant-position</title>
<link rel="author" title="John Daggett" href="mailto:jdaggett@mozilla.com"/>
<link rel="help" href="http://www.w3.org/TR/css-fonts-3/#font-variant-position-prop"/>
<link rel="match" href="../../../../expected/wpt-import/css/css-fonts/font-variant-position-ref.html"/>
<meta name="assert" content="Values of font-variant-position should enable specific features without enabling others"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="../../../../data/gsubtest-features.js"></script>
<script type="text/javascript" src="../../../../data/font-variant-features.js"></script>
<link rel="stylesheet" href="../../../../data/font-variant-features.css" type="text/css"/>
</head>
<body>
<div id="content"></div>
<script type="text/javascript">
document.fonts.ready.then(() => {
internals.finishLoading();
})
internals.disableAutomaticPageFinish();
document.getElementById("content").appendChild(createFeatureTestTable(gPropertyData, "font-variant-position", false, false));
</script>
</body>
</html>

View file

@ -1,6 +1,6 @@
All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle: All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle:
'cssText': '' 'cssText': ''
'length': '203' 'length': '210'
'parentRule': 'null' 'parentRule': 'null'
'cssFloat': 'none' 'cssFloat': 'none'
'WebkitAlignContent': 'normal' 'WebkitAlignContent': 'normal'
@ -305,6 +305,20 @@ All supported properties and their default values exposed from CSSStyleDeclarati
'font-style': 'normal' 'font-style': 'normal'
'fontVariant': 'normal' 'fontVariant': 'normal'
'font-variant': 'normal' 'font-variant': 'normal'
'fontVariantAlternates': 'normal'
'font-variant-alternates': 'normal'
'fontVariantCaps': 'normal'
'font-variant-caps': 'normal'
'fontVariantEastAsian': 'normal'
'font-variant-east-asian': 'normal'
'fontVariantEmoji': 'normal'
'font-variant-emoji': 'normal'
'fontVariantLigatures': 'normal'
'font-variant-ligatures': 'normal'
'fontVariantNumeric': 'normal'
'font-variant-numeric': 'normal'
'fontVariantPosition': 'normal'
'font-variant-position': 'normal'
'fontVariationSettings': 'normal' 'fontVariationSettings': 'normal'
'font-variation-settings': 'normal' 'font-variation-settings': 'normal'
'fontWeight': '400' 'fontWeight': '400'

View file

@ -18,191 +18,197 @@ All properties associated with getComputedStyle(document.body):
"15": "font-size", "15": "font-size",
"16": "font-style", "16": "font-style",
"17": "font-variant", "17": "font-variant",
"18": "font-variation-settings", "18": "font-variant-alternates",
"19": "font-weight", "19": "font-variant-caps",
"20": "font-width", "20": "font-variant-east-asian",
"21": "image-rendering", "21": "font-variant-emoji",
"22": "letter-spacing", "22": "font-variant-ligatures",
"23": "line-height", "23": "font-variant-numeric",
"24": "list-style-image", "24": "font-variant-position",
"25": "list-style-position", "25": "font-variation-settings",
"26": "list-style-type", "26": "font-weight",
"27": "math-depth", "27": "font-width",
"28": "math-shift", "28": "image-rendering",
"29": "math-style", "29": "letter-spacing",
"30": "pointer-events", "30": "line-height",
"31": "quotes", "31": "list-style-image",
"32": "stroke", "32": "list-style-position",
"33": "stroke-dashoffset", "33": "list-style-type",
"34": "stroke-linecap", "34": "math-depth",
"35": "stroke-linejoin", "35": "math-shift",
"36": "stroke-miterlimit", "36": "math-style",
"37": "stroke-opacity", "37": "pointer-events",
"38": "stroke-width", "38": "quotes",
"39": "tab-size", "39": "stroke",
"40": "text-align", "40": "stroke-linecap",
"41": "text-anchor", "41": "stroke-linejoin",
"42": "text-decoration-line", "42": "stroke-miterlimit",
"43": "text-indent", "43": "stroke-opacity",
"44": "text-justify", "44": "stroke-width",
"45": "text-shadow", "45": "tab-size",
"46": "text-transform", "46": "text-align",
"47": "visibility", "47": "text-anchor",
"48": "white-space", "48": "text-decoration-line",
"49": "word-break", "49": "text-indent",
"50": "word-spacing", "50": "text-justify",
"51": "word-wrap", "51": "text-shadow",
"52": "writing-mode", "52": "text-transform",
"53": "align-content", "53": "visibility",
"54": "align-items", "54": "white-space",
"55": "align-self", "55": "word-break",
"56": "animation-delay", "56": "word-spacing",
"57": "animation-direction", "57": "word-wrap",
"58": "animation-duration", "58": "writing-mode",
"59": "animation-fill-mode", "59": "align-content",
"60": "animation-iteration-count", "60": "align-items",
"61": "animation-name", "61": "align-self",
"62": "animation-play-state", "62": "animation-delay",
"63": "animation-timing-function", "63": "animation-direction",
"64": "appearance", "64": "animation-duration",
"65": "aspect-ratio", "65": "animation-fill-mode",
"66": "backdrop-filter", "66": "animation-iteration-count",
"67": "background-attachment", "67": "animation-name",
"68": "background-clip", "68": "animation-play-state",
"69": "background-color", "69": "animation-timing-function",
"70": "background-image", "70": "appearance",
"71": "background-origin", "71": "aspect-ratio",
"72": "background-position-x", "72": "backdrop-filter",
"73": "background-position-y", "73": "background-attachment",
"74": "background-repeat", "74": "background-clip",
"75": "background-size", "75": "background-color",
"76": "border-bottom-color", "76": "background-image",
"77": "border-bottom-left-radius", "77": "background-origin",
"78": "border-bottom-right-radius", "78": "background-position-x",
"79": "border-bottom-style", "79": "background-position-y",
"80": "border-bottom-width", "80": "background-repeat",
"81": "border-left-color", "81": "background-size",
"82": "border-left-style", "82": "border-bottom-color",
"83": "border-left-width", "83": "border-bottom-left-radius",
"84": "border-right-color", "84": "border-bottom-right-radius",
"85": "border-right-style", "85": "border-bottom-style",
"86": "border-right-width", "86": "border-bottom-width",
"87": "border-top-color", "87": "border-left-color",
"88": "border-top-left-radius", "88": "border-left-style",
"89": "border-top-right-radius", "89": "border-left-width",
"90": "border-top-style", "90": "border-right-color",
"91": "border-top-width", "91": "border-right-style",
"92": "bottom", "92": "border-right-width",
"93": "box-shadow", "93": "border-top-color",
"94": "box-sizing", "94": "border-top-left-radius",
"95": "clear", "95": "border-top-right-radius",
"96": "clip", "96": "border-top-style",
"97": "clip-path", "97": "border-top-width",
"98": "column-count", "98": "bottom",
"99": "column-gap", "99": "box-shadow",
"100": "column-span", "100": "box-sizing",
"101": "column-width", "101": "clear",
"102": "content", "102": "clip",
"103": "content-visibility", "103": "clip-path",
"104": "counter-increment", "104": "column-count",
"105": "counter-reset", "105": "column-gap",
"106": "counter-set", "106": "column-span",
"107": "cx", "107": "column-width",
"108": "cy", "108": "content",
"109": "display", "109": "content-visibility",
"110": "filter", "110": "counter-increment",
"111": "flex-basis", "111": "counter-reset",
"112": "flex-direction", "112": "counter-set",
"113": "flex-grow", "113": "cx",
"114": "flex-shrink", "114": "cy",
"115": "flex-wrap", "115": "display",
"116": "float", "116": "filter",
"117": "grid-auto-columns", "117": "flex-basis",
"118": "grid-auto-flow", "118": "flex-direction",
"119": "grid-auto-rows", "119": "flex-grow",
"120": "grid-column-end", "120": "flex-shrink",
"121": "grid-column-start", "121": "flex-wrap",
"122": "grid-row-end", "122": "float",
"123": "grid-row-start", "123": "grid-auto-columns",
"124": "grid-template-areas", "124": "grid-auto-flow",
"125": "grid-template-columns", "125": "grid-auto-rows",
"126": "grid-template-rows", "126": "grid-column-end",
"127": "height", "127": "grid-column-start",
"128": "inline-size", "128": "grid-row-end",
"129": "inset-block-end", "129": "grid-row-start",
"130": "inset-block-start", "130": "grid-template-areas",
"131": "inset-inline-end", "131": "grid-template-columns",
"132": "inset-inline-start", "132": "grid-template-rows",
"133": "justify-content", "133": "height",
"134": "justify-items", "134": "inline-size",
"135": "justify-self", "135": "inset-block-end",
"136": "left", "136": "inset-block-start",
"137": "margin-block-end", "137": "inset-inline-end",
"138": "margin-block-start", "138": "inset-inline-start",
"139": "margin-bottom", "139": "justify-content",
"140": "margin-inline-end", "140": "justify-items",
"141": "margin-inline-start", "141": "justify-self",
"142": "margin-left", "142": "left",
"143": "margin-right", "143": "margin-block-end",
"144": "margin-top", "144": "margin-block-start",
"145": "mask", "145": "margin-bottom",
"146": "mask-image", "146": "margin-inline-end",
"147": "mask-type", "147": "margin-inline-start",
"148": "max-height", "148": "margin-left",
"149": "max-inline-size", "149": "margin-right",
"150": "max-width", "150": "margin-top",
"151": "min-height", "151": "mask",
"152": "min-inline-size", "152": "mask-image",
"153": "min-width", "153": "mask-type",
"154": "object-fit", "154": "max-height",
"155": "object-position", "155": "max-inline-size",
"156": "opacity", "156": "max-width",
"157": "order", "157": "min-height",
"158": "outline-color", "158": "min-inline-size",
"159": "outline-offset", "159": "min-width",
"160": "outline-style", "160": "object-fit",
"161": "outline-width", "161": "object-position",
"162": "overflow-x", "162": "opacity",
"163": "overflow-y", "163": "order",
"164": "padding-block-end", "164": "outline-color",
"165": "padding-block-start", "165": "outline-offset",
"166": "padding-bottom", "166": "outline-style",
"167": "padding-inline-end", "167": "outline-width",
"168": "padding-inline-start", "168": "overflow-x",
"169": "padding-left", "169": "overflow-y",
"170": "padding-right", "170": "padding-block-end",
"171": "padding-top", "171": "padding-block-start",
"172": "position", "172": "padding-bottom",
"173": "r", "173": "padding-inline-end",
"174": "right", "174": "padding-inline-start",
"175": "rotate", "175": "padding-left",
"176": "row-gap", "176": "padding-right",
"177": "rx", "177": "padding-top",
"178": "ry", "178": "position",
"179": "scrollbar-gutter", "179": "r",
"180": "scrollbar-width", "180": "right",
"181": "stop-color", "181": "rotate",
"182": "stop-opacity", "182": "row-gap",
"183": "table-layout", "183": "rx",
"184": "text-decoration-color", "184": "ry",
"185": "text-decoration-style", "185": "scrollbar-gutter",
"186": "text-decoration-thickness", "186": "scrollbar-width",
"187": "text-overflow", "187": "stop-color",
"188": "top", "188": "stop-opacity",
"189": "transform", "189": "table-layout",
"190": "transform-box", "190": "text-decoration-color",
"191": "transform-origin", "191": "text-decoration-style",
"192": "transition-delay", "192": "text-decoration-thickness",
"193": "transition-duration", "193": "text-overflow",
"194": "transition-property", "194": "top",
"195": "transition-timing-function", "195": "transform",
"196": "unicode-bidi", "196": "transform-box",
"197": "user-select", "197": "transform-origin",
"198": "vertical-align", "198": "transition-delay",
"199": "width", "199": "transition-duration",
"200": "x", "200": "transition-property",
"201": "y", "201": "transition-timing-function",
"202": "z-index" "202": "unicode-bidi",
"203": "user-select",
"204": "vertical-align",
"205": "width",
"206": "x",
"207": "y",
"208": "z-index"
} }
All properties associated with document.body.style by default: All properties associated with document.body.style by default:
{} {}

View file

@ -16,6 +16,13 @@ font-language-override: normal
font-size: 16px font-size: 16px
font-style: normal font-style: normal
font-variant: normal font-variant: normal
font-variant-alternates: normal
font-variant-caps: normal
font-variant-east-asian: normal
font-variant-emoji: normal
font-variant-ligatures: normal
font-variant-numeric: normal
font-variant-position: normal
font-variation-settings: normal font-variation-settings: normal
font-weight: 400 font-weight: 400
font-width: normal font-width: normal
@ -125,7 +132,7 @@ grid-row-start: auto
grid-template-areas: none grid-template-areas: none
grid-template-columns: auto grid-template-columns: auto
grid-template-rows: auto grid-template-rows: auto
height: 2159px height: 2261px
inline-size: auto inline-size: auto
inset-block-end: auto inset-block-end: auto
inset-block-start: auto inset-block-start: auto

View file

@ -23,6 +23,7 @@
#include <LibGfx/ImageFormats/PNGWriter.h> #include <LibGfx/ImageFormats/PNGWriter.h>
#include <LibURL/URL.h> #include <LibURL/URL.h>
#include <LibWeb/HTML/SelectedFile.h> #include <LibWeb/HTML/SelectedFile.h>
#include <LibWeb/HTML/Window.h>
#include <UI/Headless/Application.h> #include <UI/Headless/Application.h>
#include <UI/Headless/HeadlessWebView.h> #include <UI/Headless/HeadlessWebView.h>
#include <UI/Headless/Test.h> #include <UI/Headless/Test.h>
@ -101,6 +102,7 @@ static ErrorOr<void> collect_ref_tests(Vector<Test>& tests, StringView path, Str
static void clear_test_callbacks(HeadlessWebView& view) static void clear_test_callbacks(HeadlessWebView& view)
{ {
view.on_load_finish = {}; view.on_load_finish = {};
view.on_loading_page_and_fonts_finish = {};
view.on_text_test_finish = {}; view.on_text_test_finish = {};
view.on_web_content_crashed = {}; view.on_web_content_crashed = {};
} }
@ -239,6 +241,7 @@ static void run_ref_test(HeadlessWebView& view, Test& test, URL::URL const& url,
{ {
auto timer = Core::Timer::create_single_shot(timeout_in_milliseconds, [&view, &test]() { auto timer = Core::Timer::create_single_shot(timeout_in_milliseconds, [&view, &test]() {
view.on_load_finish = {}; view.on_load_finish = {};
view.on_loading_page_and_fonts_finish = {};
view.on_text_test_finish = {}; view.on_text_test_finish = {};
view.on_test_complete({ test, TestResult::Timeout }); view.on_test_complete({ test, TestResult::Timeout });
@ -287,20 +290,63 @@ static void run_ref_test(HeadlessWebView& view, Test& test, URL::URL const& url,
view.on_test_complete({ test, TestResult::Crashed }); view.on_test_complete({ test, TestResult::Crashed });
}; };
view.on_load_finish = [&view, &test, on_test_complete = move(on_test_complete)](auto const&) { view.on_load_finish = [&view, &test, &on_test_complete](auto const&) {
dbgln("on loading page: page={}, url={}", test.did_load_page, view.url().serialize_path());
test.did_load_page = true;
if (!test.did_load_fonts)
return;
//dbgln("DONE on loading page: fonts={}, {}", test.did_load_fonts, view.url());
test.did_load_page = true;
if (test.actual_screenshot) { if (test.actual_screenshot) {
view.take_screenshot()->when_resolved([&test, on_test_complete = move(on_test_complete)](RefPtr<Gfx::Bitmap> screenshot) { //dbgln("Take expectation screenshot <- this happens too early");
view.take_screenshot()->when_resolved([&test, &on_test_complete](RefPtr<Gfx::Bitmap> screenshot) {
test.expectation_screenshot = move(screenshot); test.expectation_screenshot = move(screenshot);
test.did_load_fonts = false;
test.did_load_page = false;
on_test_complete(); on_test_complete();
}); });
} else { } else {
//dbgln("\nTake actual screenshot");
view.take_screenshot()->when_resolved([&view, &test](RefPtr<Gfx::Bitmap> screenshot) { view.take_screenshot()->when_resolved([&view, &test](RefPtr<Gfx::Bitmap> screenshot) {
test.actual_screenshot = move(screenshot); test.actual_screenshot = move(screenshot);
test.did_load_fonts = false;
test.did_load_page = false;
test.loading_reference_page = true;
dbgln("load-reference-page");
view.debug_request("load-reference-page"); view.debug_request("load-reference-page");
}); });
} }
}; };
// FIXME: rename to on_fonts_ready
view.on_loading_page_and_fonts_finish = [&view, &test, &on_test_complete](auto const&) {
dbgln("on fonts finish: load={}, url={}", test.did_load_fonts, view.url().serialize_path());
// This callback will be called multiple times, one for normal page load and one for the reference page load.
// We only want to take the screenshot once the reference page has loaded its fonts
if (test.loading_reference_page && !view.url().serialize_path().ends_with_bytes("-ref.html"sv))
return;
if (test.did_load_fonts)
return;
test.did_load_fonts = true;
if (!test.did_load_page)
return;
if (test.actual_screenshot) {
view.take_screenshot()->when_resolved([&test, &on_test_complete](RefPtr<Gfx::Bitmap> screenshot) {
test.expectation_screenshot = move(screenshot);
on_test_complete();
});
} else {
dbgln("\nTake actual screenshot");
view.take_screenshot()->when_resolved([&view, &test](RefPtr<Gfx::Bitmap> screenshot) {
test.actual_screenshot = move(screenshot);
test.did_load_fonts = false;
test.did_load_page = false;
test.loading_reference_page = true;
dbgln("load-reference-page");
view.debug_request("load-reference-page");
});
}
};
view.on_text_test_finish = [&](auto const&) { view.on_text_test_finish = [&](auto const&) {
dbgln("Unexpected text test finished during ref test for {}", url); dbgln("Unexpected text test finished during ref test for {}", url);
}; };

View file

@ -66,9 +66,13 @@ struct Test {
String text {}; String text {};
bool did_finish_test { false }; bool did_finish_test { false };
bool did_finish_loading { false }; bool did_finish_loading { false };
bool did_load_page { false };
bool did_load_fonts { false };
bool loading_reference_page { false };
RefPtr<Gfx::Bitmap> actual_screenshot {}; RefPtr<Gfx::Bitmap> actual_screenshot {};
RefPtr<Gfx::Bitmap> expectation_screenshot {}; RefPtr<Gfx::Bitmap> expectation_screenshot {};
bool is_ref() const { return mode == TestMode::Ref; };
}; };
struct TestCompletion { struct TestCompletion {