gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/widget/text_bench_test.go (about) 1 package widget 2 3 import ( 4 "fmt" 5 "image" 6 "math/rand" 7 "os" 8 "sort" 9 "testing" 10 11 colEmoji "eliasnaur.com/font/noto/emoji/color" 12 "gioui.org/font" 13 "gioui.org/font/gofont" 14 "gioui.org/font/opentype" 15 "gioui.org/gpu/headless" 16 "gioui.org/io/system" 17 "gioui.org/layout" 18 "gioui.org/op" 19 "gioui.org/text" 20 "gioui.org/unit" 21 "golang.org/x/exp/maps" 22 ) 23 24 var ( 25 documents = map[string]string{ 26 "latin": latinDocument, 27 "arabic": arabicDocument, 28 "complex": complexDocument, 29 "emoji": emojiDocument, 30 } 31 emojiFace = func() opentype.Face { 32 face, _ := opentype.Parse(colEmoji.TTF) 33 return face 34 }() 35 sizes = []int{10, 100, 1000} 36 locales = []system.Locale{arabic, english} 37 benchFonts = func() []font.FontFace { 38 collection := gofont.Collection() 39 collection = append(collection, arabicCollection...) 40 collection = append(collection, font.FontFace{ 41 Font: font.Font{ 42 Typeface: "Noto Color Emoji", 43 }, 44 Face: emojiFace, 45 }) 46 return collection 47 }() 48 ) 49 50 func runBenchmarkPermutations(b *testing.B, benchmark func(b *testing.B, runes int, locale system.Locale, document string)) { 51 docKeys := maps.Keys(documents) 52 sort.Strings(docKeys) 53 for _, locale := range locales { 54 for _, runes := range sizes { 55 for _, textType := range docKeys { 56 txt := documents[textType] 57 b.Run(fmt.Sprintf("%drunes-%s-%s", runes, locale.Direction, textType), func(b *testing.B) { 58 benchmark(b, runes, locale, txt) 59 }) 60 } 61 } 62 } 63 } 64 65 var render bool 66 67 func init() { 68 if _, ok := os.LookupEnv("RENDER_WIDGET_TESTS"); ok { 69 render = true 70 } 71 } 72 73 func BenchmarkLabelStatic(b *testing.B) { 74 runBenchmarkPermutations(b, func(b *testing.B, runeCount int, locale system.Locale, txt string) { 75 var win *headless.Window 76 size := image.Pt(200, 1000) 77 gtx := layout.Context{ 78 Ops: new(op.Ops), 79 Constraints: layout.Constraints{ 80 Max: size, 81 }, 82 Locale: locale, 83 } 84 cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(benchFonts)) 85 if render { 86 win, _ = headless.NewWindow(size.X, size.Y) 87 defer win.Release() 88 } 89 fontSize := unit.Sp(10) 90 font := font.Font{} 91 runes := []rune(txt)[:runeCount] 92 runesStr := string(runes) 93 l := Label{} 94 b.ResetTimer() 95 for i := 0; i < b.N; i++ { 96 l.Layout(gtx, cache, font, fontSize, runesStr, op.CallOp{}) 97 if render { 98 win.Frame(gtx.Ops) 99 } 100 gtx.Ops.Reset() 101 } 102 }) 103 } 104 105 func BenchmarkLabelDynamic(b *testing.B) { 106 runBenchmarkPermutations(b, func(b *testing.B, runeCount int, locale system.Locale, txt string) { 107 var win *headless.Window 108 size := image.Pt(200, 1000) 109 gtx := layout.Context{ 110 Ops: new(op.Ops), 111 Constraints: layout.Constraints{ 112 Max: size, 113 }, 114 Locale: locale, 115 } 116 cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(benchFonts)) 117 if render { 118 win, _ = headless.NewWindow(size.X, size.Y) 119 defer win.Release() 120 } 121 fontSize := unit.Sp(10) 122 font := font.Font{} 123 runes := []rune(txt)[:runeCount] 124 l := Label{} 125 r := rand.New(rand.NewSource(42)) 126 b.ResetTimer() 127 for i := 0; i < b.N; i++ { 128 // simulate a constantly changing string 129 a := r.Intn(len(runes)) 130 b := r.Intn(len(runes)) 131 runes[a], runes[b] = runes[b], runes[a] 132 l.Layout(gtx, cache, font, fontSize, string(runes), op.CallOp{}) 133 if render { 134 win.Frame(gtx.Ops) 135 } 136 gtx.Ops.Reset() 137 } 138 }) 139 } 140 141 func BenchmarkEditorStatic(b *testing.B) { 142 runBenchmarkPermutations(b, func(b *testing.B, runeCount int, locale system.Locale, txt string) { 143 var win *headless.Window 144 size := image.Pt(200, 1000) 145 gtx := layout.Context{ 146 Ops: new(op.Ops), 147 Constraints: layout.Constraints{ 148 Max: size, 149 }, 150 Locale: locale, 151 } 152 cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(benchFonts)) 153 if render { 154 win, _ = headless.NewWindow(size.X, size.Y) 155 defer win.Release() 156 } 157 fontSize := unit.Sp(10) 158 font := font.Font{} 159 runes := []rune(txt)[:runeCount] 160 runesStr := string(runes) 161 e := Editor{} 162 e.SetText(runesStr) 163 b.ResetTimer() 164 for i := 0; i < b.N; i++ { 165 e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) 166 if render { 167 win.Frame(gtx.Ops) 168 } 169 gtx.Ops.Reset() 170 } 171 }) 172 } 173 174 func BenchmarkEditorDynamic(b *testing.B) { 175 runBenchmarkPermutations(b, func(b *testing.B, runeCount int, locale system.Locale, txt string) { 176 var win *headless.Window 177 size := image.Pt(200, 1000) 178 gtx := layout.Context{ 179 Ops: new(op.Ops), 180 Constraints: layout.Constraints{ 181 Max: size, 182 }, 183 Locale: locale, 184 } 185 cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(benchFonts)) 186 if render { 187 win, _ = headless.NewWindow(size.X, size.Y) 188 defer win.Release() 189 } 190 fontSize := unit.Sp(10) 191 font := font.Font{} 192 runes := []rune(txt)[:runeCount] 193 e := Editor{} 194 e.SetText(string(runes)) 195 r := rand.New(rand.NewSource(42)) 196 b.ResetTimer() 197 for i := 0; i < b.N; i++ { 198 // simulate a constantly changing string 199 a := r.Intn(e.Len()) 200 b := r.Intn(e.Len()) 201 e.SetCaret(a, a+1) 202 takeStr := e.SelectedText() 203 e.Insert("") 204 e.SetCaret(b, b) 205 e.Insert(takeStr) 206 e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) 207 if render { 208 win.Frame(gtx.Ops) 209 } 210 gtx.Ops.Reset() 211 } 212 }) 213 } 214 215 func FuzzEditorEditing(f *testing.F) { 216 f.Add(complexDocument, int16(0), int16(len([]rune(complexDocument)))) 217 gtx := layout.Context{ 218 Ops: new(op.Ops), 219 Constraints: layout.Constraints{ 220 Max: image.Pt(200, 1000), 221 }, 222 Locale: arabic, 223 } 224 cache := text.NewShaper(text.NoSystemFonts(), text.WithCollection(benchFonts)) 225 fontSize := unit.Sp(10) 226 font := font.Font{} 227 e := Editor{} 228 f.Fuzz(func(t *testing.T, txt string, replaceFrom, replaceTo int16) { 229 e.SetText(txt) 230 e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) 231 // simulate a constantly changing string 232 if e.Len() > 0 { 233 a := int(replaceFrom) % e.Len() 234 b := int(replaceTo) % e.Len() 235 e.SetCaret(a, a+1) 236 takeStr := e.SelectedText() 237 e.Insert("") 238 e.SetCaret(b, b) 239 e.Insert(takeStr) 240 } 241 e.Layout(gtx, cache, font, fontSize, op.CallOp{}, op.CallOp{}) 242 gtx.Ops.Reset() 243 }) 244 } 245 246 const ( 247 latinDocument = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 248 Porttitor eget dolor morbi non arcu risus quis. 249 Nibh sit amet commodo nulla. 250 Posuere ac ut consequat semper viverra nam libero justo. 251 Risus in hendrerit gravida rutrum quisque. 252 Natoque penatibus et magnis dis parturient montes nascetur. 253 In metus vulputate eu scelerisque felis imperdiet proin fermentum. 254 Mattis rhoncus urna neque viverra. 255 Elit pellentesque habitant morbi tristique. 256 Nisl nunc mi ipsum faucibus vitae aliquet nec. 257 Sed augue lacus viverra vitae congue eu consequat. 258 At quis risus sed vulputate odio ut. 259 Sit amet volutpat consequat mauris nunc congue nisi. 260 Dignissim cras tincidunt lobortis feugiat. 261 Faucibus turpis in eu mi bibendum. 262 Odio aenean sed adipiscing diam donec adipiscing tristique. 263 Fermentum leo vel orci porta non pulvinar. 264 Ut venenatis tellus in metus vulputate eu scelerisque felis imperdiet. 265 Et netus et malesuada fames ac turpis. 266 Venenatis urna cursus eget nunc scelerisque viverra mauris in. 267 Risus ultricies tristique nulla aliquet enim tortor. 268 Risus pretium quam vulputate dignissim suspendisse in. 269 Interdum velit euismod in pellentesque massa placerat duis ultricies lacus. 270 Proin gravida hendrerit lectus a. 271 Auctor augue mauris augue neque gravida in fermentum et. 272 Laoreet sit amet cursus sit amet dictum. 273 In fermentum et sollicitudin ac orci phasellus egestas tellus rutrum. 274 Tempus imperdiet nulla malesuada pellentesque elit eget gravida. 275 Consequat id porta nibh venenatis cras sed. 276 Vulputate ut pharetra sit amet aliquam. 277 Congue mauris rhoncus aenean vel elit. 278 Risus quis varius quam quisque id diam vel quam elementum. 279 Pretium lectus quam id leo in vitae. 280 Sed sed risus pretium quam vulputate dignissim suspendisse in est. 281 Velit laoreet id donec ultrices. 282 Nunc sed velit dignissim sodales ut. 283 Nunc scelerisque viverra mauris in aliquam sem fringilla ut. 284 Sed enim ut sem viverra aliquet eget sit. 285 Convallis posuere morbi leo urna molestie at. 286 Aliquam id diam maecenas ultricies mi eget mauris. 287 Ipsum dolor sit amet consectetur adipiscing elit ut aliquam. 288 Accumsan tortor posuere ac ut consequat semper. 289 Viverra vitae congue eu consequat ac felis donec et odio. 290 Scelerisque in dictum non consectetur a. 291 Consequat nisl vel pretium lectus quam id leo in vitae. 292 Morbi tristique senectus et netus et malesuada fames ac turpis. 293 Ac orci phasellus egestas tellus. 294 Tempus egestas sed sed risus. 295 Ullamcorper morbi tincidunt ornare massa eget egestas purus. 296 Nibh venenatis cras sed felis eget velit.` 297 arabicDocument = `و سأعرض مثال حي لهذا، من منا لم يتحمل جهد بدني شاق إلا من أجل الحصول على ميزة أو فائدة؟ ولكن من لديه الحق أن ينتقد شخص ما أراد أن يشعر بالسعادة التي لا تشوبها عواقب أليمة أو آخر أراد أن يتجنب الألم الذي ربما تنجم عنه بعض المتعة ؟ علي الجانب الآخر نشجب ونستنكر هؤلاء الرجال المفتونون بنشوة اللحظة الهائمون في رغباتهم فلا يدركون ما يعقبها من الألم والأسي المحتم، واللوم كذلك يشمل هؤلاء الذين أخفقوا في واجباتهم نتيجة لضعف إرادتهم فيتساوي مع هؤلاء الذين يتجنبون وينأون عن تحمل الكدح والألم . 298 من المفترض أن نفرق بين هذه الحالات بكل سهولة ومرونة. 299 في ذاك الوقت عندما تكون قدرتنا علي الاختيار غير مقيدة بشرط وعندما لا نجد ما يمنعنا أن نفعل الأفضل فها نحن نرحب بالسرور والسعادة ونتجنب كل ما يبعث إلينا الألم. 300 في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا الواجب والعمل سنتنازل غالباً ونرفض الشعور بالسرور ونقبل ما يجلبه إلينا الأسى. 301 الإنسان الحكيم عليه أن يمسك زمام الأمور ويختار إما أن يرفض مصادر السعادة من أجل ما هو أكثر أهمية أو يتحمل الألم من أجل ألا يتحمل ما هو أسوأ. 302 و سأعرض مثال حي لهذا، من منا لم يتحمل جهد بدني شاق إلا من أجل الحصول على ميزة أو فائدة؟ ولكن من لديه الحق أن ينتقد شخص ما أراد أن يشعر بالسعادة التي لا تشوبها عواقب أليمة أو آخر أراد أن يتجنب الألم الذي ربما تنجم عنه بعض المتعة ؟ علي الجانب الآخر نشجب ونستنكر هؤلاء الرجال المفتونون بنشوة اللحظة الهائمون في رغباتهم فلا يدركون ما يعقبها من الألم والأسي المحتم، واللوم كذلك يشمل هؤلاء الذين أخفقوا في واجباتهم نتيجة لضعف إرادتهم فيتساوي مع هؤلاء الذين يتجنبون وينأون عن تحمل الكدح والألم . 303 من المفترض أن نفرق بين هذه الحالات بكل سهولة ومرونة. 304 في ذاك الوقت عندما تكون قدرتنا علي الاختيار غير مقيدة بشرط وعندما لا نجد ما يمنعنا أن نفعل الأفضل فها نحن نرحب بالسرور والسعادة ونتجنب كل ما يبعث إلينا الألم. 305 في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا الواجب والعمل سنتنازل غالباً ونرفض الشعور بالسرور ونقبل ما يجلبه إلينا الأسى. 306 الإنسان الحكيم عليه أن يمسك زمام الأمور ويختار إما أن يرفض مصادر السعادة من أجل ما هو أكثر أهمية أو يتحمل الألم من أجل ألا يتحمل ما هو أسوأ.` 307 complexDocument = `و سأعرض مثال dolor sit amet, لم يتحمل جهد adipiscing elit, sed do الحصول على ميزة incididunt ut labore أن ينتقد magna aliqua. 308 Porttitor إرادتهم فيتساوي morbi non arcu يدركون ما يعقبها . 309 Nibh نشجب ونستنكر commodo nulla. 310 بكل سهولة ومرونة ut consequat لهذا، من منا nam libero justo. 311 Risus in hendrerit علينا الواجب والعمل. 312 Natoque تكون قدرتنا علي magnis dis parturient يمسك زمام الأمور ويختار. 313 In نجد ما يمنعنا eu scelerisque ونظراً للالتزامات التي fermentum. 314 Mattis ة بشرط وعندما لا neque viverra. 315 يمسك زمام الأمور habitant لهذا، من. 316 Nisl تي يفرضها علينا faucibus ،من منا لم nec. 317 Sed augue علي الاختيار غير vitae congue eu consequat. 318 At quis risus سك زمام الأمور ويختار. 319 Sit amet volutpat consequat mauris الأمور ويختار إما nisi. 320 Dignissim لواجب والعمل tincidunt سنتنازل feugiat. 321 Faucibus التزامات in eu mi bibendum. 322 Odio ويختار إما أن يرفض مصادر السعادة sed adipiscing ذا، من منا لم tristique. 323 Fermentum leo vel ور ويختار إما pulvinar. 324 Ut ر إما أن يرفض مصادر السعادة من in metus تكون قدرتنا علي felis imperdiet. 325 ي الاختيار غير مقيدة بشرط et malesuada fames ac turpis. 326 Venenatis على ميزة أو فائدة؟ ولكن eget nunc scelerisque سك زمام الأمور ويختار إما in. 327 رتنا ultricies tristique ي الاختيار غير مقيدة بشرط enim tortor. 328 Risus اختيار غير مقيدة بشرط وعندما quam سان الحكيم عليه أن suspendisse in. 329 Interdum velit ونظراً للالتزامات التي pellentesque massa placerat لأمور ويختار إما أن يرفض lacus. 330 Proin دما تكون قدرتنا علي الاختيار lectus a. 331 Auctor الوقت عندما تكون augue neque ض مثال حي fermentum et. 332 Laoreet مسك زمام الأمور ويختار amet cursus لم يتحمل جهد dictum. 333 In fermentum et sollicitudin ac orci phasellus علي الاختيار غير rutrum. 334 Tempus imperdiet المفترض أن نفرق pellentesque ت بكل سهولة eget gravida. 335 Consequat id portaمصادر السعادة cras sed. 336 Vulputate علي الاختيار غير مقيدة sit amet aliquam. 337 Congue mauris حيان ونظراً للالتزامات التي vel elit. 338 Risus quis varius quam quisque id ار غير مقيدة بشرط elementum. 339 Pretium تي يفرضها علينا الواجب leo in vitae. 340 شاق إلا من أجل pretium quam الحكيم عليه أن يمسك suspendisse in est. 341 Velit ونظراً للالتزامات التي يفرضها ultrices. 342 الوقت عندما تكون velit dignissim يه أن يمسك . 343 Nunc scelerisque viverra mauris in aliquam sem ر إما أن ut. 344 السعادة من أجل ما هو أكثر أهمية أو يتحمل الألم 345 Convallis posuere morbi leo urna molestie at.` 346 emojiDocument = `📚🎶🐰🌷👹🌟 🔰🐲📑🍢🔎 👢💮👷👧💑🐪 📙📜🐎🏠🎠 👧🌼💛🎉💜🎍 🔜💷🐉👘🕟📗🍟 🎆🍚📹💄 🐾🎩💽👘 📒💕👅💽🐩 📷🌌🌚🎣📌. 🍈🍅🔖🍄 🍐🔈🍤🐽 🐹💘🍚👩📡 🎸🏠🔳🏩🌳💣 🔡🔠🕤🔔🎴📕 📼👝🎓🕗💸 📓🌽🍟💵🕗🌒🏉📨 🔀🏉🍴💘🍣💸 🔪🔻🕖🎰 🐲👮🔙🌇🐒🏇 🐝🌚🏫🔀👍 👾🎧🍋🍔👧 💣💞🐴👆🐢🏊📀 🕤🌃🍌🕛🔬. 🏃🍜🍔🐽🎁🏩🎰 📮🍄🐖💕👈 🔠🕡🐊💞🍬📳 🎤🌆🌛🐍🔳 🐄🔇🔱🌇📺👞 💌👍📳🎤🏂 👞🎉🍶📊🔶🌅🐭🕙 🍜📠🎴💒🔶 📀💂🌷👺👙. 347 348 📥🕝🎎🐻💘🍇🔤 💠🎇📦👩🍁 👜🍏🔏👎 🔟🌹🌗🎬🔙 🐁📛🐝🐏🐣 🔃🗻🔎🌺👀 📰📮🏩👯🐳🍀🍇 🍨📵🌂📌 👌📐🏨🐉 🍏🍘🔟🎣🔏📠 👤📭🐱📣. 🕓👶🎳📭🔌📃🔧 📟🔰🌂🎈🔣 🔤👍🍤👔🐪 🔨🎼🎊🎪🕝🐬 📴🎶🔈🔐🔘 🐬🐯🕜🎎👴🎃 🎑🐾👏👇🔭 🐥🔙💦🔩🔮 👊🐶👗📕 🐎📹👠🍤 🔢💘📷🐷🐂 🐫💕🕕🍖🔆🎽 👼🎶🌸👻🔷🌰 🔔💉💱🔂👵🔑. 🌁🎪🎌🍘🍏 🌛🍂🔎🕃📧👻 🎍🌔🐦🐻 🔉🎌🌘💉👒 📙💠🔙📰 🌒👏💪🌇💈 🌌📯📂🌀🔁 🏧💷🍀🐐🏈 📢🌏🔷💭 👋🕓🌓🕛🏢👡👋 🍶🐂🍠🔟 👵🏇🔶🕜👎. 👹💉🔌🍳🕗 🐫🌈🔠🐀🎩🎽 👺🔣🔂👪👴🐚🕙 👀🕓🔱🌇 🎻🐘🔐🕕 🌉🔡🐊🍮 💫🎆🎹🐍🎯 🐑🐱🍠🕑🍒. 349 350 🎳🐎🔹🎾🐹📖 📘🐒📷🕧🔛 🐾📺🎿🍖💂🕥 🍜🎷🍣👳 🕛📧💶🌑 🌀💣🎎🐛🎪🐒 🎇🌹👺🎆💄📚 🔓🍗📓🎂🌍🌘📢 🍩💞🏂💥🔹📇💴 🐇🕝💹🐣💔🎫 👐🍼🏰🎄🎨👚 👑🔗🍅🐈 🐰🐙🌻👹👆 👬🐧🍬🕡🐽💉 🌅🔉🎤🔁📨🔧🔀🍏 🎼🔛📉🌺. 👖🌔🍢🏂💯 🏁🐰🍉📬🍖 📨💜📮🔕🎣🔩 🔏🕀🏫🎳📵👭👟 💨💃📶🎃 📚🔇🍛👽🐍🐄 🔼👻🍮🍔🍨🏪🐺 📩📜🍨📖 🏢🎉🔢🌚🌀🔊💍 🐟🕚🔴🎿🍞 🌈📤👲🌿🌅🍲📛💍 🐦🔰🐗🐆🎻 👑🕐📔🎁🍙🔪🔭. 🎐🐵🎼🌒🎰🍳🎽 🐻🔉💺🕁🍷 🐛🍬💦📶🔖 🔕🌳💃🌺🔢 💒📒🔘🐸👩 🌺🍈🌀🏁🎢🔖 📈🎸🐖👪 🐅🏁🔹🎬🍖📊🗼 🎬📅💝📀🎐. 🌗📍👇🎠 🌸🐸🐐🍕🐋 💈🌌🐶💤🌻🐞 🍯🌳📌🍮🐻🍝 🕦📯🔱👒 💖🌱🐨🎰🏭🏈 🔳🏩🌟🔭📢📒 🔅💬💓💻💁💂 🔗🍂🏇🌒🌂💩🕢 🔙🌆💞📜🔘👇 🍎🌃🔢🌵🏬 🔄💢🍨📋💇🌄 🍝🍧💂🏮🏁. 🎬🐽🔇🎣🌜🔣 🌍🔒👿🎆🌞🍇🍸 👖🍘🏡🕣 📝🐖💆🎈 👙🔳👙🔩👀🔂 🐤📈💃👗🔌🎾🔭🍴 🌺👛🌵🌕🐺 🎆💼👌👘🍈👛 🎳🐪🕧🏄 💯🍟💂👖🎍 🕀💟🌷💕🐉🐲🎷. 🎍👂📓🌽 🐉🕕🐤🌲📟🔂💷 🎑📛🕠🔹🐚 🍆📹🐚🐵🏇🏢 🍠💱🕦💙🏢🐌🎎 🐄🍨📄🌾🎻🏈 🏇🍪💸🔆💍📢👢 💇🌋👝🕜🌍🐶🎓 🍪📄🐤🎃💖 🔲🕒🍧🌎🐪🌶 🍓👲🔭🍯🌔👌 🔼🐗🗼🍂 🔶🍯🎶🐅🐂💗🐴🐶 📭📰📔👬🏯🕟🐄🍊 💆👞📆🐶🌖🎁👺 🐃💺👊🌿🎌. 351 352 🍧🕔👆🔭🕛👇 🐆🔖🎂🐭📗🗼🐐 🌐🎢🌞💛🐚 🌿🎶💎💬🔩 💾🔐🎷🍙🐬🕐 🌏🍄🎾🐎🌽🍓🐳 💥🍎👳📫🐤📼🎾 👨🕃🕞🍯🍲. 💥🎍🔉🎈👻🔵🏬🔸 🔼🍹🔱🔮🕔 🌈💎👜📠 👢🍻🍢🎃👺🌍👰 🍵👃🕠🍎🍑 📜💥📘📌 🔹🔵🍷👅💏 💮💘🐜📠👬📖 🌅🍺🔇🌈👒🔀 🎢🌆💌🍬📱🎰 🌺🍆🔰🏁🍁🎠 🔇🔁🌹🔞🎀🎬🐭🌹 🏬📫🗾🎻📌. 🐠🏣👋👊🐟 👲🔣💻👅🎎 🎇🌲🕑🍨📯 🐜📵💙📷🎒🕔 🎇🏀🔴🐑🌗 🎧🔡👅🕁🏉👛🐬 🕧🐞🎩📓🍆📪 🐼📻👼🌄 🌟🌺🏦🍧🍕🐯 🕕🕦🐤💆🍧💩 🐑📜👏👐🐧🍞👵 👞🌲🍼🔍 🌛🐔🌄🎸🐯.` 353 )