Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Androidエンジニアが抑えておくべきUnicode Emojiの知識 (DroidKaigi 2019) / Unicode Emoji for Android Engineer DroidKaigi09

tacke
February 07, 2019

Androidエンジニアが抑えておくべきUnicode Emojiの知識 (DroidKaigi 2019) / Unicode Emoji for Android Engineer DroidKaigi09

絵文字がUnicode規格として正式に導入されてからというもの、今や世界中で利用されるなど、テキストコミュニケーションにおいてその役割は年々増加しています。

Unicode Emojiの規格(UTS51)は毎年改定され、その都度新たな文字や規則が追加されます。仕様は複雑化しており、今後もその傾向は続くでしょう。

そのためAndroid OSのバージョン間で差異が発生し、下位互換が必要になります。そのようなUnicode絵文字に関する様々な罠と対処法、メンテナンス性を高めるための設計戦略についてお話します。

https://droidkaigi.jp/2019/timetable/70911/

tacke

February 07, 2019
Tweet

More Decks by tacke

Other Decks in Technology

Transcript

  1. *$6ʹΑΔ&NPKJଐੑͷ൑ఆ import android.icu.lang.UCharacter import android.icu.lang.UProperty val whiteSmilingFace = Character.codePointAt("☺", 0)

    // (int)0x0000263a UCharacter.hasBinaryProperty(whiteSmilingFace, UProperty.EMOJI) // true val smilingFaceWithHalo = Character.codePointAt("", 0) // (int)0x0001f607 UCharacter.hasBinaryProperty(smilingFaceWithHalo, UProperty.EMOJI) // true val hiraganaLetterA = Character.codePointAt("͋", 0) // (int)0x00003042 UCharacter.hasBinaryProperty(hiraganaLetterA, UProperty.EMOJI) // false
  2. &NPKJ1SFTFOUBUJPOଐੑ ❤ 5FYU1SFTFOUBUJPO നࠇ &NPKJ1SFTFOUBUJPO ֆจࣈ Emoji=Yes Emoji_Presentation=No 6 

    6 # Emoji=Yes Emoji_Presentation=Yes σϑΥϧτͷ1SFTFOUBUJPOΛࢦఆ ಉ༷ʹemoji-data.txtʹจࣈΛྻڍ͢Δ͜ͱͰఆٛ
  3. &NPKJ1SFTFOUBUJPOͱҟମࣈηϨΫλ ❤ ⭐ 6  6 # 6 '&' 6

    '&& 74 ʴ ʴ 74 &NPKJ1SFTFOUBUJPO 7BSJBUJPO4FMFDUPS 5FYU1SFTFOUBUJPO ˞IUUQTVOJDPEFPSH1VCMJDFNPKJFNPKJWBSJBUJPOTFRVFODFTUYUͰఆٛ͞Εͨ΋ͷ͔͠74͕෇͚ΒΕͳ͍
  4. ⭐ "OESPJE04ͰͷϑΥϯτͷ༏ઌ౓ /PUP4BOT4ZNCPMT3FHVMBS4VCTFUUFEUUG /PUP$PMPS&NPKJUUG /PUP4BOT4ZNCPMT3FHVMBS4VCTFUUFEUUG 6 ' /system/etc/fonts.xml ˞ ༏ઌ౓

    6 # 6 ' ˞74 74Λ͚ͭΔͱ༏ઌ౓͕มΘΔ   6 &% IJHI MPX σϑΥϧτ͕5FYU1SFTFOUBUJPOͷจࣈ σϑΥϧτ͕&NPKJ1SFTFOUBUJPOͷจࣈ σϑΥϧτ͕&NPKJ1SFTFOUBUJPO͕ͩޙΖʹ74Λ෇͚ΕΔจࣈ ˞IUUQTBOESPJEHPPHMFTPVSDFDPNQMBUGPSNGSBNFXPSLTCBTF BOESPJEDUT@SEBUBGPOUTGPOUTYNM
  5. ʴ 6  6 & FNPKJLFZDBQTFRVFODF  ⃣  6

    '&' 74 ʴ FNPKJ[XKTFRVFODF ;8+ 6 % ;8+ ;8+ 6 % 6 % ʴ ʴ ʴ ʴ ʴ ʴ   $0.#*/*/(&/$-04*/(,&:$"1 ˞IUUQTVOJDPEFPSH1VCMJDFNPKJFNPKJ[XKTFRVFODFTUYUͰఆٛ͞Εͨ3(*4FRVFODFͷΈ
  6. FNPKJqBHTFRVFODF ʴ FNPKJUBHTFRVFODF ( 6 & # & 6 &

    6 && ʴ ʴ ʴ ʴ ʴ ʴ 6 '' / ( ✦ 6 & 6 & 6 &' 5BH$IBSBDUFST 3FHJPOBM*OEJDBUPS Y ˞IUUQTVOJDPEFPSH1VCMJDFNPKJFNPKJTFRVFODFTUYUͰఆٛ͞Εͨ3(*4FRVFODFͷΈ
  7. "OESPJEͰͷ&NPKJ4FRVFODFͷඳը (46#ςʔϒϧʹ &NPKJ4FRVFODF͔ ΒάϦϑ΁ͷରԠ෇ ͚͕ఆٛ͞Ε͓ͯΓɺ )BSG#V[[͕ͦΕΛར ༻ͯ͠άϦϑΛஔ׵ HMZQI HMZQI HMZQI

    HMZQI (MZQI*% $#%5 V'"" <LigatureSet glyph="u1F4AA"> <Ligature components="u1F3FB" glyph="glyph02038"/> <Ligature components="u1F3FC" glyph="glyph02039"/> <Ligature components="u1F3FD" glyph="glyph02040"/> <Ligature components="u1F3FE" glyph="glyph02041"/> <Ligature components="u1F3FF" glyph="glyph02042"/> </LigatureSet> (46#ςʔϒϧ 0QFO5ZQF  
  8. &NPKJ$PNQBUͷ࢓૊Έ  SpannableString Ϋϥε ಛघͳ૷০͕ࢪ͞Εͨ 4QBOOFE จࣈྻΛද͢ ReplacementSpan Ϋϥε ಛఆͷจࣈͷඳը෦෼Λஔ͖׵

    ͑ΔͨΊͷSpan (PPE GGGGE 4QBOOBCMF4USJOH &NPKJ4QBO ˢ&NPKJ4QBO͕ඳը 04͕ඳըˢ $BOWBT
  9. &NPKJ$PNQBUͷઃఆ implementation "com.android.support:support-emoji-appcompat:27.1.1" implementation "com.android.support:support-emoji-bundled:27.1.1" class MyApplication : Application() {

    override fun onCreate() { super.onCreate() val config = BundledEmojiCompatConfig(this) config.setReplaceAll(true) EmojiCompat.init(config) } }
  10. σϑΥϧτͷ1SFTFOUBUJPOΛมߋ͢Δ // શ෦Emoji Presentationʹ͢Δ config.setUseEmojiAsDefaultStyle(true) // iOSͷσϑΥϧτڍಈʹ߹ΘͤΔ val list =

    "☺☹✈♠♥♟↗↘↙↖↔㽉㽊⚕♾‼⁉⾺©®™".toCharArray().map { it.toInt() } + listOf<Int>(0x1f170, 0x1f171, 0x1f17e, 0x1f17f) // ⷁⷂ⷏ⷐ config.setUseEmojiAsDefaultStyle(true, list) // ແࢦఆ → Android OSͷσϑΥϧτPresentationʹै͏
  11. 6OJDPEF&NPKJΛؚΉจࣈྻͷॲཧ "OESPJE಺ଂͷ*$6͸࠷৽ͷֆจࣈʹରԠ͍ͯ͠ͳ͍ IUUQTBOESPJEHPPHMFTPVSDFDPNQMBUGPSNGSBNFXPSLTNJOJLJO BOESPJEDUT@SMJCTNJOJLJO&NPKJDQQ bool isNewEmoji(uint32_t c) { // Emoji

    characters new in Unicode emoji 11 // From https://www.unicode.org/Public/emoji/11.0/emoji-data.txt // TODO: Remove once emoji-data.text 11 is in ICU or update to 11. if (c < 0x1F6F9 || c > 0x1F9FF) { // Optimization for characters outside the new emoji range. return false; } return c == 0x265F || c == 0x267E || c == 0x1F6F9 || (0x1F94D <= c && c <= 0x1F94F) || (0x1F96C <= c && c <= 0x1F970) || (0x1F973 <= c && c <= 0x1F976) || c == 0x1F97A || (0x1F97C <= c && c <= 0x1F97F) || (0x1F998 <= c && c <= 0x1F9A2) || (0x1F9B0 <= c && c <= 0x1F9B9) || (0x1F9C1 <= c && c <= 0x1F9C2) || (0x1F9E7 <= c && c <= 0x1F9FF); } bool isEmoji(uint32_t c) { return isNewEmoji(c) || u_hasBinaryProperty(c, UCHAR_EMOJI); } ˣ*$6ͷ"1* minikin/Emoji.cpp
  12. 6OJDPEF&NPKJΛؚΉจࣈྻͷॲཧ val string = this.toString() var displayLength = 0 var

    i = 0 while (i < text.length) { val span = spans.firstOrNull { i == text.getSpanStart(it) } if (span == null) { //charCountͷ෼͚ͩΛ1จࣈͱΈͳ͢ val codePoint = string.codePointAt(i) val count = Character.charCount(codePoint) i += count } else { //EmojiSpanͷ͔͔͍ͬͯΔindex·ͰΛ1จࣈͱΈͳ͢ i = text.getSpanEnd(span) } displayLength++ } return displayLength IUUQTRJJUBDPNIJSPZVLJTFUPJUFNTDCCGBFGB
  13. ΧελϜֆจࣈϑΥϯτ B αʔυύʔςΟͷֆจࣈϑΥϯτϕϯμ͔Βೖख w FNPKJPOFͳͲ C /PUP$PMPS&NPKJΛੜ੒͢ΔεΫϦϓτΛར༻ͯ͠࡞੒ w IUUQTHJUIVCDPNHPPHMFJOOPUPFNPKJ ˞

    ˞ϊϋφͰ͸IUUQTHJUIVCDPNOPIBOBOPUPFNPKJUSFFUXFNPKJWQJTUPMVQEBUFͰੜ੒ ࡞੒ͨ͠ϑΥϯτΛGPOUSFTPVSDFʹ͍ΕͯsetTypeface() w ਺ࣈͳͲ͕ਖ਼͘͠දࣔ͞Εͳ͘ͳΔ͜ͱ͕͋Δ
  14. /PUP4BOT4ZNCPMT3FHVMBS4VCTFUUFEUUG /PUP$PMPS&NPKJUUG /PUP4BOT4ZNCPMT3FHVMBS4VCTFUUFEUUG 6 ' 6 # 6 ' 6

     FNPKJPOF@BOESPJEUUG ༏ઌ౓ IJHI MPX ʢsetTypface()Ͱࢦఆʣ ⭐ 3PCPUP3FHVMBSUUG 7 σϑΥϧτϑΥϯτΛ্ॻ͖ͯ͠͠·͏