<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Fonts Module Level 4: getComputedStyle().font</title>
<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#font-prop">
<meta name="assert" content="font computed value round-trips.">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/css/support/computed-testcommon.js"></script>
<style>
#container {
font-weight: 800;
font-size: 40px;
}
</style>
</head>
<body>
<div id="container">
<div id="target"></div>
</div>
<script>
'use strict';
// Firefox and Edge 18 serialize these as supplied.
// Blink and Safari have implementation-dependent or platform-dependent serializations.
function test_system_font(keyword) {
test(() => {
const target = document.getElementById('target');
const previousValue = 'italic xx-large/0px fantasy';
target.style.font = previousValue;
target.style.font = keyword;
const readValue = getComputedStyle(target).font;
assert_not_equals(readValue, '', 'font should be set');
assert_not_equals(readValue, previousValue, 'font should be updated');
target.style.font = previousValue;
target.style.font = readValue;
assert_equals(getComputedStyle(target).font, readValue, "serialization should round-trip");
}, keyword + ' should be a supported system font.');
}
test_system_font('caption');
test_system_font('icon');
test_system_font('menu');
test_system_font('message-box');
test_system_font('small-caption');
test_system_font('status-bar');
// a value other than normal
const generate_style = () => 'italic';
// value other than normal
const generate_variant = () => 'small-caps';
// values other than normal
const generate_weight = (() => {
const alternatives = [
'bold',
'bolder',
'lighter',
'100',
'900'
];
let counter = 0;
return () => alternatives[counter++ % alternatives.length];
})();
const compute_weight = (() => {
const cache = {}
return (weight) => {
if (!(weight in cache)) {
const weight_reference = document.createElement('div');
document.getElementById('container').appendChild(weight_reference);
weight_reference.style.fontWeight = weight;
cache[weight] = getComputedStyle(weight_reference).fontWeight;
weight_reference.remove();
}
return cache[weight];
}
})();
// values other than normal
const generate_stretch = (() => {
const alternatives = [
'ultra-condensed',
'extra-condensed',
'condensed',
'semi-condensed',
'semi-expanded',
'expanded',
'extra-expanded',
'ultra-expanded'
];
let counter = 0;
return () => alternatives[counter++ % alternatives.length];
})();
const generate_size = (() => {
const alternatives = [
// <absolute-size>
'xx-small',
'medium',
'xx-large',
// <relative-size>
'larger',
'smaller',
// <length-percentage>
'10px',
'20%',
'calc(30% - 40px)',
];
let counter = 0;
return () => alternatives[counter++ % alternatives.length];
})();
const generate_line_height = (() => {
const alternatives = [
null,
'normal',
'1.2',
'calc(120% + 1.2em)'
];
let counter = 0;
return () => alternatives[counter++ % alternatives.length];
})();
const generate_family = (() => {
const alternatives = [
'serif',
'sans-serif',
'cursive',
'fantasy',
'monospace',
'Menu',
'"Non-Generic Example Family Name"'
];
let counter = 0;
return () => alternatives[counter++ % alternatives.length];
})();
function test_specific(prefix) {
const reference = document.createElement('div');
document.getElementById('container').appendChild(reference);
let parts = [];
let canonical = [];
let style = null;
let variant = null;
let weight = null;
let stretch = null;
for (let entry of prefix) {
if (entry === 'style') {
style = generate_style();
parts.push(style);
} else if (entry === 'variant') {
variant = generate_variant();
parts.push(variant);
} else if (entry === 'weight') {
weight = generate_weight();
parts.push(weight);
} else if (entry === 'stretch') {
stretch = generate_stretch();
parts.push(stretch);
} else {
// normal
parts.push('normal');
}
}
if (style) {
canonical.push(style);
reference.style.fontStyle = style;
}
if (variant) {
canonical.push(variant);
reference.style.fontVariant = style;
}
if (weight) {
canonical.push(compute_weight(weight));
reference.style.fontWeight = style;
}
if (stretch) {
canonical.push(stretch);
reference.style.fontStretch = style;
}
const size = generate_size();
reference.style.fontSize = size;
const line_height = generate_line_height();
if (line_height) {
parts.push(size + '/' + line_height);
reference.style.lineHeight = line_height;
} else {
parts.push(size);
}
const family = generate_family();
parts.push(family);
reference.style.fontFamily = family;
if (!line_height || line_height === 'normal') {
canonical.push(getComputedStyle(reference).fontSize);
} else {
// Implementations differ on adjacent space when serializing '/'
// https://github.com/w3c/csswg-drafts/issues/4282
canonical.push(getComputedStyle(reference).fontSize + ' / ' + getComputedStyle(reference).lineHeight);
}
canonical.push(family);
reference.remove();
test_computed_value('font', parts.join(' '), canonical.join(' '));
}
// Font style, variant, weight and stretch may appear in any order.
// Any or all may be omitted. Each accepts the keyword 'normal'.
// We generate every permutation of these four properties, treating
// the cases of a property value being omitted or being explicitly
// 'normal' as being distinct permutations from when the property
// has a value other than 'normal'.
function test_various(prefix) {
test_specific(prefix);
if (prefix.length === 4) {
// Font style, variant, weight and stretch may not appear
// more than once.
return;
}
const alternatives = [
'normal',
'style',
'variant',
'weight',
'stretch'
];
for (let alternative of alternatives) {
// Since this is called recursively, check prefix for existing
// alternatives, otherwise we may have two styles or two variants, etc.
if (alternative === 'normal' || !prefix.includes(alternative))
test_various(prefix.concat(alternative));
}
}
test_various([]);
</script>
</body>
</html>