Color-Emoji support without native.* really not doable?

Excellent input, I’ll consider your tips in my final solution (which will be posted here once it’s done)…

Great thread.  Please share if you come up with a solution. Thanks  :slight_smile:

Will do, promised. My graphic designer is currently trying to understand potential copyright issues with iOS / Android emojis and where to get them and I just discovered that our German öäü Umlaute dont work anymore, but we’ll get there :slight_smile:

Busy week with many clients to take care of, but a working solution will be posted here!

/edit: Umlaut-problem fixed by changing

-- if next object is a char if( codepoint \<= 255 ) then -- create a newText object with the single char obj = display.newText( tempGroup, string.char(codepoint), curX, curY, font, fontSize ) 

to

-- if next object is a char if( codepoint \<= 255 ) then -- create a newText object with the single char obj = display.newText( tempGroup, utf8.char(codepoint), curX, curY, font, fontSize )

So, it has to be utft8.char(codepoint), not string.char(codepoint).

Funny thing is: ÄäÖÜ did work, but öü didnt on my testdevice. Now they all do :slight_smile:

New issue observed: As long as each character is printed separately via display.newText(singleCharacter, …), the distance between those characters isn’t correct any more. They all have - of course - the same distance between them, which looks weird when there is “T” involved for example. In the word “Test”, one would expect the “e” to move closer to the stem of the “T”, which wouldn’t happen in the current version, it’d rather look like “T e”.

Solved it by building up a substring and only print it via display.newText(substring, …) as soon as we see a space, an emoji or we reach end of specified with. This way, it should be even faster and less memory consuming while the whole transformation is in progress since we build way less newText-objects (even if we delete them after taking a snapshot).

Works like a charme, now distance between chars isnt an issue any more :slight_smile:

Ok, worked on another issue the last couple of days, seems like everything is working fine now.

Problem was that there are emojis that are a combination of emojis, like a the “family-emoji”, which consists of a man, a woman and a girl that are combined used a so called zero-width-joiner. In addition to that, there are fitzpatrick-modifier for different skin-colors, so that a normal “man”-emoji can become a “man-lightskin”, “man-mediumlightskin” and so on.

All these special cases should be handled now. I had to forget the idea of taking a snapshot after everything is rendered, because scrollViews with different texts, onscreen and below, dont allow taking snapshots of parts that are not yet visible.

This version does not yet consider different imageSheets for Android/iOS, but this souldnt be a problem to implement.

This is the final result:

