blob: aeba40aa749eca86aade09cfa5ebc32ba1b352bd [file] [log] [blame]
/*
* Copyright (C) 2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
WI.Font = class Font
{
constructor(name, variationAxes)
{
this._name = name;
this._variationAxes = variationAxes;
}
// Static
static fromPayload(payload)
{
let variationAxes = payload.variationAxes.map((axisPayload) => WI.FontVariationAxis.fromPayload(axisPayload));
return new WI.Font(payload.displayName, variationAxes);
}
// Public
get name() { return this._name; }
get variationAxes() { return this._variationAxes; }
variationAxis(tag)
{
return this._variationAxes.find((axis) => axis.tag === tag);
}
calculateFontProperties(domNodeStyle)
{
let featuresMap = this._calculateFontFeatureAxes(domNodeStyle);
let variationsMap = this._calculateFontVariationAxes(domNodeStyle);
let propertiesMap = this._calculateProperties({domNodeStyle, featuresMap, variationsMap});
return {propertiesMap, featuresMap, variationsMap};
}
// Private
_calculateProperties(style)
{
let resultProperties = new Map;
this._populateProperty("font-size", style, resultProperties, {
keywordComputedReplacements: ["larger", "smaller", "xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"],
});
this._populateProperty("font-style", style, resultProperties, {
variations: ["ital", "slnt"],
keywordReplacements: new Map([
["oblique", "oblique 14deg"],
]),
});
this._populateProperty("font-weight", style, resultProperties, {
variations: ["wght"],
keywordComputedReplacements: ["bolder", "lighter"],
keywordReplacements: new Map([
["normal", "400"],
["bold", "700"],
]),
});
this._populateProperty("font-stretch", style, resultProperties, {
variations: ["wdth"],
keywordReplacements: new Map([
["ultra-condensed", "50%"],
["extra-condensed", "62.5%"],
["condensed", "75%"],
["semi-condensed", "87.5%"],
["normal", "100%"],
["semi-expanded", "112.5%"],
["expanded", "125%"],
["extra-expanded", "150%"],
["ultra-expanded", "200%"],
]),
});
this._populateProperty("font-variant-ligatures", style, resultProperties, {features: ["liga", "clig", "dlig", "hlig", "calt"]});
this._populateProperty("font-variant-position", style, resultProperties, {features: ["subs", "sups"]});
this._populateProperty("font-variant-caps", style, resultProperties, {features: ["smcp", "c2sc", "pcap", "c2pc", "unic", "titl"]});
this._populateProperty("font-variant-numeric", style, resultProperties, {features: ["lnum", "onum", "pnum", "tnum", "frac", "afrc", "ordn", "zero"]});
this._populateProperty("font-variant-alternates", style, resultProperties, {features: ["hist"] });
this._populateProperty("font-variant-east-asian", style, resultProperties, {features: ["jp78", "jp83", "jp90", "jp04", "smpl", "trad", "fwid", "pwid", "ruby"]});
return resultProperties;
}
_calculateFontFeatureAxes(domNodeStyle)
{
return this._parseFontFeatureOrVariationSettings(domNodeStyle, "font-feature-settings");
}
_calculateFontVariationAxes(domNodeStyle)
{
let cssVariationSettings = this._parseFontFeatureOrVariationSettings(domNodeStyle, "font-variation-settings");
let resultAxes = new Map;
for (let axis of this._variationAxes) {
// `value` can be undefined.
resultAxes.set(axis.tag, {
name: axis.name,
minimumValue: axis.minimumValue,
maximumValue: axis.maximumValue,
defaultValue: axis.defaultValue,
value: cssVariationSettings.get(axis.tag),
});
}
return resultAxes;
}
_parseFontFeatureOrVariationSettings(domNodeStyle, property)
{
let cssSettings = new Map;
let cssSettingsRawValue = this._computedPropertyValueForName(domNodeStyle, property);
if (cssSettingsRawValue !== "normal") {
for (let axis of cssSettingsRawValue.split(",")) {
// Tags can contains upper and lowercase latin letters, numbers, and spaces (only ending with space(s)). Values will be numbers, `on`, or `off`.
let [tag, value] = axis.match(WI.Font.SettingPattern);
tag = tag.replaceAll(/["']/g, "");
if (!value || value === "on")
value = 1;
else if (value === "off")
value = 0;
cssSettings.set(tag, parseFloat(value));
}
}
return cssSettings;
}
_populateProperty(name, style, resultProperties, {variations, features, keywordComputedReplacements, keywordReplacements})
{
resultProperties.set(name, this._computeProperty(name, style, {variations, features, keywordComputedReplacements, keywordReplacements}));
}
_computeProperty(name, style, {variations, features, keywordComputedReplacements, keywordReplacements})
{
variations ??= [];
features ??= [];
keywordComputedReplacements ??= [];
keywordReplacements ??= new Map;
let resultProperties = {};
let value = this._effectivePropertyValueForName(style.domNodeStyle, name);
if (!value || value === "inherit" || keywordComputedReplacements.includes(value))
value = this._computedPropertyValueForName(style.domNodeStyle, name);
if (keywordReplacements.has(value))
value = keywordReplacements.get(value);
resultProperties.value = value;
for (let fontVariationSetting of variations) {
let variationSettingValue = style.variationsMap.get(fontVariationSetting);
if (variationSettingValue) {
resultProperties.variations ??= new Map;
resultProperties.variations.set(fontVariationSetting, variationSettingValue);
// Remove the tag so it is not presented twice.
style.variationsMap.delete(fontVariationSetting);
}
}
for (let fontFeatureSetting of features) {
let featureSettingValue = style.featuresMap.get(fontFeatureSetting);
if (featureSettingValue || featureSettingValue === 0) {
resultProperties.features ??= new Map;
resultProperties.features.set(fontFeatureSetting, featureSettingValue);
// Remove the tag so it is not presented twice.
style.featuresMap.delete(fontFeatureSetting);
}
}
return resultProperties;
}
_effectivePropertyValueForName(domNodeStyle, name)
{
return domNodeStyle.effectivePropertyForName(name)?.value || "";
}
_computedPropertyValueForName(domNodeStyle, name)
{
return domNodeStyle.computedStyle?.propertyForName(name)?.value || "";
}
};
WI.Font.SettingPattern = /[^\s"']+|["']([^"']*)["']/g;