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

I am not suggesting you use PonyFont unmodified.

I do not think you will be able to get colored Emojis and bitmapped fonts.

You will need to modify PonyFont to identify emojies and instead of using a letter texture, use a emoji image from the sheet like I did.

i.e. You probably cannot have a single sheet of letters and emojis.  No tool for creating Font Sheets that I know of works that way.

Final note regarding y-align.

Did you try to use baselineOffset?

https://docs.coronalabs.com/api/type/TextObject/baselineOffset.html

With that info you may be able to get by with my hacky solution and the addition of taking a snapshot to reduce the number of total objects.

Ok then, seems like I misunderstood something. Wow, this whole thing is like the biggest challenge for me so far in Corona. Guess I’ll take a deep look into PonyFont and try to modify it the way you suggested.

And - concerning your second question: Yes, I came accross baselineOffset today, but then switched to the bitmap font solution. I now have those two options, we’ll see if I can make one work :slight_smile:

I’d experiment with baselineOffset first.
 
That is a few minutes of work.
 
Modifying PonyFont will be much more time.
 
 
Adding baselineOffset should literally be changing this:
 

tmp = display.newText( text, string.char(codepoint), curX, 0, font, fontSize ) tmp:translate( 0, tmp.baselineOffset )

to this:
 

tmp = display.newText( text, string.char(codepoint), curX, 0, font, fontSize ) tmp:translate( 0, tmp.baselineOffset )

PASTED WRONG CODE ORIGINALLY

Ugh, now I am curious and have to find out… I’ll check back in a bit.

OK, I’m not 100% sure, but this might be it:

tmp = display.newText( text, string.char(codepoint), curX, 0, font, fontSize ) tmp:translate( 0, tmp.baselineOffset \* tmp.anchorY )

I have not tested this on device.

Here is an update to my demo (I used a TTF to test):

https://github.com/roaminggamer/RG_FreeStuff/raw/master/AskEd/2018/10/emojis.zip (same link; download again)

I also tweaked the function to allow you to provide an ‘alternate’ emoji’ size to make the fit better.

I tried it with baselineOffset * 0.5 this morning, not really understanding why the *0.5 needed to be included, but your  * tmp.anchorY now makes sense. Yes, it does work on my Huawei P20. The emojis are a little bit bigger than a single char, but their y-alignment looks good and I guess the size for emojis should be easily customizable (fontSize * 0.9 or something).

One step closer :slight_smile:

So, right now I am thinking of providing two imageSheets: One with iOS emojis, one for Android (ignoring copyright for the moment, I’ll check that out later). Same mapping table though. This way, iPhone user would see their apple emojis, android users would receive the same emoji in their familiar Android way.

So, step by step :

  1. User types in using the standard iOS or Android keyboard into a native.newTextBox()

  2. User sees his “normal” emojis since its all native. String is stored in a module

  3. In other scenes I fetch the string and shoot it into your newEmojiText function.

  4. Within this function, the string is split into single elements (chars and emojis)

  5. for chars, use display.newText, for emojis use a spriteSheet (iOS or Android) and newImageRects.

  6. add those chars and symbols next to each other, or if end of line (specified limit) is reached, change curY and reset curX to 0.

  7. This way, all emojis are presented the way the user is familiar with and all should look fine.

Sounds not too bad to me. I’ll send another beer via paypal if that works out  :slight_smile:

Good idea on the per-platform emoji sheets.  Remember to make one of them the default in case of a failure to identify the OS.

I keep thinking there should be some clever way to get the emojis by downloading them or something, but making a sheet or finding one is probably the safest bet.

Of course, the remapping is going to be a hassle if nobody has a ordered sheet already.

Tip: Don’t forget to provide a nice way for handling ‘unknown’/‘unmapped’ emojis IDs.  Right now I just make a unfilled white rectangle, but you might want to fill with a transparent texture or default texture instead.

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.