-- =================================================================================== -- =================================================================================== -- This module loops through strings, extracts every word (separated by space or emoji) -- into a single display.newText() and every single emoji into a display.newImageRect() -- Words and emojis are aligned next to each other, taking into consideration -- newlines when maxwidth is reached. -- Combined emojis (e.g. "family: Men-woman-boy-girl") are handled. -- =============================================================================== -- =============================================================================== -- no emojis without utf8 local utf8 = require("plugin.utf8") -- create module-table that is returned at the end local emojiModule = {} -- create our emojiMap, gets filled at the bottom local emojiMap = {} -- setup spriteSheet with emojis local options = { width = 32, height = 32, numFrames = 43 ^ 2, sheetContentWidth = 1376, sheetContentHeight = 1376 } local sheet = graphics.newImageSheet( "emojis.png", options ) -- write down codepoints for modifiers local modFemale = 9792 -- uses female version of neutral emoji local modMale = 9794 -- uses male version of neutral emoji local modJoin = 8205 -- zero-width-joiner to combine emojis local modVariant = 65039 -- indicator that last object is an emoji local modLightSkin = 127995 -- fitzpatrick1: modify emoji with light skin local modMediumLightSkin = 127996 -- fitzpatrick2: modify emoji with medium light skin local modMediumSkin = 127997 -- fitzpatrick3: modify emoji with medium skin local modMediumDarkSkin = 127998 -- fitzpatrick4: modify emoji with medium dark skin local modDarkSkin = 127999 -- fitzpatrick5: modify emoji with dark skin -- function to create our emoijiText; -- parameters: -- (displaygroup to put elements in, inputstring, initial X, initial Y, max-width of line, font, fontSize, fontColor) function emojiModule.createEmojiText(group, text, x, y, width, font, fontSize, fillColor, filename) -- create a tempGroup local tempGroup = display.newGroup() group:insert(tempGroup) -- get font metrics since iOS and android treat metrics.leading different when rendering local metrics = graphics.getFontMetrics( font, fontSize ) local fontLeading = metrics.leading -- set initial x and y positions for first char or emoji local curX = x local curY = y -- set initial width to 0, adds up with each char or emoji local currentWidth = 0 -- create substring for chars, gets printed on screen once we see a space, an emoji or end of input-string local substring = "" -- create emoji substring (emojis can be combinations of emojis) local currentEmoji = "" -- create a flag indicating whether an emoji is a combination of emojis local combinedEmoji = false -- loop through input string for charpos, codepoint in utf8.codes(text) do -- ======================== -- if next object is a char -- ======================== if( codepoint \<= 255) then -- check if there is an unrendered emoji left if(currentEmoji ~= "") then -- if yes, create emoji local emoji -- normal case: We have the emoji in our Sheet if( emojiMap[currentEmoji] ) then emoji = display.newImageRect( tempGroup, sheet, emojiMap[currentEmoji], fontSize, fontSize) emoji.x = curX emoji.y = curY -- fallback: We dont have this emoji, use a standard one of your choice else emoji = display.newImageRect( tempGroup, sheet, emojiMap["128514"], fontSize, fontSize) emoji.x = curX emoji.y = curY end -- set anchorPoint emoji.anchorX = 0 -- update current Width currentWidth = currentWidth + emoji.contentWidth -- check if we passed max-width (end of line) if(currentWidth \> width) then -- if yes, reset positions for next char / emoji curX = x curY = curY + fontSize -- move our emoji into next line emoji.x = curX emoji.y = curY -- reset currentWidth currentWidth = emoji.contentWidth -- and y-translate group tempGroup.y = tempGroup.y - fontSize/2 end -- reset currentEmoji currentEmoji = "" combinedEmoji = false -- update currentX for next char/emoji curX = curX + emoji.contentWidth end -- add char to our substring substring = substring .. utf8.char(codepoint) -- in case char is a space if(codepoint == 32) then -- render substring that has been built so far -- has to be done since otherwise substring might get longer than max-width local obj = display.newText( tempGroup, substring, curX, curY, font, fontSize ) obj:setFillColor(fillColor) obj.anchorX = 0 -- on iOS, add font-leading to y-center text correct if (system.getInfo( "platform" ) == "ios") then obj.y = obj.y + fontLeading end -- update currentWidth currentWidth = currentWidth + obj.contentWidth -- check if we passed max-width (end of line) if(currentWidth \> width) then -- if yes, reset positions for next char / emoji curX = x curY = curY + fontSize -- reset currentWidth currentWidth = obj.contentWidth -- move our substring into next line obj.x = curX obj.y = curY -- and y-translate group group.y = group.y - fontSize/2 end -- reset substring substring = "" -- update currentX curX = curX + obj.contentWidth end -- ============================= -- else: next object is an emoji -- ============================= else -- render current substring, but only if it hasnt been rendered yet -- detected by checking currenct substring (is empty after a space) if(substring ~= "") then -- render substring that has been built so far local obj = display.newText( tempGroup, substring, curX, curY, font, fontSize ) obj:setFillColor(fillColor) obj.anchorX = 0 -- on iOS, add font-leading to y-center text correct if (system.getInfo( "platform" ) == "ios") then obj.y = obj.y + fontLeading end -- update currentWidth currentWidth = currentWidth + obj.contentWidth -- check if we passed of max-width (end of line) if(currentWidth \> width) then -- if yes, reset positions for next char / emoji curX = x curY = curY + fontSize -- reset currentWidth currentWidth = obj.contentWidth -- move our substring into next line obj.x = curX obj.y = curY -- and y-translate group tempGroup.y = tempGroup.y - fontSize/2 end -- reset substring substring = "" -- update currentX curX = curX + obj.contentWidth end -- ========================= -- now lets handle our emoji -- ========================= -- if emoji is variant-indicator if( codepoint == modVariant ) then combinedEmoji = false -- elseif emoji is a male/female modifiers elseif(codepoint == modFemale or codepoint == modMale) then currentEmoji = currentEmoji .. "\_" .. codepoint combinedEmoji = false -- elseif emoji is colored via fitzpatrick scale elseif( codepoint == modLightSkin or codepoint == modMediumLightSkin or codepoint == modMediumSkin or codepoint == modMediumDarkSkin or codepoint == modDarkSkin ) then currentEmoji = currentEmoji .. "\_" .. codepoint combinedEmoji = false -- elseif emoji is a zero-width-joiner elseif(codepoint == modJoin) then -- special case: set combinedEmoji to true so that next emoji can be added -- to create a combined emoji currentEmoji = currentEmoji .. "\_" combinedEmoji = true -- else: emoji is an actual emoji else -- in case previous emoji was not a combined one if(combinedEmoji == false) then -- if there actually is a previous, still unrendered emoji if(currentEmoji ~= "") then -- normal case: We have the emoji in our Sheet if( emojiMap[currentEmoji] ) then emoji = display.newImageRect( tempGroup, sheet, emojiMap[currentEmoji], fontSize, fontSize) emoji.x = curX emoji.y = curY -- fallback: We dont have this emoji, use a standard one of your choice else emoji = display.newImageRect( tempGroup, sheet, emojiMap["128514"], fontSize, fontSize) emoji.x = curX emoji.y = curY end -- set anchorPoint emoji.anchorX = 0 -- update current Width currentWidth = currentWidth + emoji.contentWidth -- check if we passed of max-width (end of line) if(currentWidth \> width) then -- if yes, reset positions for next char / emoji curX = x curY = curY + fontSize -- reset currentWidth currentWidth = emoji.contentWidth -- move our emoji into next line emoji.x = curX emoji.y = curY -- and y-translate group tempGroup.y = tempGroup.y - fontSize/2 end -- update currentX curX = curX + emoji.contentWidth end -- after printing previous emoji, reset currentEmoji currentEmoji = "" end -- with each emoji, reset combinationFlag, -- only gets true once we see a zero-width-joiner combinedEmoji = false currentEmoji = currentEmoji .. codepoint end end end -- after we are done looping through input string, check if there is a remaing substring if(substring ~= "") then -- if yes, print substring local obj = display.newText( tempGroup, substring, curX, curY, font, fontSize ) obj:setFillColor(fillColor) obj.anchorX = 0 -- on iOS, add font-leading to y-center text correct if (system.getInfo( "platform" ) == "ios") then obj.y = obj.y + fontLeading end -- update currentWidth currentWidth = currentWidth + obj.contentWidth -- check if we passed of max-width (end of line) if(currentWidth \> width) then -- move our substring into next line obj.x = x obj.y = obj.y + fontSize -- and y-translate group tempGroup.y = tempGroup.y - fontSize/2 end -- also, check if there is a remaining emoji elseif(currentEmoji ~= "") then -- normal case: We have the emoji in our Sheet if( emojiMap[currentEmoji] ) then emoji = display.newImageRect( tempGroup, sheet, emojiMap[currentEmoji], fontSize, fontSize) emoji.x = curX emoji.y = curY -- fallback: We dont have this emoji, use a standard one of your choice else emoji = display.newImageRect( tempGroup, sheet, emojiMap["128514"], fontSize, fontSize) emoji.x = curX emoji.y = curY end -- set anchorPoint emoji.anchorX = 0 -- update current Width currentWidth = currentWidth + emoji.contentWidth -- check if we passed of max-width (end of line) if(currentWidth \> width) then -- if yes, move our emoji into next line emoji.x = x emoji.y = curY + fontSize -- and y-translate group tempGroup.y = tempGroup.y - fontSize/2 end end end -- fill emoji map -- this needs to be done by user emojiMap["128512"] = 294 emojiMap["128514"] = 42 emojiMap["128515"] = 12 emojiMap["128129"] = 1698 -- return table return emojiModule

