UNPKG

monaco-editor-core

Version:

A browser based code editor

204 lines (203 loc) 10.3 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { getWindowId } from '../../../base/browser/dom.js'; import { PixelRatio } from '../../../base/browser/pixelRatio.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'; export class FontMeasurementsImpl extends Disposable { constructor() { super(...arguments); this._cache = new Map(); this._evictUntrustedReadingsTimeout = -1; this._onDidChange = this._register(new Emitter()); this.onDidChange = this._onDidChange.event; } dispose() { if (this._evictUntrustedReadingsTimeout !== -1) { clearTimeout(this._evictUntrustedReadingsTimeout); this._evictUntrustedReadingsTimeout = -1; } super.dispose(); } /** * Clear all cached font information and trigger a change event. */ clearAllFontInfos() { this._cache.clear(); this._onDidChange.fire(); } _ensureCache(targetWindow) { const windowId = getWindowId(targetWindow); let cache = this._cache.get(windowId); if (!cache) { cache = new FontMeasurementsCache(); this._cache.set(windowId, cache); } return cache; } _writeToCache(targetWindow, item, value) { const cache = this._ensureCache(targetWindow); cache.put(item, value); if (!value.isTrusted && this._evictUntrustedReadingsTimeout === -1) { // Try reading again after some time this._evictUntrustedReadingsTimeout = targetWindow.setTimeout(() => { this._evictUntrustedReadingsTimeout = -1; this._evictUntrustedReadings(targetWindow); }, 5000); } } _evictUntrustedReadings(targetWindow) { const cache = this._ensureCache(targetWindow); const values = cache.getValues(); let somethingRemoved = false; for (const item of values) { if (!item.isTrusted) { somethingRemoved = true; cache.remove(item); } } if (somethingRemoved) { this._onDidChange.fire(); } } /** * Read font information. */ readFontInfo(targetWindow, bareFontInfo) { const cache = this._ensureCache(targetWindow); if (!cache.has(bareFontInfo)) { let readConfig = this._actualReadFontInfo(targetWindow, 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: PixelRatio.getInstance(targetWindow).value, fontFamily: readConfig.fontFamily, fontWeight: readConfig.fontWeight, fontSize: readConfig.fontSize, fontFeatureSettings: readConfig.fontFeatureSettings, fontVariationSettings: readConfig.fontVariationSettings, 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(targetWindow, bareFontInfo, readConfig); } return cache.get(bareFontInfo); } _createRequest(chr, type, all, monospace) { const result = new CharWidthRequest(chr, type); all.push(result); monospace?.push(result); return result; } _actualReadFontInfo(targetWindow, 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(targetWindow, 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: PixelRatio.getInstance(targetWindow).value, fontFamily: bareFontInfo.fontFamily, fontWeight: bareFontInfo.fontWeight, fontSize: bareFontInfo.fontSize, fontFeatureSettings: bareFontInfo.fontFeatureSettings, fontVariationSettings: bareFontInfo.fontVariationSettings, 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();