I need to find out whether a character in a string is an emoji.
For example, I have this character:
let string = "????"
let character = Array(string)[0]
I need to find out if that character is an emoji.
What I stumbled upon is the difference between characters, unicode scalars and glyphs.
For example, the glyph ๐จโ๐จโ๐งโ๐ง consists of 7 unicode scalars:
Another example, the glyph ๐๐ฟ consists of 2 unicode scalars:
Last one, the glyph 1๏ธโฃ contains three unicode characters:
So when rendering the characters, the resulting glyphs really matter.
Swift 5.0 and above makes this process much easier and gets rid of some guesswork we needed to do. Unicode.Scalar
's new Property
type helps is determine what we're dealing with.
However, those properties only make sense when checking the other scalars within the glyph. This is why we'll be adding some convenience methods to the Character class to help us out.
For more detail, I wrote an article explaining how this works.
For Swift 5.0, it leaves you with the following result:
extension Character {
/// A simple emoji is one scalar and presented to the user as an Emoji
var isSimpleEmoji: Bool {
guard let firstScalar = unicodeScalars.first else { return false }
return firstScalar.properties.isEmoji && firstScalar.value > 0x238C
}
/// Checks if the scalars will be merged into an emoji
var isCombinedIntoEmoji: Bool { unicodeScalars.count > 1 && unicodeScalars.first?.properties.isEmoji ?? false }
var isEmoji: Bool { isSimpleEmoji || isCombinedIntoEmoji }
}
extension String {
var isSingleEmoji: Bool { count == 1 && containsEmoji }
var containsEmoji: Bool { contains { $0.isEmoji } }
var containsOnlyEmoji: Bool { !isEmpty && !contains { !$0.isEmoji } }
var emojiString: String { emojis.map { String($0) }.reduce("", +) }
var emojis: [Character] { filter { $0.isEmoji } }
var emojiScalars: [UnicodeScalar] { filter { $0.isEmoji }.flatMap { $0.unicodeScalars } }
}
Which will give you the following results:
"Aฬอฬ".containsEmoji // false
"3".containsEmoji // false
"Aฬอฬโถ๏ธ".unicodeScalars // [65, 795, 858, 790, 9654, 65039]
"Aฬอฬโถ๏ธ".emojiScalars // [9654, 65039]
"3๏ธโฃ".isSingleEmoji // true
"3๏ธโฃ".emojiScalars // [51, 65039, 8419]
"๐๐ฟ".isSingleEmoji // true
"๐๐ผโโ๏ธ".isSingleEmoji // true
"๐น๐ฉ".isSingleEmoji // true
"โฐ".isSingleEmoji // true
"๐ถ".isSingleEmoji // true
"๐จโ๐ฉโ๐งโ๐ง".isSingleEmoji // true
"๐ด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ".isSingleEmoji // true
"๐ด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ".containsOnlyEmoji // true
"๐จโ๐ฉโ๐งโ๐ง".containsOnlyEmoji // true
"Hello ๐จโ๐ฉโ๐งโ๐ง".containsOnlyEmoji // false
"Hello ๐จโ๐ฉโ๐งโ๐ง".containsEmoji // true
"๐ซ Hรฉllo ๐จโ๐ฉโ๐งโ๐ง".emojiString // "๐ซ๐จโ๐ฉโ๐งโ๐ง"
"๐จโ๐ฉโ๐งโ๐ง".count // 1
"๐ซ Hรฉllล ๐จโ๐ฉโ๐งโ๐ง".emojiScalars // [128107, 128104, 8205, 128105, 8205, 128103, 8205, 128103]
"๐ซ Hรฉllล ๐จโ๐ฉโ๐งโ๐ง".emojis // ["๐ซ", "๐จโ๐ฉโ๐งโ๐ง"]
"๐ซ Hรฉllล ๐จโ๐ฉโ๐งโ๐ง".emojis.count // 2
"๐ซ๐จโ๐ฉโ๐งโ๐ง๐จโ๐จโ๐ฆ".isSingleEmoji // false
"๐ซ๐จโ๐ฉโ๐งโ๐ง๐จโ๐จโ๐ฆ".containsOnlyEmoji // true
For older Swift versions, check out this gist containing my old code.
The simplest, cleanest, and swiftiest way to accomplish this is to simply check the Unicode code points for each character in the string against known emoji and dingbats ranges, like so:
extension String {
var containsEmoji: Bool {
for scalar in unicodeScalars {
switch scalar.value {
case 0x1F600...0x1F64F, // Emoticons
0x1F300...0x1F5FF, // Misc Symbols and Pictographs
0x1F680...0x1F6FF, // Transport and Map
0x2600...0x26FF, // Misc symbols
0x2700...0x27BF, // Dingbats
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs
0x1F1E6...0x1F1FF: // Flags
return true
default:
continue
}
}
return false
}
}
โฆ introduced a new way of checking exactly this!
You have to break your String
into its Scalars
. Each Scalar
has a Property
value which supports the isEmoji
value!
Actually you can even check if the Scalar is a Emoji modifier or more. Check out Apple's documentation: https://developer.apple.com/documentation/swift/unicode/scalar/properties
You may want to consider checking for isEmojiPresentation
instead of isEmoji
, because Apple states the following for isEmoji
:
This property is true for scalars that are rendered as emoji by default and also for scalars that have a non-default emoji rendering when followed by U+FE0F VARIATION SELECTOR-16. This includes some scalars that are not typically considered to be emoji.
This way actually splits up Emoji's into all the modifiers, but it is way simpler to handle. And as Swift now counts Emoji's with modifiers (e.g.: ๐จโ๐ฉโ๐งโ๐ฆ, ๐จ๐ปโ๐ป, ๐ด) as 1 you can do all kind of stuff.
var string = "๐ค test"
for scalar in string.unicodeScalars {
let isEmoji = scalar.properties.isEmoji
print("\(scalar.description) \(isEmoji)"))
}
// ๐ค true
// false
// t false
// e false
// s false
// t false
NSHipster points out an interesting way to get all Emoji's:
import Foundation
var emoji = CharacterSet()
for codePoint in 0x0000...0x1F0000 {
guard let scalarValue = Unicode.Scalar(codePoint) else {
continue
}
// Implemented in Swift 5 (SE-0221)
// https://github.com/apple/swift-evolution/blob/master/proposals/0221-character-properties.md
if scalarValue.properties.isEmoji {
emoji.insert(scalarValue)
}
}
With Swift 5 you can now inspect the unicode properties of each character in your string. This gives us the convenient isEmoji
variable on each letter. The problem is isEmoji
will return true for any character that can be converted into a 2-byte emoji, such as 0-9.
We can look at the variable isEmoji
and also check the for the presence of an emoji modifier to determine if the ambiguous characters will display as an emoji.
This solution should be much more future proof than the regex solutions offered here.
extension String {
func containsOnlyEmojis() -> Bool {
if count == 0 {
return false
}
for character in self {
if !character.isEmoji {
return false
}
}
return true
}
func containsEmoji() -> Bool {
for character in self {
if character.isEmoji {
return true
}
}
return false
}
}
extension Character {
// An emoji can either be a 2 byte unicode character or a normal UTF8 character with an emoji modifier
// appended as is the case with 3๏ธโฃ. 0x238C is the first instance of UTF16 emoji that requires no modifier.
// `isEmoji` will evaluate to true for any character that can be turned into an emoji by adding a modifier
// such as the digit "3". To avoid this we confirm that any character below 0x238C has an emoji modifier attached
var isEmoji: Bool {
guard let scalar = unicodeScalars.first else { return false }
return scalar.properties.isEmoji && (scalar.value > 0x238C || unicodeScalars.count > 1)
}
}
Giving us
"hey".containsEmoji() //false
"Hello World ๐".containsEmoji() //true
"Hello World ๐".containsOnlyEmojis() //false
"3".containsEmoji() //false
"3๏ธโฃ".containsEmoji() //true
extension String {
func containsEmoji() -> Bool {
for scalar in unicodeScalars {
switch scalar.value {
case 0x3030, 0x00AE, 0x00A9,// Special Characters
0x1D000...0x1F77F, // Emoticons
0x2100...0x27BF, // Misc symbols and Dingbats
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF: // Supplemental Symbols and Pictographs
return true
default:
continue
}
}
return false
}
}
This is my fix, with updated ranges.
Swift 3 Note:
It appears the cnui_containsEmojiCharacters
method has either been removed or moved to a different dynamic library. _containsEmoji
should still work though.
let str: NSString = "hello๐"
@objc protocol NSStringPrivate {
func _containsEmoji() -> ObjCBool
}
let strPrivate = unsafeBitCast(str, to: NSStringPrivate.self)
strPrivate._containsEmoji() // true
str.value(forKey: "_containsEmoji") // 1
let swiftStr = "hello๐"
(swiftStr as AnyObject).value(forKey: "_containsEmoji") // 1
Swift 2.x:
I recently discovered a private API on NSString
which exposes functionality for detecting if a string contains an Emoji character:
let str: NSString = "hello๐"
With an objc protocol and unsafeBitCast
:
@objc protocol NSStringPrivate {
func cnui_containsEmojiCharacters() -> ObjCBool
func _containsEmoji() -> ObjCBool
}
let strPrivate = unsafeBitCast(str, NSStringPrivate.self)
strPrivate.cnui_containsEmojiCharacters() // true
strPrivate._containsEmoji() // true
With valueForKey
:
str.valueForKey("cnui_containsEmojiCharacters") // 1
str.valueForKey("_containsEmoji") // 1
With a pure Swift string, you must cast the string as AnyObject
before using valueForKey
:
let str = "hello๐"
(str as AnyObject).valueForKey("cnui_containsEmojiCharacters") // 1
(str as AnyObject).valueForKey("_containsEmoji") // 1
Methods found in the NSString header file.
You can use this code example or this pod.
To use it in Swift, import the category into the YourProject_Bridging_Header
#import "NSString+EMOEmoji.h"
Then you can check the range for every emoji in your String:
let example: NSString = "string๐จโ๐จโ๐งโ๐งwith๐emojisโ๐ฟ" //string with emojis
let containsEmoji: Bool = example.emo_containsEmoji()
print(containsEmoji)
// Output: ["true"]
Over the years these emoji-detecting solutions keep breaking as Apple adds new emojis w/ new methods (like skin-toned emojis built by pre-cursing a character with an additional character), etc.
I finally broke down and just wrote the following method which works for all current emojis and should work for all future emojis.
The solution creates a UILabel with the character and a black background. CG then takes a snapshot of the label and I scan all pixels in the snapshot for any non solid-black pixels. The reason I add the black background is to avoid issues of false-coloring due to Subpixel Rendering
The solution runs VERY fast on my device, I can check hundreds of characters a second, but it should be noted that this is a CoreGraphics solution and should not be used heavily like you could with a regular text method. Graphics processing is data heavy so checking thousands of characters at once could result in noticeable lag.
-(BOOL)isEmoji:(NSString *)character {
UILabel *characterRender = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
characterRender.text = character;
characterRender.font = [UIFont fontWithName:@"AppleColorEmoji" size:12.0f];//Note: Size 12 font is likely not crucial for this and the detector will probably still work at an even smaller font size, so if you needed to speed this checker up for serious performance you may test lowering this to a font size like 6.0
characterRender.backgroundColor = [UIColor blackColor];//needed to remove subpixel rendering colors
[characterRender sizeToFit];
CGRect rect = [characterRender bounds];
UIGraphicsBeginImageContextWithOptions(rect.size,YES,0.0f);
CGContextRef contextSnap = UIGraphicsGetCurrentContext();
[characterRender.layer renderInContext:contextSnap];
UIImage *capturedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRef imageRef = [capturedImage CGImage];
NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
NSUInteger bytesPerPixel = 4;//Note: Alpha Channel not really needed, if you need to speed this up for serious performance you can refactor this pixel scanner to just RGB
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
BOOL colorPixelFound = NO;
int x = 0;
int y = 0;
while (y < height && !colorPixelFound) {
while (x < width && !colorPixelFound) {
NSUInteger byteIndex = (bytesPerRow * y) + x * bytesPerPixel;
CGFloat red = (CGFloat)rawData[byteIndex];
CGFloat green = (CGFloat)rawData[byteIndex+1];
CGFloat blue = (CGFloat)rawData[byteIndex+2];
CGFloat h, s, b, a;
UIColor *c = [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];
[c getHue:&h saturation:&s brightness:&b alpha:&a];//Note: I wrote this method years ago, can't remember why I check HSB instead of just checking r,g,b==0; Upon further review this step might not be needed, but I haven't tested to confirm yet.
b /= 255.0f;
if (b > 0) {
colorPixelFound = YES;
}
x++;
}
x=0;
y++;
}
return colorPixelFound;
}
For Swift 3.0.2, the following answer is the simplest one:
class func stringContainsEmoji (string : NSString) -> Bool
{
var returnValue: Bool = false
string.enumerateSubstrings(in: NSMakeRange(0, (string as NSString).length), options: NSString.EnumerationOptions.byComposedCharacterSequences) { (substring, substringRange, enclosingRange, stop) -> () in
let objCString:NSString = NSString(string:substring!)
let hs: unichar = objCString.character(at: 0)
if 0xd800 <= hs && hs <= 0xdbff
{
if objCString.length > 1
{
let ls: unichar = objCString.character(at: 1)
let step1: Int = Int((hs - 0xd800) * 0x400)
let step2: Int = Int(ls - 0xdc00)
let uc: Int = Int(step1 + step2 + 0x10000)
if 0x1d000 <= uc && uc <= 0x1f77f
{
returnValue = true
}
}
}
else if objCString.length > 1
{
let ls: unichar = objCString.character(at: 1)
if ls == 0x20e3
{
returnValue = true
}
}
else
{
if 0x2100 <= hs && hs <= 0x27ff
{
returnValue = true
}
else if 0x2b05 <= hs && hs <= 0x2b07
{
returnValue = true
}
else if 0x2934 <= hs && hs <= 0x2935
{
returnValue = true
}
else if 0x3297 <= hs && hs <= 0x3299
{
returnValue = true
}
else if hs == 0xa9 || hs == 0xae || hs == 0x303d || hs == 0x3030 || hs == 0x2b55 || hs == 0x2b1c || hs == 0x2b1b || hs == 0x2b50
{
returnValue = true
}
}
}
return returnValue;
}
The absolutely similar answer to those that wrote before me, but with updated set of emoji scalars.
extension String {
func isContainEmoji() -> Bool {
let isContain = unicodeScalars.first(where: { $0.isEmoji }) != nil
return isContain
}
}
extension UnicodeScalar {
var isEmoji: Bool {
switch value {
case 0x1F600...0x1F64F,
0x1F300...0x1F5FF,
0x1F680...0x1F6FF,
0x1F1E6...0x1F1FF,
0x2600...0x26FF,
0x2700...0x27BF,
0xFE00...0xFE0F,
0x1F900...0x1F9FF,
65024...65039,
8400...8447,
9100...9300,
127000...127600:
return true
default:
return false
}
}
}
There is a nice solution for the mentioned task. But Checking Unicode.Scalar.Properties of unicode scalars is good for a single Character. And not flexible enough for Strings.
We can use Regular Expressions instead โ more universal approach. There is a detailed description of how it works below. And here goes the solution.
In Swift you can check, whether a String is a single Emoji character, using an extension with such a computed property:
extension String {
var isSingleEmoji : Bool {
if self.count == 1 {
let emodjiGlyphPattern = "\\p{RI}{2}|(\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})|[\\p{Emoji}&&\\p{Other_symbol}])(\\x{200D}(\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})|[\\p{Emoji}&&\\p{Other_symbol}]))*"
let fullRange = NSRange(location: 0, length: self.utf16.count)
if let regex = try? NSRegularExpression(pattern: emodjiGlyphPattern, options: .caseInsensitive) {
let regMatches = regex.matches(in: self, options: NSRegularExpression.MatchingOptions(), range: fullRange)
if regMatches.count > 0 {
// if any range found โ it means, that that single character is emoji
return true
}
}
}
return false
}
}
A single Emoji (a glyph) can be reproduced by a number of different symbols, sequences and their combinations. Unicode specification defines several possible Emoji character representations.
An Emoji character reproduced by a single Unicode Scalar.
Unicode defines Emoji Character as:
emoji_character := \p{Emoji}
But it doesnโt necessarily mean that such a character will be drawn as an Emoji. An ordinary numeric symbol โ1โ has Emoji property being true, though it still might be drawn as text. And there is a list of such symbols: #, ยฉ, 4, etc.
One should think, that we can use additional property to check: โEmoji_Presentationโ. But it doesnโt work like this. There is an Emoji like ๐ or ๐, which have property Emoji_Presentation=false.
To make sure, that the character is drawn as Emoji by default, we should check its category: it should be โOther_symbolโ.
So, in fact regular expression for Single-Character Emoji should be defined as:
emoji_character := \p{Emoji}&&\p{Other_symbol}
A character, which normally can be drawn as either text or as Emoji. Itโs appearance depends on a special following symbol, a presentation selector, which indicates its presentation type. \x{FE0E} defines text representation. \x{FE0F} defines emoji representation.
The list of such symbols can be found [here](โจhttps://unicode.org/Public/emoji/12.1/emoji-variation-sequences.txt).
Unicode defines presentation sequence like this:
emoji_presentation_sequence := emoji_character emoji_presentation_selector
Regular expression sequence for it:
emoji_presentation_sequence := \p{Emoji} \x{FE0F}
The sequence looks very alike with Presentation sequence, but it has additional scalar at the end: \x{20E3}. The scope of possible base scalars used for it is rather narrow: 0-9#* โ and thatโs all. Examples: 1๏ธโฃ, 8๏ธโฃ, *๏ธโฃ.
Unicode defines keycap sequence like this:
emoji_keycap_sequence := [0-9#*] \x{FE0F 20E3}
Regular expression for it:
emoji_keycap_sequence := \p{Emoji} \x{FE0F} \x{FE0F}
Some Emojis can have modified appearance like a skin tone. For example Emoji ๐ง can be different: ๐ง๐ง๐ป๐ง๐ผ๐ง๐ฝ๐ง๐พ๐ง๐ฟ. To define an Emoji, which is called โEmoji_Modifier_Baseโ in this case, one can use a subsequent โEmoji_Modifierโ.
In general such sequence looks like this:
emoji_modifier_sequence := emoji_modifier_base emoji_modifier
To detect it we can search for a regular expression sequence:
emoji_modifier_sequence := \p{Emoji} \p{EMod}
Flags are Emojis with their particular structure. Each flag is represented with two โRegional_Indicatorโ symbols.
Unicode defines them like:
emoji_flag_sequence := regional_indicator regional_indicator
For example flag of Ukraine ๐บ๐ฆ in fact is represented with two scalars: \u{0001F1FA \u{0001F1E6}
Regular expression for it:
emoji_flag_sequence := \p{RI}{2}
A sequence which uses a so-called tag_base, which is followed by a custom tag specification composed from range of symbols \x{E0020}-\x{E007E} and concluded by tag_end mark \x{E007F}.
Unicode defines it like this:
emoji_tag_sequence := tag_base tag_spec tag_end
tag_baseย ย ย ย ย ย ย ย ย ย ย := emoji_character
ย ย ย ย ย ย ย ย ย ย | emoji_modifier_sequence
ย ย ย ย ย ย ย ย ย ย | emoji_presentation_sequence
tag_specย ย ย ย ย ย ย ย ย ย ย := [\x{E0020}-\x{E007E}]+
tag_endย ย ย ย ย ย ย ย ย ย ย ย := \x{E007F}
Strange thing is that Unicode allows tag to be based on emoji_modifier_sequence or emoji_presentation_sequence in ED-14a. But at the same time in regular expressions provided at the same documentation they seem to check the sequence based on a single Emoji character only.
In Unicode 12.1 Emoji list there are only three such Emojis defined. All of them are flags of the UK countries: England ๐ด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ, Scotland ๐ด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ and Wales ๐ด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ. And all of them are based on a single Emoji character. So, weโd better check for such a sequence only.
Regular expression:
\p{Emoji} [\x{E0020}-\x{E007E}]+ \x{E007F}
A zero-width joiner is a scalar \x{200D}. With its help several characters, which are already Emojis by themselves, can be combined into new ones.
For a example a โfamily with father, son and daughterโ Emoji ๐จโ๐งโ๐ฆ is reproduced by a combination of father ๐จ, daughter ๐ง and son ๐ฆ Emojis glued together with ZWJ symbols.
It is allowed to stick together elements, which are Single Emoji characters, Presentation and Modifier sequences.
Regular expression for such sequence in general looks like this:
emoji_zwj_sequence := emoji_zwj_element (\x{200d} emoji_zwj_element )+
All of the mentioned above Emoji representations can be described by a single regular expression:
\p{RI}{2}
| ( \p{Emoji}
( \p{EMod}
| \x{FE0F}\x{20E3}?
| [\x{E0020}-\x{E007E}]+\x{E007F}
)
| โจ[\p{Emoji}&&\p{Other_symbol}]
)
( \x{200D}
( \p{Emoji}
( \p{EMod}
| \x{FE0F}\x{20E3}?
| [\x{E0020}-\x{E007E}]+\x{E007F}
)
| [\p{Emoji}&&\p{Other_symbol}]
)
)*
Using unicode scalar property isEmoji
extension String {
var containsEmoji: Bool {
for scalar in unicodeScalars {
if scalar.properties.isEmoji {
return true
}
}
return false
}
}
How to use
let str = "๐"
print(str.containsEmoji) // true
i had the same problem and ended up making a String
and Character
extensions.
The code is too long to post as it actually lists all emojis (from the official unicode list v5.0) in a CharacterSet
you can find it here:
https://github.com/piterwilson/StringEmoji
Character set containing all known emoji (as described in official Unicode List 5.0 http://unicode.org/emoji/charts-5.0/emoji-list.html)
Whether or not the String
instance represents a known single Emoji character
print("".isEmoji) // false
print("๐".isEmoji) // true
print("๐๐".isEmoji) // false (String is not a single Emoji)
Whether or not the String
instance contains a known Emoji character
print("".containsEmoji) // false
print("๐".containsEmoji) // true
print("๐๐".containsEmoji) // true
Applies a kCFStringTransformToUnicodeName
- CFStringTransform
on a copy of the String
print("รก".unicodeName) // \N{LATIN SMALL LETTER A WITH ACUTE}
print("๐".unicodeName) // "\N{FACE WITH STUCK-OUT TONGUE AND WINKING EYE}"
Returns the result of a kCFStringTransformToUnicodeName
- CFStringTransform
with \N{
prefixes and }
suffixes removed
print("รก".unicodeName) // LATIN SMALL LETTER A WITH ACUTE
print("๐".unicodeName) // FACE WITH STUCK-OUT TONGUE AND WINKING EYE
Whether or not the Character
instance represents a known Emoji character
print("".isEmoji) // false
print("๐".isEmoji) // true
let character = string[string.index(after: string.startIndex)]
orlet secondCharacter = string[string.index(string.startIndex, offsetBy: 1)]
โ Paul B