Chris - I’m glad you got where you needed to go with this.  Congrats on the effort, sticking-to-it, and the result.

-Ed/RoamingGamer

Hey Chris

I testet it and it seems to work very well. 

I am wondering… there are a lot of emojies these days, so do you create a giant imagesheet to support all of them?

I think you would be better off just exporting the emojies as separate png files and load them on the fly, to avoid filling up texture memory  with unused emojies.

Thanks for sharing. Jacob

It is even 4 imagesheets in total, since I read somewhere that 2048 x 2048 is max-size for sheets in Corona (correct me if Im wrong). And those 27xx emojis dont fit on just one in that size.

I am afraid I dont know anything about which way would be better in terms of memory-usage. In my app young people might use more emojis than actual characters and my understanding is that in this case it makes more sense to have an imagesheet. I’d be happy to hear the technical background to this though :slight_smile:

A 2048 x 2048 imagesheet will use around 16 mb of texture memory. So preloading 4 of those will take up 64 mb in total.

That is a lot of texture memory to spend on emojies. 

So I am just suggesting that instead of using imagesheets you create a folder and export each emoji, as a separate file named after their codepoint, into that.

This way it will be easy just to load the ones they actually use… when they use it.

And your use of texture memory drop to almost nothing.

No & Yes

No, not through Corona’s built-in display.* features directly.

