Pixel Font module

Monkey Archive Forums/Monkey Projects/Pixel Font module

chrisc2977(Posted 2014) [#1]
PixelFont is my attempt at a different approach to text/fonts in monkey.

Html5 Example: http://www.challenger-arts.com/html5/pixelfont/

Features so far
Draws text fast - on the above example I was getting 230+fps on local html5
Use mojo's SetColor() to set font drawing colour - got this working fast, even on html5! (see example)
Smooth rendering - Uses alpha - does NOT use readpixel
Multiline text
Vertical text
Align Left, center and right - Even for vertical text
Included is a basic homemade convertor for ttf/oft fonts

If your interested downlad it here:
http://www.challenger-arts.com/wp-content/uploads/pixelfont.zip

Thoughts / Comments / Issues?


chrisc2977(Posted 2014) [#2]
I should Probably mention basic HowTo:

Global MyFont:PixelFont = New PixelFont(FilePath.txt)

SetColor(Whatever)
MyFont.DrawText(Text, X, Y, Align=0, Vertical=0)
'Align - 0:Left  1:Centered  2:Right
'Vertical - 0:Normal Left to right  1: Vertical Down to up

MyFont.TextWidth(Text)

MyFont.TextHeight(Text)



therevills(Posted 2014) [#3]
Wow, now thats thinking outside of the box!

I'm going thru your module trying to understand it.

1. You convert the TTF to a massive text file containing the name of the TTF, size and the character data
2. You load the text file into MonkeyX where you parse the file and store them into arrays
3. You draw the line of text on screen using writepixels and the data stored in the arrays
4. You store the image you have created for future use

When you are creating the image, does it draw it to the screen and then store it? If so, how do you deal with a busy background?

Any chance that you can share the code you created for the TTF to text file?


ziggy(Posted 2014) [#4]
Nice ideas indeed. I like how it can pre-cache lines of text but, have you considered a way to prevent filling the whole vram on small devices? Also, while the idea behind is nice, I see the problem that rendering speed is not predictable when a text that has never been drawn before gets drawn. So I see like a good complementary algorithm to a regular atlas based text drawing routine. As an example, making a game that shows a timer with a good resolution could just be slowing down because it's creating and pre-rendering images at almost every frame! also, not always lots of images from different textures are drawn faster than much more images from the same texture, so it'll depend on the number of characters being rendered, and the ability of the underlying renderer to handle coloring of textures, changes of contexts and big amounts of images.
I would suggest you to also add a function to draw uncached text for the situations where there's a highly dynamic text on screen, or the situations where you have limited VRam


therevills(Posted 2014) [#5]
I would suggest you to also add a function to draw uncached text for the situations where there's a highly dynamic text on screen, or the situations where you have limited VRam

Yeah good point, should be pretty easy to add.
Method DrawText(Text:String, X, Y, Align = 0, Vertical = 0, cacheImage:Bool = True)

And in FastDrawLine:
Method FastDrawLine:Image(Line:String, cacheImage:Bool = True)



chrisc2977(Posted 2014) [#6]
@therevills (1st post)
Yes thats all correct, when creating the font it draws each char to the backbuffer (white text on black) reads the pixels and sets the alpha based on level of red. I will include bmx file on next upload if you want. Im also looking at ways to reduce the file size :)

@ziggy
I agree vram is a bit of an issue :s will have a think on that. A for creating a new line, I THINK, although unpredictable, creating a previously unwritten line of text isnt much slower than the std method of drawing single chars to the screen. The problem occurs when the lists start filling up and it has to search though a load to find the correct string.
But I def agree especially for ever changing text that an uncached method would be good (easily done and I did this when I was testing different ways).

Cheers guys, a bit more work to do, but im glad you like the principles, the main goal was to have setColor() on all platforms. Am thinking about having different chardata for colours used. dunno.


Nobuyuki(Posted 2014) [#7]
I agree vram is a bit of an issue :s will have a think on that. A for creating a new line, I THINK, although unpredictable, creating a previously unwritten line of text isnt much slower than the std method of drawing single chars to the screen. The problem occurs when the lists start filling up and it has to search though a load to find the correct string.


could be one particular place where a splay tree automatically pruned to a set maximum might improve performance, if you really need to cache stuff like that.


ziggy(Posted 2014) [#8]
I think this can't be right:
	Method CreateLineImage:Image(Line:String)
		Local Img2:Image = CreateImage(Line.Length * FontHeight, FontHeight)
		Local XPos = 0
		For Local i:Int = EachIn Line.ToChars()
			Local Data:Int[] = ChangeColor(CharData[i])
			Img2.WritePixels(Data, XPos, 0, FontHeight, FontHeight)
			XPos = XPos + CharWidth[i]
		Next
		Return Img2
	End Method

Why are you using the FontHeight * Line.Length to determine image size? Each character can have different width, and there are fonts that can be wider than higher, also, you could be reserving too much room and wasting resources in narrow or condensed fonts

Also,how are you handling fonts with overlapping characters like this one:
http://www.dafont.com/en/holiday-home.font?text=Overlapping+Chars&psize=l&back=theme (btw, this forum does not like this link? )
I see no way a difference between char drawing width and char horizontal kerning, it would be nice if the font description that is generated could provide this information or, if you're not doing it like this way, is there anything I'm missing? I'm curious, and I haven't bothered reading the txt font parser, as it seems there are too many numbers for my liking (like in FontMachine!)


therevills(Posted 2014) [#9]
Also,how are you handling fonts with overlapping characters like this one:

Quick screenie:


Yeah its not perfect, how does FontMachine handle them?

Also if you haven't seen I've created a module to load in TTF for HTML5 and Android, but a full target solution would be great:
http://www.monkeycoder.co.nz/Community/posts.php?topic=8190


ziggy(Posted 2014) [#10]
Yeah its not perfect, how does FontMachine handle them?
By measuring character spacing in addition to character face image size. Current implementation does not support for negative values on character spacing, so overlapping that happens on the left of characters could get cut and I'm working on a fix for the next editor version, but more regular ones (at the right of each character) are currently properly measured so script with legatto fonts are supported. You need to keep separate information for both the image itself, and the offset at which next character is meant to be drawn.

EDIT: Here's an old sample of a script font under FontMachine http://www.jungleide.com/fontmachine/demo01/MonkeyGame.html


chrisc2977(Posted 2014) [#11]
Right! Thank for all the into and links :) will have a proper look at all this tonight :D
I feel inspired to make this much better lol


chrisc2977(Posted 2014) [#12]
God ttf files are a pain!
Im trying to parse one just using monkey ... So far I have just managed to extract the directory table and successfully listed all the table tags... only took a bunch of research and around 4 hrs :( This was done in html.
Am I right in thinking ':DataStream' works on all targets? if so. good.


ziggy(Posted 2014) [#13]
God ttf files are a pain!
Yes they are! If you're taking thi route, you'll find (if I don't recall wrong) there are several formats for subpixel interpolation. I would plainly ignore this part if you really don't need this


chrisc2977(Posted 2014) [#14]
At the moment I can read the table tags, but their offset appears wrong :( maybe something to do with PeekInt instead of Long datatype :s


chrisc2977(Posted 2014) [#15]
Just figured out how the ttf file works, The numbers are unsigned and the bytes for the numbers are in reverse order!


chrisc2977(Posted 2014) [#16]
Managed to get as far as loading up all the data in the 'name' table - this was in html so I think it should work on most targets?
I managed to make functions to extract unsigned floats and ints from a Big edian datasrteam! :)
Tested with oft and ttf files
Next is to extract the other tables and try to do something with them... :s



ps. screenshot was a test using a completly unmodified .ttf plonked straight into the data folder :D


DruggedBunny(Posted 2014) [#17]
Nice work... I fear your task is monumental if you're going to try and render TTF by hand, but that's still an impressive result for only a few hours' effort! I wouldn't even have thought we could do such low-level file-parsing in HTML5... hmmm...


chrisc2977(Posted 2014) [#18]
Just about... Im pretty much at the limit if not beyond my skill level lol I diddnt even know what an unsigned int was before today! Learning as I go.
All kudos to monkey for being so versitile :)
Ill post code when I have the file completly parsed, so hopefully if I cant work out how to draw the font, all the data will be availible from the file for someone more skilled to have a go!


Beaker(Posted 2014) [#19]
Hi Chris - your demo in the first post repeatedly crashes Safari on my iPad 2 after running for about 10 seconds. Runs about 8 fps. I rarely see a crash like this on iOS.


chrisc2977(Posted 2014) [#20]
@Beaker - I must have a leak somewhere :s, thanks


nikoniko(Posted 2014) [#21]
Hi,

What is about international chars?

I generated font from arial.ttf, put some russian text to TextOut() and got error in the browser

Monkey Runtime Error : Array index out of range
D:/MonkeyPro/NikoGames/pixelfont/Examples/pixelfont.monkey<110>
D:/MonkeyPro/NikoGames/pixelfont/Examples/pixelfont.monkey<63>
D:/MonkeyPro/NikoGames/pixelfont/Examples/Test.monkey<39>
D:/MonkeyPro/modules/mojo/app.monkey<113>





chrisc2977(Posted 2014) [#22]
Heres the code so for for parsing a ttf file.
http://www.challenger-arts.com/wp-content/uploads/TFont.zip

tables done are: name, head, cmap
I am really struggling to get the right glyph offsets/table data, but the rest seems to load ok :(


chrisc2977(Posted 2014) [#23]
Just managed to get it to retrieve the correct Glyph Id for a given unicode :)
I was having loads of trouble until I noticed I was feeding it int char codes lol not unicode chars. *Oops
I re-wrote the parser as well, including a 'Datastream' Class and improved the way it handles unsigned data
Next will be to get the glyph Id fot each glyph, find its location in the loca table and retrieve the x and y points in the glyph table.

Anyone got any ideas on how to handle the xy point data and to create letter filled shapes??? bezier curves?

Getting there slowly


therevills(Posted 2014) [#24]
These pages might help:

http://www.microsoft.com/typography/otspec/TTCH01.htm
http://www.microsoft.com/typography/otspec/glyf.htm


CopperCircle(Posted 2014) [#25]
Hi Chris, check out my SVG parser, it has some functions for drawing the points, I have been experimenting with rendering SVG fonts.

http://www.monkeycoder.co.nz/Community/posts.php?topic=7898


chrisc2977(Posted 2014) [#26]
Thanks guys, very helpful :)
Im at a stage now where I can render the outline points onto the screen :)
I.e I have successfully managed to extract char glyph data :D and it gives me corner points / curve points for each letter
Did not think I would get this far at all!


chrisc2977(Posted 2014) [#27]
Quick Screenie showing the char verticies and joined by 'DrawLine'
(Not the final way the ttf chars will be drawn, just a representation showing that the verticies are in the right place)




chrisc2977(Posted 2014) [#28]
Ok... Progress!
I have just managed to get it to work out the angles of all the points in the outline of each glyph and work out if they are interior/exterior angles (convex/concave)
ready for triangulation :D
I think I have set up the contours right for triangulation that will exclude interior contours... I think...

I also re-wrote the datastream class, and altered the font loading code, so the entire font loads almost instantly (all glyphs, their outline data and working out the angle stuff)


therevills(Posted 2014) [#29]
Excellent! Looking forward to see how you progress with this... I looked at the TTF format and decided it wasn't for me to even try to load it into MonkeyX, so I congratulate you on your work so far :)


DruggedBunny(Posted 2014) [#30]
Wow, I'm amazed even at that... really well done.


chrisc2977(Posted 2014) [#31]
A bit of a breakthrough :)

Image below shows text drawn from three different ttf files loaded directly from the data folder at runtime :) no external program needed to convert beforehand!
Also , as you can see from the middle font it also handles left side bearing and advance width :)



As you can see in the top font there is a slight line where they are triangulated, think this is due to antialias? will have to look into this.

Next up is to use a bezier curve style method to smooth the rounded edges.

At the moment this can only handle ttf outlines and NOT otf outlines hopefully I can implement this too at some point


ziggy(Posted 2014) [#32]
That's awesome! keep up the good work! Are you handling UTF chars in any way? Great module!


therevills(Posted 2014) [#33]
Excellent! What targets will support this in the end?


chrisc2977(Posted 2014) [#34]
Thanks :)

@ziggy
I think it handles UTF, from my limited understanding. Depending on the ttf file obiously, it looks up the glyphId from extended ascii , I managed to get the copyright symbol to display, is that utf?

@therevills
The only imports are mojo and brl.databuffer
Is this compatible with most targets? (I purposely DID NOT use datastream)


ziggy(Posted 2014) [#35]
Try some japanese text on a font that supports it: 世界こんにちは!(that's hello world!)


chrisc2977(Posted 2014) [#36]


Starting to look like it should now :)
I really want to have a crack at otf fonts and ttf fonts with ot outlines before release, as currently this only loads ttf files with tt outlines.
Also looks smooth as hell on flash and html, but due to triangulation of concave polys it looks a bit bitty on other targets. Dont know what to do about this.

Next update I will probably start a new thread as the heading 'pixel font' is a bit misleading now.


therevills(Posted 2014) [#37]
Top job :) Any chance of the source code? I'm very interested in seeing it running on my Android devices.


chrisc2977(Posted 2014) [#38]
http://www.challenger-arts.com/wp-content/uploads/tfont.zip

For flash and html5 I know I can use the inbuilt drawpoly without my triangulation, but I wanted you to see how much faster it is to triangulate on html5/flash rather than android.
It takes 600ms on html and 26000ms on my android. And I dont know why :(

Other than that. I have a working ttf loader and drawer. How to below.

Import tfont
Global MyFont:TFont

MyFont = New TFont(Path:String, PixelHeight,[R, G, B])

MyFont.DrawText(Text:String, X, Y, CenterX=0, CenterY=0, AdditionalLetterSpace=0, AdditionalLineSpace=0)

MyFont.TextWidth(Text:String, AdditionalLetterSpace=0)

MyFont.TextHeight(Text:String, AdditionalLineSpace = 0)


Any help appreciated.


ziggy(Posted 2014) [#39]
Are you creating garbage on the calculations? This could be the first thing to look at.


Samah(Posted 2014) [#40]
There's an insane amount of Lists being created there, and some programming patterns that can be done much more efficiently without resizing arrays. I had a quick go at changing these and managed to halve the time on HTML5 (haven't tried Android yet). I'm pretty sure if I have some time tonight I could cut that down to a few milliseconds on both targets.

Something else you should do is to avoid the List class like the plague. Use Stack instead, because it uses an internal array rather than node objects.

Personally I wouldn't bother splitting xyList into XArr and YArr, I'd just index it with [i*2] and [i*2+1].

Reversing the arrays can be done like this:
For Local i:Int = 0 Until XArr.Length / 2
	Local temp:Float = XArr[i]
	XArr[i] = XArr[XArr.Length-i-1]
	XArr[XArr.Length-i-1] = temp
	temp = YArr[i]
	YArr[i] = YArr[YArr.Length-i-1]
	YArr[YArr.Length-i-1] = temp
Next


Edit: Summary:
* Don't split xyList, just access it with [i*2] and [i*2+1].
* Don't reverse the array, just loop backwards when drawing it.
* Don't shrink the array when removing points, just keep track of how many elements are in use.
* Use Stack anywhere you would have used List.
* Avoid Resize as much as possible. Take a guess at the size and Resize*2 if it needs to increase (Stack will do this for you automatically).

Edit 2:
I went through and rewrote a heap of the tesselation stuff and managed to get about a 5x speed improvement on HTML5. Android is still a bit slow, but not unbearably so. I'm sure there's more optimisation I can do.


Aman(Posted 2014) [#41]
Might not be that effective but you could try it and see if it would help or not.

instead of this:
'Set Alpha & Color
For Local i:Int = 0 Until pixels.Length
	Local argb:Int = pixels[i]
	Local a:Int = (argb Shr 24) & $ff
	Local r:Int = (argb Shr 16) & $ff
	Local g:Int = (argb Shr 8) & $ff
	Local b:Int = argb & $ff
	a = 255 - r
	r = Color[0]
	g = Color[1]
	b = Color[2]
	argb = (a Shl 24) | (r Shl 16) | (g Shl 8) | b
	pixels[i] = argb
Next

you could have this:
'Set Alpha & Color
For Local i:Int = 0 Until pixels.Length
	'if pixel is white, then make it transparent, otherwise set the color
	if pixels[i] = $FFFFFFFF Then pixels[i] = $00000000 Else pixels[i]=$FF000000 | (Color[0] Shl 16) | (Color[1] Shl 8) | Color[2]
Next


Great work by the way.


CopperCircle(Posted 2014) [#42]
This is great and coming along really well, good work!

Edit:
Having a look through the code it seems you are scaling the matrix when drawing the polys to get the correct font size, this is giving the fonts a jagged edge. Maybe we could look at a way to scale and reposition the poly so every stays smooth?


chrisc2977(Posted 2014) [#43]
Wow Samah! 5x, using your suggestions I managed to get 2x and cound not fathom how to do better than that :)
I dont suppose you could let me have the code :) ill be stumbling around for weeks otherwise!

I also did a test, seeing the time it took for android to only parse the ttf file and it looks like its struggling (thats without triangulating and drawing to image) :s so there's something in there that android doesnt like! I will have to look deeper into this now.

Thanks for the invaluable help all!


Nobuyuki(Posted 2014) [#44]
@297chrisc

if there's 2 things android doesn't like, it's 1. creating tons of new objects in block scope only to have to run a GC inside a loop, and 2. string manipulation outside of a stringbuilder. Make sure you're not playing games with immutable strings by using monkey's built-in operator appending in a loop, things like that. Each time you modify a string, you create a new one (because strings are immuatable), and that means doing it in a loop goes back to problem 1. More thrash causes runaway load times. Instead, in some of those places, if you have raw char data, manipulate an int array instead, and build a string FromChars at the end.


Samah(Posted 2014) [#45]
@297chrisc: I dont suppose you could let me have the code :) ill be stumbling around for weeks otherwise!

https://dl.dropboxusercontent.com/u/20511758/tfonttesselate.monkey
:)

Edit 2: Cleaned up source code and updated link.

Summary of changes:
Triangles is now a Stack instead of a List
Removed XArr and YArr arrays.
CleanUp has been renamed to BuildPoints and pushes all the X,Y values to a global Stack called "points".
Clockwise is now calculated before BuildPoints so that the points can be added in reverse (rather than reversing the list afterward).
All references to XArr and YArr now reference points.Get(p*2) and points.Get(p*2+1) instead.

Note that I made points Global because it's unused once the Triangles list is built, so we can reuse it later without having to create another Stack.


chrisc2977(Posted 2014) [#46]
Ok, I have rewritten a bit of the loading stuff.
html and flash is now smooth like the image posted #36
I have greatly reduced Parsing times : for loading a singe font is around:
html5: 41ms
flash: 81ms
desktop: 10ms
android: 600ms

which is now acceptable ( just about for android, but I cant get it any lower lol)

All that remains is the tessalation/triangulation for non html/flash targets.
I have been trying most of day with no sucess :( it either doesnt work or is unbearably slow.

[Edit]
Just seen a post from Samah, Works like a charm :) Thank you!!
Android now loads a font in around 4 secs (it was 26!)

I will put an update up tomorrow evening


therevills(Posted 2014) [#47]
Just to highlight in case you didn't see it, Samah updated his post again (I reckon another post would have been fine and easy to spot ;)):
Samah writes
https://dl.dropboxusercontent.com/u/20511758/tfonttesselate.monkey
:)

Edit 2: Cleaned up source code and updated link.

Summary of changes:
Triangles is now a Stack instead of a List
Removed XArr and YArr arrays.
CleanUp has been renamed to BuildPoints and pushes all the X,Y values to a global Stack called "points".
Clockwise is now calculated before BuildPoints so that the points can be added in reverse (rather than reversing the list afterward).
All references to XArr and YArr now reference points.Get(p*2) and points.Get(p*2+1) instead.

Note that I made points Global because it's unused once the Triangles list is built, so we can reuse it later without having to create another Stack.




chrisc2977(Posted 2014) [#48]
Creating new thread due to 'pixel font' title not really applicable
http://www.monkey-x.com/Community/posts.php?topic=8470#86837