/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as browser from '../../../base/browser/browser.js'; import { Emitter } from '../../../base/common/event.js'; import { Disposable } from '../../../base/common/lifecycle.js'; import { CharWidthRequest, readCharWidths } from './charWidthReader.js'; import { EditorFontLigatures } from '../../common/config/editorOptions.js'; import { FontInfo } from '../../common/config/fontInfo.js'; class FontMeasurementsImpl extends Disposable { constructor() { super(); this._onDidChange = this._register(new Emitter()); this.onDidChange = this._onDidChange.event; this._cache = new FontMeasurementsCache(); this._evictUntrustedReadingsTimeout = -1; } dispose() { if (this._evictUntrustedReadingsTimeout !== -1) { window.clearTimeout(this._evictUntrustedReadingsTimeout); this._evictUntrustedReadingsTimeout = -1; } super.dispose(); } /** * Clear all cached font information and trigger a change event. */ clearAllFontInfos() { this._cache = new FontMeasurementsCache(); this._onDidChange.fire(); } _writeToCache(item, value) { this._cache.put(item, value); if (!value.isTrusted && this._evictUntrustedReadingsTimeout === -1) { // Try reading again after some time this._evictUntrustedReadingsTimeout = window.setTimeout(() => { this._evictUntrustedReadingsTimeout = -1; this._evictUntrustedReadings(); }, 5000); } } _evictUntrustedReadings() { const values = this._cache.getValues(); let somethingRemoved = false; for (const item of values) { if (!item.isTrusted) { somethingRemoved = true; this._cache.remove(item); } } if (somethingRemoved) { this._onDidChange.fire(); } } /** * Read font information. */ readFontInfo(bareFontInfo) { if (!this._cache.has(bareFontInfo)) { let readConfig = this._actualReadFontInfo(bareFontInfo); if (readConfig.typicalHalfwidthCharacterWidth <= 2 || readConfig.typicalFullwidthCharacterWidth <= 2 || readConfig.spaceWidth <= 2 || readConfig.maxDigitWidth <= 2) { // Hey, it's Bug 14341 ... we couldn't read readConfig = new FontInfo({ pixelRatio: browser.PixelRatio.value, fontFamily: readConfig.fontFamily, fontWeight: readConfig.fontWeight, fontSize: readConfig.fontSize, fontFeatureSettings: readConfig.fontFeatureSettings, lineHeight: readConfig.lineHeight, letterSpacing: readConfig.letterSpacing, isMonospace: readConfig.isMonospace, typicalHalfwidthCharacterWidth: Math.max(readConfig.typicalHalfwidthCharacterWidth, 5), typicalFullwidthCharacterWidth: Math.max(readConfig.typicalFullwidthCharacterWidth, 5), canUseHalfwidthRightwardsArrow: readConfig.canUseHalfwidthRightwardsArrow, spaceWidth: Math.max(readConfig.spaceWidth, 5), middotWidth: Math.max(readConfig.middotWidth, 5), wsmiddotWidth: Math.max(readConfig.wsmiddotWidth, 5), maxDigitWidth: Math.max(readConfig.maxDigitWidth, 5), }, false); } this._writeToCache(bareFontInfo, readConfig); } return this._cache.get(bareFontInfo); } _createRequest(chr, type, all, monospace) { const result = new CharWidthRequest(chr, type); all.push(result); monospace === null || monospace === void 0 ? void 0 : monospace.push(result); return result; } _actualReadFontInfo(bareFontInfo) { const all = []; const monospace = []; const typicalHalfwidthCharacter = this._createRequest('n', 0 /* CharWidthRequestType.Regular */, all, monospace); const typicalFullwidthCharacter = this._createRequest('\uff4d', 0 /* CharWidthRequestType.Regular */, all, null); const space = this._createRequest(' ', 0 /* CharWidthRequestType.Regular */, all, monospace); const digit0 = this._createRequest('0', 0 /* CharWidthRequestType.Regular */, all, monospace); const digit1 = this._createRequest('1', 0 /* CharWidthRequestType.Regular */, all, monospace); const digit2 = this._createRequest('2', 0 /* CharWidthRequestType.Regular */, all, monospace); const digit3 = this._createRequest('3', 0 /* CharWidthRequestType.Regular */, all, monospace); const digit4 = this._createRequest('4', 0 /* CharWidthRequestType.Regular */, all, monospace); const digit5 = this._createRequest('5', 0 /* CharWidthRequestType.Regular */, all, monospace); const digit6 = this._createRequest('6', 0 /* CharWidthRequestType.Regular */, all, monospace); const digit7 = this._createRequest('7', 0 /* CharWidthRequestType.Regular */, all, monospace); const digit8 = this._createRequest('8', 0 /* CharWidthRequestType.Regular */, all, monospace); const digit9 = this._createRequest('9', 0 /* CharWidthRequestType.Regular */, all, monospace); // monospace test: used for whitespace rendering const rightwardsArrow = this._createRequest('→', 0 /* CharWidthRequestType.Regular */, all, monospace); const halfwidthRightwardsArrow = this._createRequest('→', 0 /* CharWidthRequestType.Regular */, all, null); // U+00B7 - MIDDLE DOT const middot = this._createRequest('·', 0 /* CharWidthRequestType.Regular */, all, monospace); // U+2E31 - WORD SEPARATOR MIDDLE DOT const wsmiddotWidth = this._createRequest(String.fromCharCode(0x2E31), 0 /* CharWidthRequestType.Regular */, all, null); // monospace test: some characters const monospaceTestChars = '|/-_ilm%'; for (let i = 0, len = monospaceTestChars.length; i < len; i++) { this._createRequest(monospaceTestChars.charAt(i), 0 /* CharWidthRequestType.Regular */, all, monospace); this._createRequest(monospaceTestChars.charAt(i), 1 /* CharWidthRequestType.Italic */, all, monospace); this._createRequest(monospaceTestChars.charAt(i), 2 /* CharWidthRequestType.Bold */, all, monospace); } readCharWidths(bareFontInfo, all); const maxDigitWidth = Math.max(digit0.width, digit1.width, digit2.width, digit3.width, digit4.width, digit5.width, digit6.width, digit7.width, digit8.width, digit9.width); let isMonospace = (bareFontInfo.fontFeatureSettings === EditorFontLigatures.OFF); const referenceWidth = monospace[0].width; for (let i = 1, len = monospace.length; isMonospace && i < len; i++) { const diff = referenceWidth - monospace[i].width; if (diff < -0.001 || diff > 0.001) { isMonospace = false; break; } } let canUseHalfwidthRightwardsArrow = true; if (isMonospace && halfwidthRightwardsArrow.width !== referenceWidth) { // using a halfwidth rightwards arrow would break monospace... canUseHalfwidthRightwardsArrow = false; } if (halfwidthRightwardsArrow.width > rightwardsArrow.width) { // using a halfwidth rightwards arrow would paint a larger arrow than a regular rightwards arrow canUseHalfwidthRightwardsArrow = false; } return new FontInfo({ pixelRatio: browser.PixelRatio.value, fontFamily: bareFontInfo.fontFamily, fontWeight: bareFontInfo.fontWeight, fontSize: bareFontInfo.fontSize, fontFeatureSettings: bareFontInfo.fontFeatureSettings, lineHeight: bareFontInfo.lineHeight, letterSpacing: bareFontInfo.letterSpacing, isMonospace: isMonospace, typicalHalfwidthCharacterWidth: typicalHalfwidthCharacter.width, typicalFullwidthCharacterWidth: typicalFullwidthCharacter.width, canUseHalfwidthRightwardsArrow: canUseHalfwidthRightwardsArrow, spaceWidth: space.width, middotWidth: middot.width, wsmiddotWidth: wsmiddotWidth.width, maxDigitWidth: maxDigitWidth }, true); } } class FontMeasurementsCache { constructor() { this._keys = Object.create(null); this._values = Object.create(null); } has(item) { const itemId = item.getId(); return !!this._values[itemId]; } get(item) { const itemId = item.getId(); return this._values[itemId]; } put(item, value) { const itemId = item.getId(); this._keys[itemId] = item; this._values[itemId] = value; } remove(item) { const itemId = item.getId(); delete this._keys[itemId]; delete this._values[itemId]; } getValues() { return Object.keys(this._keys).map(id => this._values[id]); } } export const FontMeasurements = new FontMeasurementsImpl();