Yes, write your own layer over display.newText() (or follow my suggestion below)

I’m not saying it will be easy, but you can do it.

You’ll need emoji art to support this.

Suggestion

  • Your best bet is to download a BitMapped font library you can use and understand. 

  • Generate a set of fonts to use with it.

  • Modify the module/library to support emojis.

  • Supply a standard set of emojis.

Note: I believe, you’ll you’ll need to support unicode.  This could be a bit of work.

PS - Thanks for the downloadable example.  You did however forget to include the font in you example, and I don’t see how this demonstrates the issue.

Question, how do you expect the text to be provided? 

i.e. Is this entirely internal with an arbitrary encoding for the emojis or are you trying to make something that can take text input from the users’s device that is then displayed as a text object with the same emoji’s used on their device?

I mean do you expect this to work:

  1. User types in some text and emojis into native.newTextField().

  2. You extract the ‘string’ from the text field.

  3. You display it using Corona’s display.new*() features.  

Download this to see an hacky solution and tool I made to demonstrate the singular fact that ‘it can (almost) always be done with Corona’:
 
https://github.com/roaminggamer/RG_FreeStuff/raw/master/AskEd/2018/10/emojis.zip
 
(emoji_mod subfolder)
 
Run using a simulated android device for mouse hover to work.
 
 
>>> If you like my effort click here:  Thanks RoamingGamer!
 
Image below shows demo running.
 
Image description top-to-bottom:

  • line 1 - What you get when printing strings containing emoji using display.newText()
  • line 2 - The output of my hacky extension display.newEmojiText()
  • remainder - A tool for getting sheet index for emojis from a sheet of emojis I found online.

emojis.png
 
 
 
PS - I know  those are probably NOT the right emojis.  I randomly selected two for the mapping.

Ed, it’s just always a pleasure reading your replies to either my or someone else’s questions. It’s not that often that people put that much effort into a reply and even upload a demo of a - hacky or not - solution. I’ve said it many times: Thank you :slight_smile:

I’ve downloaded your mod and I’m going to try to understand every single line and function tomorrow - just too tired right now, too many hours of coding :slight_smile:

After having a quick look it seems that your newEmojiText function sort of splits an input text into multiple parts: “Normal” characters are treated with display.newText - objects, emojis instead are added via newImageRect right next to the last string by setting curX + tmp.contentWidth.

