@@ -3,6 +3,11 @@ const AssetUtil = require('../util/tw-asset-util');
33const StringUtil = require ( '../util/string-util' ) ;
44const log = require ( '../util/log' ) ;
55
6+ /*
7+ * In general in this file, note that font names in browsers are case-insensitive
8+ * but are whitespace-sensitive.
9+ */
10+
611/**
712 * @typedef InternalFont
813 * @property {boolean } system True if the font is built in to the system
@@ -11,40 +16,121 @@ const log = require('../util/log');
1116 * @property {Asset } [asset] scratch-storage asset if system: false
1217 */
1318
19+ /**
20+ * @param {string } font
21+ * @returns {string }
22+ */
23+ const removeInvalidCharacters = font => font . replace ( / [ ^ - \w ] / g, '' ) ;
24+
25+ /**
26+ * @param {InternalFont[] } fonts Modified in-place
27+ * @param {InternalFont } newFont
28+ * @returns {InternalFont|null }
29+ */
30+ const addOrUpdateFont = ( fonts , newFont ) => {
31+ let oldFont ;
32+ const oldIndex = fonts . findIndex ( i => i . family . toLowerCase ( ) === newFont . family . toLowerCase ( ) ) ;
33+ if ( oldIndex !== - 1 ) {
34+ oldFont = fonts [ oldIndex ] ;
35+ fonts . splice ( oldIndex , 1 ) ;
36+ }
37+ fonts . push ( newFont ) ;
38+ return oldFont ;
39+ } ;
40+
1441class FontManager extends EventEmitter {
1542 /**
1643 * @param {Runtime } runtime
1744 */
1845 constructor ( runtime ) {
1946 super ( ) ;
47+
48+ /** @type {Runtime } */
2049 this . runtime = runtime ;
50+
2151 /** @type {Array<InternalFont> } */
2252 this . fonts = [ ] ;
53+
54+ /**
55+ * All entries should be lowercase.
56+ * @type {Set<string> }
57+ */
58+ this . restrictedFonts = new Set ( ) ;
2359 }
2460
2561 /**
26- * @param { string } family An unknown font family
27- * @returns { boolean } true if the family is valid
62+ * Prevents a family from being overridden by a custom font. The project may still use it as a system font.
63+ * @param { string } family
2864 */
29- isValidFamily ( family ) {
65+ restrictFont ( family ) {
66+ if ( ! this . isValidSystemFont ( family ) ) {
67+ throw new Error ( 'Invalid font' ) ;
68+ }
69+
70+ this . restrictedFonts . add ( family . toLowerCase ( ) ) ;
71+
72+ const oldLength = this . fonts . length ;
73+ this . fonts = this . fonts . filter ( font => font . system || this . isValidCustomFont ( font . family ) ) ;
74+ if ( this . fonts . length !== oldLength ) {
75+ this . updateRenderer ( ) ;
76+ this . changed ( ) ;
77+ }
78+ }
79+
80+ /**
81+ * @param {string } family Untrusted font name input
82+ * @returns {boolean } true if the family is valid for a system font
83+ */
84+ isValidSystemFont ( family ) {
3085 return / ^ [ - \w ] + $ / . test ( family ) ;
3186 }
3287
3388 /**
34- * @param {string } family
35- * @returns {boolean }
89+ * @param {string } family Untrusted font name input
90+ * @returns {boolean } true if the family is valid for a custom font
3691 */
37- hasFont ( family ) {
38- return ! ! this . fonts . find ( i => i . family === family ) ;
92+ isValidCustomFont ( family ) {
93+ return / ^ [ - \w ] + $ / . test ( family ) && ! this . restrictedFonts . has ( family . toLowerCase ( ) ) ;
94+ }
95+
96+ /**
97+ * @deprecated only exists for extension compatibility, use isValidSystemFont or isValidCustomFont instead
98+ */
99+ isValidFamily ( family ) {
100+ return this . isValidSystemFont ( family ) && this . isValidCustomFont ( family ) ;
101+ }
102+
103+ /**
104+ * @param {string } family Untrusted font name input
105+ * @returns {string }
106+ */
107+ getUnusedSystemFont ( family ) {
108+ return StringUtil . caseInsensitiveUnusedName (
109+ removeInvalidCharacters ( family ) ,
110+ this . fonts . map ( i => i . family )
111+ ) ;
112+ }
113+
114+ /**
115+ * @param {string } family Untrusted font name input
116+ * @returns {string }
117+ */
118+ getUnusedCustomFont ( family ) {
119+ return StringUtil . caseInsensitiveUnusedName (
120+ removeInvalidCharacters ( family ) ,
121+ [
122+ ...this . fonts . map ( i => i . family ) ,
123+ ...this . restrictedFonts
124+ ]
125+ ) ;
39126 }
40127
41128 /**
42129 * @param {string } family
43130 * @returns {boolean }
44131 */
45- getSafeName ( family ) {
46- family = family . replace ( / [ ^ - \w ] / g, '' ) ;
47- return StringUtil . unusedName ( family , this . fonts . map ( i => i . family ) ) ;
132+ hasFont ( family ) {
133+ return ! ! this . fonts . find ( i => i . family . toLowerCase ( ) === family . toLowerCase ( ) ) ;
48134 }
49135
50136 changed ( ) {
@@ -56,14 +142,17 @@ class FontManager extends EventEmitter {
56142 * @param {string } fallback
57143 */
58144 addSystemFont ( family , fallback ) {
59- if ( ! this . isValidFamily ( family ) ) {
60- throw new Error ( 'Invalid family' ) ;
145+ if ( ! this . isValidSystemFont ( family ) ) {
146+ throw new Error ( 'Invalid system font family' ) ;
61147 }
62- this . fonts . push ( {
148+ const oldFont = addOrUpdateFont ( this . fonts , {
63149 system : true ,
64150 family,
65151 fallback
66152 } ) ;
153+ if ( oldFont && ! oldFont . system ) {
154+ this . updateRenderer ( ) ;
155+ }
67156 this . changed ( ) ;
68157 }
69158
@@ -73,17 +162,15 @@ class FontManager extends EventEmitter {
73162 * @param {Asset } asset scratch-storage asset
74163 */
75164 addCustomFont ( family , fallback , asset ) {
76- if ( ! this . isValidFamily ( family ) ) {
77- throw new Error ( 'Invalid family' ) ;
165+ if ( ! this . isValidCustomFont ( family ) ) {
166+ throw new Error ( 'Invalid custom font family' ) ;
78167 }
79-
80- this . fonts . push ( {
168+ addOrUpdateFont ( this . fonts , {
81169 system : false ,
82170 family,
83171 fallback,
84172 asset
85173 } ) ;
86-
87174 this . updateRenderer ( ) ;
88175 this . changed ( ) ;
89176 }
0 commit comments