115 } |
115 } |
116 |
116 |
117 /** |
117 /** |
118 * Checks if two sets of Emoji characters render the same visually. |
118 * Checks if two sets of Emoji characters render the same visually. |
119 * |
119 * |
|
120 * This is used to determine if the browser is rendering an emoji with multiple data points |
|
121 * correctly. set1 is the emoji in the correct form, using a zero-width joiner. set2 is the emoji |
|
122 * in the incorrect form, using a zero-width space. If the two sets render the same, then the browser |
|
123 * does not support the emoji correctly. |
|
124 * |
120 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing |
125 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing |
121 * scope. Everything must be passed by parameters. |
126 * scope. Everything must be passed by parameters. |
122 * |
127 * |
123 * @since 4.9.0 |
128 * @since 4.9.0 |
124 * |
129 * |
159 return rendered2Data === rendered2[ index ]; |
164 return rendered2Data === rendered2[ index ]; |
160 } ); |
165 } ); |
161 } |
166 } |
162 |
167 |
163 /** |
168 /** |
|
169 * Checks if the center point of a single emoji is empty. |
|
170 * |
|
171 * This is used to determine if the browser is rendering an emoji with a single data point |
|
172 * correctly. The center point of an incorrectly rendered emoji will be empty. A correctly |
|
173 * rendered emoji will have a non-zero value at the center point. |
|
174 * |
|
175 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing |
|
176 * scope. Everything must be passed by parameters. |
|
177 * |
|
178 * @since 6.8.2 |
|
179 * |
|
180 * @private |
|
181 * |
|
182 * @param {CanvasRenderingContext2D} context 2D Context. |
|
183 * @param {string} emoji Emoji to test. |
|
184 * |
|
185 * @return {boolean} True if the center point is empty. |
|
186 */ |
|
187 function emojiRendersEmptyCenterPoint( context, emoji ) { |
|
188 // Cleanup from previous test. |
|
189 context.clearRect( 0, 0, context.canvas.width, context.canvas.height ); |
|
190 context.fillText( emoji, 0, 0 ); |
|
191 |
|
192 // Test if the center point (16, 16) is empty (0,0,0,0). |
|
193 var centerPoint = context.getImageData(16, 16, 1, 1); |
|
194 for ( var i = 0; i < centerPoint.data.length; i++ ) { |
|
195 if ( centerPoint.data[ i ] !== 0 ) { |
|
196 // Stop checking the moment it's known not to be empty. |
|
197 return false; |
|
198 } |
|
199 } |
|
200 |
|
201 return true; |
|
202 } |
|
203 |
|
204 /** |
164 * Determines if the browser properly renders Emoji that Twemoji can supplement. |
205 * Determines if the browser properly renders Emoji that Twemoji can supplement. |
165 * |
206 * |
166 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing |
207 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing |
167 * scope. Everything must be passed by parameters. |
208 * scope. Everything must be passed by parameters. |
168 * |
209 * |
171 * @private |
212 * @private |
172 * |
213 * |
173 * @param {CanvasRenderingContext2D} context 2D Context. |
214 * @param {CanvasRenderingContext2D} context 2D Context. |
174 * @param {string} type Whether to test for support of "flag" or "emoji". |
215 * @param {string} type Whether to test for support of "flag" or "emoji". |
175 * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification. |
216 * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification. |
|
217 * @param {Function} emojiRendersEmptyCenterPoint Reference to emojiRendersEmptyCenterPoint function, needed due to minification. |
176 * |
218 * |
177 * @return {boolean} True if the browser can render emoji, false if it cannot. |
219 * @return {boolean} True if the browser can render emoji, false if it cannot. |
178 */ |
220 */ |
179 function browserSupportsEmoji( context, type, emojiSetsRenderIdentically ) { |
221 function browserSupportsEmoji( context, type, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ) { |
180 var isIdentical; |
222 var isIdentical; |
181 |
223 |
182 switch ( type ) { |
224 switch ( type ) { |
183 case 'flag': |
225 case 'flag': |
184 /* |
226 /* |
196 if ( isIdentical ) { |
238 if ( isIdentical ) { |
197 return false; |
239 return false; |
198 } |
240 } |
199 |
241 |
200 /* |
242 /* |
201 * Test for UN flag compatibility. This is the least supported of the letter locale flags, |
243 * Test for Sark flag compatibility. This is the least supported of the letter locale flags, |
202 * so gives us an easy test for full support. |
244 * so gives us an easy test for full support. |
203 * |
245 * |
204 * To test for support, we try to render it, and compare the rendering to how it would look if |
246 * To test for support, we try to render it, and compare the rendering to how it would look if |
205 * the browser doesn't render it correctly ([U] + [N]). |
247 * the browser doesn't render it correctly ([C] + [Q]). |
206 */ |
248 */ |
207 isIdentical = emojiSetsRenderIdentically( |
249 isIdentical = emojiSetsRenderIdentically( |
208 context, |
250 context, |
209 '\uD83C\uDDFA\uD83C\uDDF3', // as the sequence of two code points |
251 '\uD83C\uDDE8\uD83C\uDDF6', // as the sequence of two code points |
210 '\uD83C\uDDFA\u200B\uD83C\uDDF3' // as the two code points separated by a zero-width space |
252 '\uD83C\uDDE8\u200B\uD83C\uDDF6' // as the two code points separated by a zero-width space |
211 ); |
253 ); |
212 |
254 |
213 if ( isIdentical ) { |
255 if ( isIdentical ) { |
214 return false; |
256 return false; |
215 } |
257 } |
230 ); |
272 ); |
231 |
273 |
232 return ! isIdentical; |
274 return ! isIdentical; |
233 case 'emoji': |
275 case 'emoji': |
234 /* |
276 /* |
235 * Four and twenty blackbirds baked in a pie. |
277 * Does Emoji 16.0 cause the browser to go splat? |
236 * |
278 * |
237 * To test for Emoji 15.0 support, try to render a new emoji: Blackbird. |
279 * To test for Emoji 16.0 support, try to render a new emoji: Splatter. |
238 * |
280 * |
239 * The Blackbird is a ZWJ sequence combining 🐦 Bird and ⬛ large black square., |
281 * The splatter emoji is a single code point emoji. Testing for browser support |
240 * |
282 * required testing the center point of the emoji to see if it is empty. |
241 * 0x1F426 (\uD83D\uDC26) == Bird |
283 * |
242 * 0x200D == Zero-Width Joiner (ZWJ) that links the code points for the new emoji or |
284 * 0xD83E 0xDEDF (\uD83E\uDEDF) == Splatter. |
243 * 0x200B == Zero-Width Space (ZWS) that is rendered for clients not supporting the new emoji. |
285 * |
244 * 0x2B1B == Large Black Square |
286 * When updating this test, please ensure that the emoji is either a single code point |
245 * |
287 * or switch to using the emojiSetsRenderIdentically function and testing with a zero-width |
246 * When updating this test for future Emoji releases, ensure that individual emoji that make up the |
288 * joiner vs a zero-width space. |
247 * sequence come from older emoji standards. |
|
248 */ |
289 */ |
249 isIdentical = emojiSetsRenderIdentically( |
290 var notSupported = emojiRendersEmptyCenterPoint( context, '\uD83E\uDEDF' ); |
250 context, |
291 return ! notSupported; |
251 '\uD83D\uDC26\u200D\u2B1B', // as the zero-width joiner sequence |
|
252 '\uD83D\uDC26\u200B\u2B1B' // separated by a zero-width space |
|
253 ); |
|
254 |
|
255 return ! isIdentical; |
|
256 } |
292 } |
257 |
293 |
258 return false; |
294 return false; |
259 } |
295 } |
260 |
296 |
269 * @private |
305 * @private |
270 * |
306 * |
271 * @param {string[]} tests Tests. |
307 * @param {string[]} tests Tests. |
272 * @param {Function} browserSupportsEmoji Reference to browserSupportsEmoji function, needed due to minification. |
308 * @param {Function} browserSupportsEmoji Reference to browserSupportsEmoji function, needed due to minification. |
273 * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification. |
309 * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification. |
|
310 * @param {Function} emojiRendersEmptyCenterPoint Reference to emojiRendersEmptyCenterPoint function, needed due to minification. |
274 * |
311 * |
275 * @return {SupportTests} Support tests. |
312 * @return {SupportTests} Support tests. |
276 */ |
313 */ |
277 function testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically ) { |
314 function testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ) { |
278 var canvas; |
315 var canvas; |
279 if ( |
316 if ( |
280 typeof WorkerGlobalScope !== 'undefined' && |
317 typeof WorkerGlobalScope !== 'undefined' && |
281 self instanceof WorkerGlobalScope |
318 self instanceof WorkerGlobalScope |
282 ) { |
319 ) { |
295 context.textBaseline = 'top'; |
332 context.textBaseline = 'top'; |
296 context.font = '600 32px Arial'; |
333 context.font = '600 32px Arial'; |
297 |
334 |
298 var supports = {}; |
335 var supports = {}; |
299 tests.forEach( function ( test ) { |
336 tests.forEach( function ( test ) { |
300 supports[ test ] = browserSupportsEmoji( context, test, emojiSetsRenderIdentically ); |
337 supports[ test ] = browserSupportsEmoji( context, test, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ); |
301 } ); |
338 } ); |
302 return supports; |
339 return supports; |
303 } |
340 } |
304 |
341 |
305 /** |
342 /** |
348 testEmojiSupports.toString() + |
385 testEmojiSupports.toString() + |
349 '(' + |
386 '(' + |
350 [ |
387 [ |
351 JSON.stringify( tests ), |
388 JSON.stringify( tests ), |
352 browserSupportsEmoji.toString(), |
389 browserSupportsEmoji.toString(), |
353 emojiSetsRenderIdentically.toString() |
390 emojiSetsRenderIdentically.toString(), |
|
391 emojiRendersEmptyCenterPoint.toString() |
354 ].join( ',' ) + |
392 ].join( ',' ) + |
355 '));'; |
393 '));'; |
356 var blob = new Blob( [ workerScript ], { |
394 var blob = new Blob( [ workerScript ], { |
357 type: 'text/javascript' |
395 type: 'text/javascript' |
358 } ); |
396 } ); |
365 }; |
403 }; |
366 return; |
404 return; |
367 } catch ( e ) {} |
405 } catch ( e ) {} |
368 } |
406 } |
369 |
407 |
370 supportTests = testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically ); |
408 supportTests = testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ); |
371 setSessionSupportTests( supportTests ); |
409 setSessionSupportTests( supportTests ); |
372 resolve( supportTests ); |
410 resolve( supportTests ); |
373 } ) |
411 } ) |
374 // Once the browser emoji support has been obtained from the session, finalize the settings. |
412 // Once the browser emoji support has been obtained from the session, finalize the settings. |
375 .then( function ( supportTests ) { |
413 .then( function ( supportTests ) { |