I had something similar in mind, but stumbled when the userinput is longer (wider) than display.contentWidth (or any width value specified) since then tmp.contentWidth needs to be fixed in advance. So, I guess the demo project is a very good start, the next challenge though would be to modify it for multiline input. Hope it gets clear what I am trying to explain.

And, to answer your questions:

How does my demo project demonstrate the issue?

When you run it on a device and type in an emoji via the normal keyboard, you get the result my screenshot shows. The missing font is the result of my copy and paste process to make it a mini-version, but that shouldnt be a problem since devices just use a standard font instead.

how do you expect the text to be provided?

  • User types in some text and emojis into native.newText Box ().

  • This input is stored in a module and is retreived in another scene where it is - or should be - put into a scrollview

  • If user is happy with his input, the input is send to my Google App Engine where it’s stored in a mysql database (utf8)

  • other devices can request this input and show it again in a scrollview, so yes, display it using Corona’s display.new*() features

  • The input can be multiline

  • it would be perfect if the user could use all the emojis his keyboard offers

I’ll have a closer look tomorrow, but if this sounds like a ‘hire a hitman’ project for you and you feel like you could solve this even for newTextBox - inputs, please let me know :slight_smile:

Argh, this whole thing wont let me sleep. Just made your hacky solution even dirtier :slight_smile:

I modified your newEmojiText a little bit and

  • added currentWidth = 0

  • added currentY = 0

Now, with every Character or emoji, I add their contentWidth to currentWidth. As soon as currentWidth is equal or more than my width-limit, I set back currentWidth to 0 and currentY to group.contentHeight. This way, the next character is added into the next line.

Guess you’re right, this whole thing might be doable with pure Corona, even though there is - of course - a lot of improvements and work to do.

For the moment I am happy though that for a longer uniString in your examle I did get a multiline result with smiling emojis. Good night :slight_smile:

After the alignment issues, the sheer number of emojis to map is daunting.  I do suspect however that someone out there must have made a collection of emojis or a sheet that is ordered to match the standard emojis list (assuming there is such a thing.)

Whatever the case, hacky solution is far from perfect, but I do think it will make a first version that can be refined over and over to suit your needs.

Good luck with this.

Played around with it this morning and I am facing tthree main issues here:

  1. A user types in an emoji that his keyboard offers. Later, this emoji is replaced by the corresponding emoji from an image sheet. They do look similar, but I do see young people claiming: “Wait a minute, that’s not exactly what I typed”

  2. y-Alignment within one line: display.newText(…) and display.newImageRect are perfect on the same y-level in simulator, but accross testdevices they’re not. I know this has something to do with rendering of the newText, and if I remember correctly you can never tell the exact y-position of a text-element.

  3. Each input string right now is split into a display-object for each and every character (char or emoji). So, for an input string like “Hello :slight_smile: how are we doing today ;)” we get like 30 display-objects. Now, I have a scrollview that shows many of those input strings, so I’d get 300 objects (lets keep it simple) for ten strings. At least to me this seems like this is not good at all memory-wise.

Any ideas how this could be approached?

  1. once you have built your row or the whole paragraph just take a snapshot of it and use the snapshot in the scrollview

…and then remove all the other objects that have been created before I guess!

Good point Arteficio, that would indeed be an option to solve at least problem nr. 3 :slight_smile:

Thank you for your input :slight_smile:

Hi again.  That was about all I had to offer.  

re: #2 - Yes.  y-alignment will be an issue. This is why I suggest using and then modifying an existing Bitmapped text library (PonyFont or other) instead of plain text.  Bitmapped fonts should all be pre-aligned.

#3 I did letter by letter, but you can actually accumulate all letters and spaces into one text object till you hit an emoji.

My plain text hack is just a starting-point/concept for a real solution.

I skyped with Hassaan today and we discussed the whole thing in detail. He agress that your suggestion of using a bitmap font might be my best option here. I already downloaded PonyFont, found a .ttf file with all emojis in there (EmojiOne 4.0) and am currently trying to make bitmap font out of their ttf using Glyph Designer. Right now I’ve now idea how to get this work, but I’ll get there :slight_smile: