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