Skip to content

Commit dc64170

Browse files
committed
Fix and improve handling of Skin Tone Modifiers:
- Fix that modifiers were ignored when not part of a larger sequence #29 - Only check for valid base characters when in Emoji level is RGI - Improve docs and specs
1 parent 893f9a9 commit dc64170

File tree

5 files changed

+40
-10
lines changed

5 files changed

+40
-10
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
## 3.1.4
4+
5+
- Fix that skin tone modifiers were ignored when used in a non-ZWJ sequence
6+
context (= single emoji char + modifier) #29
7+
- Add more docs and specs about modifier handling
8+
39
## 3.1.3
410

511
Better handling of non-UTF-8 strings, patch by @Earlopain:

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,16 @@ There are many Emoji which get constructed by combining other Emoji in a sequenc
101101

102102
Another aspect where terminals disagree is whether Emoji characters which have a text presentation by default (width 1) should be turned into full-width (width 2) when combined with Variation Selector 16 (*U+FEOF*).
103103

104+
Finally, it varies if Skin Tone Modifiers can be applied to all characters or just to those with the "Emoji Base" property.
105+
104106
Emoji Type | Width / Comment
105107
------------|----------------
106-
Basic/Single Emoji character without Variation Selector | No special handling
107-
Basic/Single Emoji character with VS15 (Text) | No special handling
108-
Basic/Single Emoji character with VS16 (Emoji) | 2 or East Asian Width (see table below)
109-
Emoji Sequence | 2 if Emoji belongs to configured Emoji set (see table below)
108+
Basic/Single Emoji character without Variation Selector | No special handling
109+
Basic/Single Emoji character with VS15 (Text) | No special handling
110+
Basic/Single Emoji character with VS16 (Emoji) | 2 or East Asian Width (see table below)
111+
Single Emoji character with Skin Tone Modifier | 2
112+
Skin Tone Modifier used in isolation or with invalid base | 2 if Emoji mode is configured to RGI
113+
Emoji Sequence | 2 if Emoji belongs to configured Emoji set (see table below)
110114

111115
#### Emoji Modes
112116

lib/unicode/display_width.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ class DisplayWidth
4242
),
4343
Unicode::Emoji::REGEX_EMOJI_KEYCAP
4444
)
45-
REGEX_EMOJI_ALL_SEQUENCES = Regexp.union(/.[\u{1F3FB}-\u{1F3FF}\u{FE0F}]?(\u{200D}.[\u{1F3FB}-\u{1F3FF}\u{FE0F}]?)+/, Unicode::Emoji::REGEX_EMOJI_KEYCAP)
45+
46+
# ebase = Unicode::Emoji::REGEX_PROP_MODIFIER_BASE.source
47+
REGEX_EMOJI_ALL_SEQUENCES = Regexp.union(/.[\u{1F3FB}-\u{1F3FF}\u{FE0F}]?(\u{200D}.[\u{1F3FB}-\u{1F3FF}\u{FE0F}]?)+|.[\u{1F3FB}-\u{1F3FF}]/, Unicode::Emoji::REGEX_EMOJI_KEYCAP)
4648
REGEX_EMOJI_ALL_SEQUENCES_AND_VS16 = Regexp.union(REGEX_EMOJI_ALL_SEQUENCES, REGEX_EMOJI_VS16)
4749

4850
# Returns monospace display width of string

misc/terminal-emoji-width.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@
1111
puts
1212
puts RULER + "⛹️" + ABC
1313

14+
puts "1C) BASE EMOJI CHARACTER + MODIFIER"
15+
puts
16+
puts RULER + "🏃🏽" + ABC
17+
18+
puts "1D) MODIFIER IN ISOLATION"
19+
puts
20+
puts RULER + "Z🏽" + ABC
21+
1422
puts "2) RGI EMOJI SEQ"
1523
puts
1624
puts RULER + "🏃🏼‍♀‍➡" + ABC

spec/display_width_spec.rb

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,6 @@
221221
end
222222

223223
describe '(special emoji / emoji sequences)' do
224-
it 'works with singleton skin tone modifiers: width 2' do
225-
expect( "🏿".display_width(emoji: :all) ).to eq 2
226-
end
227-
228224
it 'works with flags: width 2' do
229225
expect( "🇵🇹".display_width(emoji: :all) ).to eq 2
230226
end
@@ -239,8 +235,12 @@
239235
end
240236

241237
describe '(modifiers and zwj sequences)' do
238+
it 'applies simple skin tone modifiers' do
239+
expect( "👏🏽".display_width(emoji: :rgi) ).to eq 2
240+
end
241+
242242
it 'counts RGI Emoji ZWJ sequence as width 2' do
243-
expect( "🤾🏽‍♀️".display_width(1, emoji: :rgi) ).to eq 2
243+
expect( "🤾🏽‍♀️".display_width(emoji: :rgi) ).to eq 2
244244
end
245245

246246
it 'works for emoji involving characters which are east asian ambiguous' do
@@ -253,6 +253,7 @@
253253
it 'does no Emoji adjustments when emoji suport is disabled' do
254254
expect( "🤾🏽‍♀️".display_width(emoji: false) ).to eq 5
255255
expect( "❣️".display_width(emoji: :none) ).to eq 1
256+
expect( "👏🏽".display_width(emoji: :none) ).to eq 4
256257
end
257258
end
258259

@@ -277,6 +278,8 @@
277278
expect( "🤾🏽‍♀️".display_width(emoji: :rgi) ).to eq 2 # FQE
278279
expect( "🤾🏽‍♀".display_width(emoji: :rgi) ).to eq 2 # MQE
279280
expect( "❤‍🩹".display_width(emoji: :rgi) ).to eq 2 # UQE
281+
expect( "👏🏽".display_width(emoji: :rgi) ).to eq 2 # Modifier
282+
expect( "J🏽".display_width(emoji: :rgi) ).to eq 3 # Modifier with invalid base
280283
expect( "🤠‍🤢".display_width(emoji: :rgi) ).to eq 4 # Non-RGI/well-formed
281284
expect( "🚄🏾‍▶️".display_width(emoji: :rgi) ).to eq 6 # Invalid/non-Emoji sequence
282285
end
@@ -308,6 +311,8 @@
308311
expect( "🤾🏽‍♀️".display_width(emoji: :possible) ).to eq 2 # FQE
309312
expect( "🤾🏽‍♀".display_width(emoji: :possible) ).to eq 2 # MQE
310313
expect( "❤‍🩹".display_width(emoji: :possible) ).to eq 2 # UQE
314+
expect( "👏🏽".display_width(emoji: :possible) ).to eq 2 # Modifier
315+
expect( "J🏽".display_width(emoji: :possible) ).to eq 3 # Modifier with invalid base
311316
expect( "🤠‍🤢".display_width(emoji: :possible) ).to eq 2 # Non-RGI/well-formed
312317
expect( "🚄🏾‍▶️".display_width(emoji: :possible) ).to eq 6 # Invalid/non-Emoji sequence
313318
end
@@ -322,6 +327,9 @@
322327
expect( "🤾🏽‍♀️".display_width(emoji: :all) ).to eq 2 # FQE
323328
expect( "🤾🏽‍♀".display_width(emoji: :all) ).to eq 2 # MQE
324329
expect( "❤‍🩹".display_width(emoji: :all) ).to eq 2 # UQE
330+
expect( "👏🏽".display_width(emoji: :all) ).to eq 2 # Modifier
331+
expect( "👏🏽".display_width(emoji: :all) ).to eq 2 # Modifier
332+
expect( "J🏽".display_width(emoji: :all) ).to eq 2 # Modifier with invalid base
325333
expect( "🤠‍🤢".display_width(emoji: :all) ).to eq 2 # Non-RGI/well-formed
326334
expect( "🚄🏾‍▶️".display_width(emoji: :all) ).to eq 2 # Invalid/non-Emoji sequence
327335
end
@@ -336,6 +344,8 @@
336344
expect( "🤾🏽‍♀️".display_width(emoji: :all_no_vs16) ).to eq 2 # FQE
337345
expect( "🤾🏽‍♀".display_width(emoji: :all_no_vs16) ).to eq 2 # MQE
338346
expect( "❤‍🩹".display_width(emoji: :all_no_vs16) ).to eq 2 # UQE
347+
expect( "👏🏽".display_width(emoji: :all_no_vs16) ).to eq 2 # Modifier
348+
expect( "J🏽".display_width(emoji: :all_no_vs16) ).to eq 2 # Modifier with wrong base
339349
expect( "🤠‍🤢".display_width(emoji: :all_no_vs16) ).to eq 2 # Non-RGI/well-formed
340350
expect( "🚄🏾‍▶️".display_width(emoji: :all_no_vs16) ).to eq 2 # Invalid/non-Emoji sequence
341351
end

0 commit comments

Comments
 (0)