Check font style and weight in addition to family

This commit is contained in:
ahocevar
2019-07-04 16:12:35 +02:00
parent ab2d97d49b
commit 4b48997a0b
4 changed files with 77 additions and 47 deletions
+20 -3
View File
@@ -2,6 +2,13 @@
* @module ol/css * @module ol/css
*/ */
/**
* @typedef {Object} FontParameters
* @property {Array<string>} families
* @property {string} style
* @property {string} weight
*/
/** /**
* The CSS class for hidden feature. * The CSS class for hidden feature.
@@ -62,10 +69,13 @@ export const CLASS_COLLAPSED = 'ol-collapsed';
* Get the list of font families from a font spec. Note that this doesn't work * Get the list of font families from a font spec. Note that this doesn't work
* for font families that have commas in them. * for font families that have commas in them.
* @param {string} The CSS font property. * @param {string} The CSS font property.
* @return {Object<string>} The font families (or null if the input spec is invalid). * @return {FontParameters} The font families (or null if the input spec is invalid).
*/ */
export const getFontFamilies = (function() { export const getFontParameters = (function() {
let style; let style;
/**
* @type {Object<string, FontParameters>}
*/
const cache = {}; const cache = {};
return function(font) { return function(font) {
if (!style) { if (!style) {
@@ -74,11 +84,18 @@ export const getFontFamilies = (function() {
if (!(font in cache)) { if (!(font in cache)) {
style.font = font; style.font = font;
const family = style.fontFamily; const family = style.fontFamily;
const fontWeight = style.fontWeight;
const fontStyle = style.fontStyle;
style.font = ''; style.font = '';
if (!family) { if (!family) {
return null; return null;
} }
cache[font] = family.split(/,\s?/); const families = family.split(/,\s?/);
cache[font] = {
families: families,
weight: fontWeight,
style: fontStyle
};
} }
return cache[font]; return cache[font];
}; };
+34 -34
View File
@@ -1,7 +1,7 @@
/** /**
* @module ol/render/canvas * @module ol/render/canvas
*/ */
import {getFontFamilies} from '../css.js'; import {getFontParameters} from '../css.js';
import {createCanvasContext2D} from '../dom.js'; import {createCanvasContext2D} from '../dom.js';
import {clear} from '../obj.js'; import {clear} from '../obj.js';
import {create as createTransform} from '../transform.js'; import {create as createTransform} from '../transform.js';
@@ -204,32 +204,30 @@ export const checkFont = (function() {
const text = 'wmytzilWMYTZIL@#/&?$%10\uF013'; const text = 'wmytzilWMYTZIL@#/&?$%10\uF013';
let interval, referenceWidth; let interval, referenceWidth;
function isAvailable(font) { /**
* @param {string} fontStyle Css font-style
* @param {string} fontWeight Css font-weight
* @param {*} fontFamily Css font-family
* @return {boolean} Font with style and weight is available
*/
function isAvailable(fontStyle, fontWeight, fontFamily) {
const context = getMeasureContext(); const context = getMeasureContext();
// Check weight ranges according to let available = true;
// https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#Fallback_weights for (let i = 0; i < len; ++i) {
for (let weight = 100; weight <= 700; weight += 300) { const referenceFont = referenceFonts[i];
const fontWeight = weight + ' '; context.font = fontStyle + ' ' + fontWeight + ' ' + size + referenceFont;
let available = true; referenceWidth = context.measureText(text).width;
for (let i = 0; i < len; ++i) { if (fontFamily != referenceFont) {
const referenceFont = referenceFonts[i]; context.font = fontStyle + ' ' + fontWeight + ' ' + size + fontFamily + ',' + referenceFont;
context.font = fontWeight + size + referenceFont; const width = context.measureText(text).width;
referenceWidth = context.measureText(text).width; // If width and referenceWidth are the same, then the fallback was used
if (font != referenceFont) { // instead of the font we wanted, so the font is not available.
context.font = fontWeight + size + font + ',' + referenceFont; available = available && width != referenceWidth;
const width = context.measureText(text).width;
// If width and referenceWidth are the same, then the fallback was used
// instead of the font we wanted, so the font is not available.
available = available && width != referenceWidth;
}
}
if (available) {
// Consider font available when it is available in one weight range.
//FIXME With this we miss rare corner cases, so we should consider
//FIXME checking availability for each requested weight range.
return true;
} }
} }
if (available) {
return true;
}
return false; return false;
} }
@@ -237,14 +235,14 @@ export const checkFont = (function() {
let done = true; let done = true;
for (const font in checked) { for (const font in checked) {
if (checked[font] < retries) { if (checked[font] < retries) {
if (isAvailable(font)) { if (isAvailable.apply(this, font.split('\n'))) {
checked[font] = retries; checked[font] = retries;
clear(textHeights); clear(textHeights);
// Make sure that loaded fonts are picked up by Safari // Make sure that loaded fonts are picked up by Safari
measureContext = null; measureContext = null;
measureFont = undefined; measureFont = undefined;
if (labelCache.getCount()) { if (labelCache.getCount()) {
labelCache.clear(); labelCache.clear();
} }
} else { } else {
++checked[font]; ++checked[font];
@@ -259,16 +257,18 @@ export const checkFont = (function() {
} }
return function(fontSpec) { return function(fontSpec) {
const fontFamilies = getFontFamilies(fontSpec); const font = getFontParameters(fontSpec);
if (!fontFamilies) { if (!font) {
return; return;
} }
for (let i = 0, ii = fontFamilies.length; i < ii; ++i) { const families = font.families;
const fontFamily = fontFamilies[i]; for (let i = 0, ii = families.length; i < ii; ++i) {
if (!(fontFamily in checked)) { const family = families[i];
checked[fontFamily] = retries; const key = font.style + '\n' + font.weight + '\n' + family;
if (!isAvailable(fontFamily)) { if (!(key in checked)) {
checked[fontFamily] = 0; checked[key] = retries;
if (!isAvailable(font.style, font.weight, family)) {
checked[key] = 0;
if (interval === undefined) { if (interval === undefined) {
interval = setInterval(check, 32); interval = setInterval(check, 32);
} }
+19 -7
View File
@@ -1,44 +1,56 @@
import {getFontFamilies} from '../../../src/ol/css.js'; import {getFontParameters} from '../../../src/ol/css.js';
describe('ol.css', function() { describe('ol.css', function() {
describe('getFontFamilies()', function() { describe('getFontParameters()', function() {
const cases = [{ const cases = [{
font: '2em "Open Sans"', font: '2em "Open Sans"',
style: 'normal',
weight: 'normal',
families: ['"Open Sans"'] families: ['"Open Sans"']
}, { }, {
font: '2em \'Open Sans\'', font: '2em \'Open Sans\'',
style: 'normal',
weight: 'normal',
families: ['"Open Sans"'] families: ['"Open Sans"']
}, { }, {
font: '2em "Open Sans", sans-serif', font: '2em "Open Sans", sans-serif',
style: 'normal',
weight: 'normal',
families: ['"Open Sans"', 'sans-serif'] families: ['"Open Sans"', 'sans-serif']
}, { }, {
font: 'italic small-caps bolder 16px/3 cursive', font: 'italic small-caps bolder 16px/3 cursive',
style: 'italic',
weight: 'bolder',
families: ['cursive'] families: ['cursive']
}, { }, {
font: 'garbage 2px input', font: 'garbage 2px input',
families: null families: null
}, { }, {
font: '100% fantasy', font: '100% fantasy',
style: 'normal',
weight: 'normal',
families: ['fantasy'] families: ['fantasy']
}]; }];
cases.forEach(function(c, i) { cases.forEach(function(c, i) {
it('works for ' + c.font, function() { it('works for ' + c.font, function() {
const families = getFontFamilies(c.font); const font = getFontParameters(c.font);
if (c.families === null) { if (c.families === null) {
expect(families).to.be(null); expect(font).to.be(null);
return; return;
} }
families.forEach(function(family, j) { font.families.forEach(function(family, j) {
// Safari uses single quotes for font families, so we have to do extra work // Safari uses single quotes for font families, so we have to do extra work
if (family.charAt(0) === '\'') { if (family.charAt(0) === '\'') {
// we wouldn't want to do this in the lib since it doesn't properly escape quotes // we wouldn't want to do this in the lib since it doesn't properly escape quotes
// but we know that our test cases don't include quotes in font names // but we know that our test cases don't include quotes in font names
families[j] = '"' + family.slice(1, -1) + '"'; font.families[j] = '"' + family.slice(1, -1) + '"';
} }
}); });
expect(families).to.eql(c.families); expect(font.style).to.eql(c.style);
expect(font.weight).to.eql(c.weight);
expect(font.families).to.eql(c.families);
}); });
}); });
+4 -3
View File
@@ -24,7 +24,7 @@ describe('ol.render.canvas', function() {
const spy = sinon.spy(); const spy = sinon.spy();
listen(render.labelCache, 'clear', spy); listen(render.labelCache, 'clear', spy);
const interval = setInterval(function() { const interval = setInterval(function() {
if (render.checkedFonts['foo'] == retries && render.checkedFonts['sans-serif'] == retries) { if (render.checkedFonts['normal\nnormal\nfoo'] == retries && render.checkedFonts['normal\nnormal\nsans-serif'] == retries) {
clearInterval(interval); clearInterval(interval);
unlisten(render.labelCache, 'clear', spy); unlisten(render.labelCache, 'clear', spy);
expect(spy.callCount).to.be(0); expect(spy.callCount).to.be(0);
@@ -39,7 +39,7 @@ describe('ol.render.canvas', function() {
const spy = sinon.spy(); const spy = sinon.spy();
listen(render.labelCache, 'clear', spy); listen(render.labelCache, 'clear', spy);
const interval = setInterval(function() { const interval = setInterval(function() {
if (render.checkedFonts['sans-serif'] == retries) { if (render.checkedFonts['normal\nnormal\nsans-serif'] == retries) {
clearInterval(interval); clearInterval(interval);
unlisten(render.labelCache, 'clear', spy); unlisten(render.labelCache, 'clear', spy);
expect(spy.callCount).to.be(0); expect(spy.callCount).to.be(0);
@@ -54,7 +54,7 @@ describe('ol.render.canvas', function() {
const spy = sinon.spy(); const spy = sinon.spy();
listen(render.labelCache, 'clear', spy); listen(render.labelCache, 'clear', spy);
const interval = setInterval(function() { const interval = setInterval(function() {
if (render.checkedFonts['monospace'] == retries) { if (render.checkedFonts['normal\nnormal\nmonospace'] == retries) {
clearInterval(interval); clearInterval(interval);
unlisten(render.labelCache, 'clear', spy); unlisten(render.labelCache, 'clear', spy);
expect(spy.callCount).to.be(0); expect(spy.callCount).to.be(0);
@@ -67,6 +67,7 @@ describe('ol.render.canvas', function() {
it('clears label cache and measurements for fonts that become available', function(done) { it('clears label cache and measurements for fonts that become available', function(done) {
head.appendChild(font); head.appendChild(font);
render.labelCache.set('dummy', {});
listen(render.labelCache, 'clear', function() { listen(render.labelCache, 'clear', function() {
expect(render.textHeights).to.eql({}); expect(render.textHeights).to.eql({});
done(); done();