d08670d9b1b1ccb4504c2a5838a0820787735ba05c89dee5872c3d769166668bdc74f9df36d533ca2ec306fd8d993cb6a21a7a575aea952d3d1485ba6c64ff 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import * as browser from '../../../base/browser/browser.js';
  6. import { Emitter } from '../../../base/common/event.js';
  7. import { Disposable } from '../../../base/common/lifecycle.js';
  8. import { CharWidthRequest, readCharWidths } from './charWidthReader.js';
  9. import { EditorFontLigatures } from '../../common/config/editorOptions.js';
  10. import { FontInfo } from '../../common/config/fontInfo.js';
  11. class FontMeasurementsImpl extends Disposable {
  12. constructor() {
  13. super();
  14. this._onDidChange = this._register(new Emitter());
  15. this.onDidChange = this._onDidChange.event;
  16. this._cache = new FontMeasurementsCache();
  17. this._evictUntrustedReadingsTimeout = -1;
  18. }
  19. dispose() {
  20. if (this._evictUntrustedReadingsTimeout !== -1) {
  21. window.clearTimeout(this._evictUntrustedReadingsTimeout);
  22. this._evictUntrustedReadingsTimeout = -1;
  23. }
  24. super.dispose();
  25. }
  26. /**
  27. * Clear all cached font information and trigger a change event.
  28. */
  29. clearAllFontInfos() {
  30. this._cache = new FontMeasurementsCache();
  31. this._onDidChange.fire();
  32. }
  33. _writeToCache(item, value) {
  34. this._cache.put(item, value);
  35. if (!value.isTrusted && this._evictUntrustedReadingsTimeout === -1) {
  36. // Try reading again after some time
  37. this._evictUntrustedReadingsTimeout = window.setTimeout(() => {
  38. this._evictUntrustedReadingsTimeout = -1;
  39. this._evictUntrustedReadings();
  40. }, 5000);
  41. }
  42. }
  43. _evictUntrustedReadings() {
  44. const values = this._cache.getValues();
  45. let somethingRemoved = false;
  46. for (const item of values) {
  47. if (!item.isTrusted) {
  48. somethingRemoved = true;
  49. this._cache.remove(item);
  50. }
  51. }
  52. if (somethingRemoved) {
  53. this._onDidChange.fire();
  54. }
  55. }
  56. /**
  57. * Read font information.
  58. */
  59. readFontInfo(bareFontInfo) {
  60. if (!this._cache.has(bareFontInfo)) {
  61. let readConfig = this._actualReadFontInfo(bareFontInfo);
  62. if (readConfig.typicalHalfwidthCharacterWidth <= 2 || readConfig.typicalFullwidthCharacterWidth <= 2 || readConfig.spaceWidth <= 2 || readConfig.maxDigitWidth <= 2) {
  63. // Hey, it's Bug 14341 ... we couldn't read
  64. readConfig = new FontInfo({
  65. pixelRatio: browser.PixelRatio.value,
  66. fontFamily: readConfig.fontFamily,
  67. fontWeight: readConfig.fontWeight,
  68. fontSize: readConfig.fontSize,
  69. fontFeatureSettings: readConfig.fontFeatureSettings,
  70. lineHeight: readConfig.lineHeight,
  71. letterSpacing: readConfig.letterSpacing,
  72. isMonospace: readConfig.isMonospace,
  73. typicalHalfwidthCharacterWidth: Math.max(readConfig.typicalHalfwidthCharacterWidth, 5),
  74. typicalFullwidthCharacterWidth: Math.max(readConfig.typicalFullwidthCharacterWidth, 5),
  75. canUseHalfwidthRightwardsArrow: readConfig.canUseHalfwidthRightwardsArrow,
  76. spaceWidth: Math.max(readConfig.spaceWidth, 5),
  77. middotWidth: Math.max(readConfig.middotWidth, 5),
  78. wsmiddotWidth: Math.max(readConfig.wsmiddotWidth, 5),
  79. maxDigitWidth: Math.max(readConfig.maxDigitWidth, 5),
  80. }, false);
  81. }
  82. this._writeToCache(bareFontInfo, readConfig);
  83. }
  84. return this._cache.get(bareFontInfo);
  85. }
  86. _createRequest(chr, type, all, monospace) {
  87. const result = new CharWidthRequest(chr, type);
  88. all.push(result);
  89. monospace === null || monospace === void 0 ? void 0 : monospace.push(result);
  90. return result;
  91. }
  92. _actualReadFontInfo(bareFontInfo) {
  93. const all = [];
  94. const monospace = [];
  95. const typicalHalfwidthCharacter = this._createRequest('n', 0 /* CharWidthRequestType.Regular */, all, monospace);
  96. const typicalFullwidthCharacter = this._createRequest('\uff4d', 0 /* CharWidthRequestType.Regular */, all, null);
  97. const space = this._createRequest(' ', 0 /* CharWidthRequestType.Regular */, all, monospace);
  98. const digit0 = this._createRequest('0', 0 /* CharWidthRequestType.Regular */, all, monospace);
  99. const digit1 = this._createRequest('1', 0 /* CharWidthRequestType.Regular */, all, monospace);
  100. const digit2 = this._createRequest('2', 0 /* CharWidthRequestType.Regular */, all, monospace);
  101. const digit3 = this._createRequest('3', 0 /* CharWidthRequestType.Regular */, all, monospace);
  102. const digit4 = this._createRequest('4', 0 /* CharWidthRequestType.Regular */, all, monospace);
  103. const digit5 = this._createRequest('5', 0 /* CharWidthRequestType.Regular */, all, monospace);
  104. const digit6 = this._createRequest('6', 0 /* CharWidthRequestType.Regular */, all, monospace);
  105. const digit7 = this._createRequest('7', 0 /* CharWidthRequestType.Regular */, all, monospace);
  106. const digit8 = this._createRequest('8', 0 /* CharWidthRequestType.Regular */, all, monospace);
  107. const digit9 = this._createRequest('9', 0 /* CharWidthRequestType.Regular */, all, monospace);
  108. // monospace test: used for whitespace rendering
  109. const rightwardsArrow = this._createRequest('→', 0 /* CharWidthRequestType.Regular */, all, monospace);
  110. const halfwidthRightwardsArrow = this._createRequest('→', 0 /* CharWidthRequestType.Regular */, all, null);
  111. // U+00B7 - MIDDLE DOT
  112. const middot = this._createRequest('·', 0 /* CharWidthRequestType.Regular */, all, monospace);
  113. // U+2E31 - WORD SEPARATOR MIDDLE DOT
  114. const wsmiddotWidth = this._createRequest(String.fromCharCode(0x2E31), 0 /* CharWidthRequestType.Regular */, all, null);
  115. // monospace test: some characters
  116. const monospaceTestChars = '|/-_ilm%';
  117. for (let i = 0, len = monospaceTestChars.length; i < len; i++) {
  118. this._createRequest(monospaceTestChars.charAt(i), 0 /* CharWidthRequestType.Regular */, all, monospace);
  119. this._createRequest(monospaceTestChars.charAt(i), 1 /* CharWidthRequestType.Italic */, all, monospace);
  120. this._createRequest(monospaceTestChars.charAt(i), 2 /* CharWidthRequestType.Bold */, all, monospace);
  121. }
  122. readCharWidths(bareFontInfo, all);
  123. const maxDigitWidth = Math.max(digit0.width, digit1.width, digit2.width, digit3.width, digit4.width, digit5.width, digit6.width, digit7.width, digit8.width, digit9.width);
  124. let isMonospace = (bareFontInfo.fontFeatureSettings === EditorFontLigatures.OFF);
  125. const referenceWidth = monospace[0].width;
  126. for (let i = 1, len = monospace.length; isMonospace && i < len; i++) {
  127. const diff = referenceWidth - monospace[i].width;
  128. if (diff < -0.001 || diff > 0.001) {
  129. isMonospace = false;
  130. break;
  131. }
  132. }
  133. let canUseHalfwidthRightwardsArrow = true;
  134. if (isMonospace && halfwidthRightwardsArrow.width !== referenceWidth) {
  135. // using a halfwidth rightwards arrow would break monospace...
  136. canUseHalfwidthRightwardsArrow = false;
  137. }
  138. if (halfwidthRightwardsArrow.width > rightwardsArrow.width) {
  139. // using a halfwidth rightwards arrow would paint a larger arrow than a regular rightwards arrow
  140. canUseHalfwidthRightwardsArrow = false;
  141. }
  142. return new FontInfo({
  143. pixelRatio: browser.PixelRatio.value,
  144. fontFamily: bareFontInfo.fontFamily,
  145. fontWeight: bareFontInfo.fontWeight,
  146. fontSize: bareFontInfo.fontSize,
  147. fontFeatureSettings: bareFontInfo.fontFeatureSettings,
  148. lineHeight: bareFontInfo.lineHeight,
  149. letterSpacing: bareFontInfo.letterSpacing,
  150. isMonospace: isMonospace,
  151. typicalHalfwidthCharacterWidth: typicalHalfwidthCharacter.width,
  152. typicalFullwidthCharacterWidth: typicalFullwidthCharacter.width,
  153. canUseHalfwidthRightwardsArrow: canUseHalfwidthRightwardsArrow,
  154. spaceWidth: space.width,
  155. middotWidth: middot.width,
  156. wsmiddotWidth: wsmiddotWidth.width,
  157. maxDigitWidth: maxDigitWidth
  158. }, true);
  159. }
  160. }
  161. class FontMeasurementsCache {
  162. constructor() {
  163. this._keys = Object.create(null);
  164. this._values = Object.create(null);
  165. }
  166. has(item) {
  167. const itemId = item.getId();
  168. return !!this._values[itemId];
  169. }
  170. get(item) {
  171. const itemId = item.getId();
  172. return this._values[itemId];
  173. }
  174. put(item, value) {
  175. const itemId = item.getId();
  176. this._keys[itemId] = item;
  177. this._values[itemId] = value;
  178. }
  179. remove(item) {
  180. const itemId = item.getId();
  181. delete this._keys[itemId];
  182. delete this._values[itemId];
  183. }
  184. getValues() {
  185. return Object.keys(this._keys).map(id => this._values[id]);
  186. }
  187. }
  188. export const FontMeasurements = new FontMeasurementsImpl();