True Type font module

Monkey Archive Forums/Monkey Projects/True Type font module

chrisc2977(Posted 2014) [#1]
Loads and parses a true type file at runtime within monkey




Download Link
Here

Currently supports ttf files with true type outlines only.

Features
* Set colour of font at load point
* Set additional letter spacing at drawtext
* Set additional line spacing at drawtext
* Center text x and y
* TextWidth and TextHeight method
* Uses characters advance width and left side bearing
* SHOULD work on all targets - Tested on html5, flash, desktop and android
* Supports "~n" as a new line (horizontal squiggle (asciitidal))n

Still to do:
* ttf files with open type layout
* OpenType file support
* Compound glyfs
* Oriental character support
* Speed up android somehow



Methods

'Import module
Import tfont

'Declaration of your font
Global MyFont:TFont

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

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

'Return text width of a given text (multi line included)
MyFont.TextWidth(Text:String, AdditionalLetterSpace=0)

'Return text Height of a given text (multi line included)
MyFont.TextHeight(Text:String, AdditionalLineSpace = 0)



therevills(Posted 2014) [#2]
Top job! I'll have a play tonight.


Supertino(Posted 2014) [#3]
Was following this like a ninja in the other thread, ill check it out.


Difference(Posted 2014) [#4]
Exellent! Really usefull.

Feel free to take a look at http://www.monkey-x.com/Community/posts.php?topic=8035 to see if there's anything you can use.
(I'm looking forward to maybe experimenting with a vector only version of this.)


SLotman(Posted 2014) [#5]
So does this works on all targets? So cool!
Have anyone done some speed tests to see how fast is it?
Can you change font color on the fly?

Maybe it could use native HTML5 functions on that TARGET... I know it is possible there, because I've done it in the past

Here, found my html5 loading code 'hack', from my own HTML engine:


Then you just do something like this to write it:



chrisc2977(Posted 2014) [#6]
@SLotman
This *Should* work on all targets, ive tested and is working on html,flash,desktop,and android.
Speedwise, loading times are aroung the 40ms per font mark - apart from android, which I have been unable to speed up loading more than 500ms per font :(
Rendering time will be around the same as other bitmap font modules as it converts it to bitmaps.
Setting the colour is done when you load the font, so not on the fly unfortunatly.

I did have a look at trying to use external libraries for each target, although started with android as html5 runs fast as is, but I was getting nowhere as I could not work out androids api combined with extern etc.

I tried for a couple of days to understand opentype table formats to (that would allow opentype and truetype with postscript outline files), but was just not intelligent enough to get it!

So unfortunately unless a genius comes along, and takes this a couple more steps further I am at a dead end :(
Incidenty if anyone can further this, please do! Full Truetype/opentype support is a massive hole missing in monkey, and you have my unconditional consent to use copy etc any of my source!


Goodlookinguy(Posted 2014) [#7]
If you're using WritePixel on Android, take a look at Danilo's post here: http://www.monkey-x.com/Community/posts.php?topic=8505&post=87728 | Changing Bind to Bind2 seemed to solve the horrendous speed issues.

So unfortunately unless a genius comes along, and takes this a couple more steps further I am at a dead end :(


Don't put yourself down. You did a good job having never worked with fonts before. I don't think a genius is needed, more like someone knowledgeable in vector graphics and fonts is needed to further it. Either that or someone really dedicated to just keep pushing until they solve it.


therevills(Posted 2014) [#8]
I agree, don't put yourself down - you have done a super job!

I've been meaning look into integrating my TTF module for Android and your version, but like always I don't find the time. My TTF module uses the native Android function to load a TTF in, draw it to the screen via native functions and from there I capture it as a bitmap font, so it should be possible and because it is using native functions it is really quick to load.
http://www.monkey-x.com/Community/posts.php?topic=8190

@Slotman, in my TTF module I added a prototype TTF loader for HTML5 and it works with IE 10 & 11.


V. Lehtinen(Posted 2014) [#9]
Never mind, I sorted it out.. :)

EDIT:
Actually.. While trying to find a good font for a console, I noticed that many fonts are missing the capital letter "i" (comes out as a square)...? Also, there's no way to turn anti-aliasing off, which is sad with pixelated fonts.... But ...any updates soon?


Arabia(Posted 2014) [#10]
Just having a play with this, I get an Array index out of range error when loading the Impact TTF. The fonts included with the module work fine though.


Alex(Posted 2014) [#11]
The link doesn't work. Could you please fix?
I can mirror!


Landon(Posted 2014) [#12]
Also i made an online monkey font generator.

http://www.monkey-x.com/Community/posts.php?topic=8897

direct link to the font generator

http://vigilsoft.net/monkeyfonts/monkeyfontgen.php


therevills(Posted 2014) [#13]
@Alex
Here's my copy of it:
https://dl.dropboxusercontent.com/u/35103024/tfont.zip

Also I've created a GoogleCode Repo here. this work is too good to go missing:
https://code.google.com/p/monkey-ttf-module/

@Chris - If you want me to remove this repo please say so, also if you want to be the owner let me know and I'll add you to the project.

@Landon
This isn't quite the same, this module loads in true TTF files into Monkey to render them.


Soap(Posted 2014) [#14]
Thank you for mirroring it! I agree that the work is too good to have vanish.


Alex(Posted 2015) [#15]
@therevills

Thank you! Great job, works like a charm!
The outline and shadow would be great ;)


Hezkore(Posted 2015) [#16]
I've added my own little "smooth font" flag which by default is true, but can be turned off to use a much sharper scaling method.
It's very handy when using smaller fonts that would otherwise turn out blurry.

Here's an example:


So the new load function works like this:
MyFont = New TFont(font, size,[r,g,b],smooth)
Where "smooth" set to "False" obviously will produce the sharp edges.

Also!
#TEXT_FILES+="*.ttf"
Doesn't actually append to TEXT_FILES, it replaces it entirely, causing a few issues.
So you'll want to place that in "Example.monkey" instead.

Here's the new tfont.monkey (With "HEZKORE" marking changes)
'#TEXT_FILES+="*.ttf" 'HEZKORE: This does not append, it replaces it
Import mojo
Import tfontdatastream
Import tfontpoly

Class TFont
	Field Stream:DataStream
	Field Size
	Field Color:Int[]
	Field Smooth:Bool 'HEZKORE: Smooth font
	Field Path:String
	
	Field OutlineType:String ' "TT" or "OT"
	Field GlyphNumber
	Field FontLimits[4]
	Field FontScale:Float
	Field LineHeight

	Field GlyphId:Int[]
	Field Glyph:TFont_Glyph[]
	Field ImagesLoaded:Bool = False
	Field FontClockwise:Bool
	Field ClockwiseFound:Bool = False
	
	
	'Load a new Font HEZKORE: Added smooth font flag
	Method New(Path:String, Size, Color:Int[] =[0, 0, 0],Smooth:Bool=True)
		Stream = New DataStream("monkey://data/" + Path, True)
		Self.Size = Size
		Self.Color = Color
		Self.Smooth = Smooth 'HEZKORE: Store smooth flag
		Self.Path = Path
		If Stream.Buffer = Null Then Error Path + " : Font file not found"
		
		'===== Read Offset Table =====
		Local sfntVersion = Stream.ReadFixed32()
		Local numTables = Stream.ReadUInt(2)
		Local searchRange = Stream.ReadUInt(2)
		Local entrySelector = Stream.ReadUInt(2)
		Local rangeShift = Stream.ReadUInt(2)
		If Int(sfntVersion) = 1 Then
			OutlineType = "TT"
		Else
			OutlineType = "OT"
		End If
		
		'===== Load Offset Table Records and get offsets ===== 
		Local cmapOffset, headOffset, hheaOffset, hmtxOffset, maxpOffset, nameOffset, glyfOffset, locaOffset, CFFOffset, VORGOffset
		For Local i = 0 To numTables - 1
			Local tag:String = Stream.ReadString(4)
			Local checksum = Stream.ReadUInt(4)
			Local offset = Stream.ReadUInt(4)
			Local length = Stream.ReadUInt(4)
			Select tag
				Case "cmap"
					cmapOffset = offset
				Case "head"
					headOffset = offset
				Case "hhea"
					hheaOffset = offset
				Case "hmtx"
					hmtxOffset = offset
				Case "maxp"
					maxpOffset = offset
				Case "name"
					nameOffset = offset
				Case "glyf"
					glyfOffset = offset
				Case "loca"
					locaOffset = offset
				Case "CFF "
					CFFOffset = offset
				Case "VORG"
					VORGOffset = offset
			End Select
		Next
		
		
		'===== Peek some font data =====
		GlyphNumber = Stream.PeekUInt(2, maxpOffset + 4)
		FontLimits[0] = Stream.PeekInt(2, headOffset + 36) 'xMin
		FontLimits[1] = Stream.PeekInt(2, headOffset + 38) 'yMin
		FontLimits[2] = Stream.PeekInt(2, headOffset + 40) 'xMax
		FontLimits[3] = Stream.PeekInt(2, headOffset + 42) 'yMax
		FontScale = (Size * 1.0) / (FontLimits[3])' - FontLimits[1])
		LineHeight = Size
		Local LocaFormat = Stream.PeekInt(2, headOffset + 50)
		Local HMetricsNumber = Stream.PeekUInt(2, hheaOffset + 34)

		'===== Setup Glyph Arrays =====
		GlyphId = New Int[1000]
		Glyph = New TFont_Glyph[GlyphNumber]
		For Local i = 0 To GlyphNumber - 1
			Glyph[i] = New TFont_Glyph
		Next
		
		'===== Load Big Data =====
		LoadMetrics(hmtxOffset, HMetricsNumber)
		LoadCmapData(cmapOffset)
		LoadLoca(locaOffset, LocaFormat, glyfOffset)
		
		'Load glyph data
		For Local g = 0 To GlyphNumber - 1
			'Load Points
			LoadGlyfData(g, Glyph[g].FileAddress)
			QuadGlyph(g)
			SmoothGlyph(g)
		Next
		
		'Make Poly
		For Local g = 0 To GlyphNumber - 1
			Glyph[g].Poly = New TFont_Poly[Glyph[g].ContourNumber]
			For Local c = 0 To Glyph[g].ContourNumber - 1
				Glyph[g].Poly[c] = New TFont_Poly(Glyph[g].xyList[c], FontScale)
				If Glyph[g].ContourNumber = 1 And Not ClockwiseFound Then
					FontClockwise = Glyph[g].Poly[0].Clockwise
					ClockwiseFound = True
				EndIf
			Next
		Next
		
		
	End Method
	
	
	Method DrawText(Text:String, x, y, CenterX = 0, CenterY = 0, AdditionalLetterSpace = 0, AdditionalLineSpace = 0)
		'Load Font
		If ImagesLoaded = False Then
			LoadGlyphImages()
			ImagesLoaded = True
		End If
		
		'Draw Lines of Text
		Local X = x
		Local Y = y
		
		If CenterY = 1 Then
			Y = Y - Self.TextHeight(Text, AdditionalLineSpace) / 2
		End If
		
		For Local L:String = EachIn Text.Split("~n")
			For Local c = EachIn L
				Local Id = GlyphId[c]
				Local tx = X + Glyph[Id].xMin * FontScale
				Local ty = Y - (Glyph[Id].yMax * FontScale) + LineHeight
				
				If CenterX = 1 Then
					tx = tx - (Self.TextWidth(L, AdditionalLetterSpace)) / 2
				End If
				
				If c > 32 Then
					If Glyph[Id].Img <> Null Then DrawImage(Glyph[Id].Img, tx, ty)
				End If
				X = X + Glyph[Id].Adv * FontScale + AdditionalLetterSpace
			Next
			
			X = x
			Y = Y + LineHeight + AdditionalLineSpace
		Next
		
		
		
	End Method
		
	Method TextWidth(Text:String, AdditionalLetterSpace = 0)
		Local Width = 0
		For Local L:String = EachIn Text.Split("~n")
			Local TempWidth = 0
			For Local c = EachIn L
				Local Id = GlyphId[c]
				TempWidth = TempWidth + (Glyph[Id].Adv * FontScale) + AdditionalLetterSpace
			Next
			If TempWidth > Width Then Width = TempWidth
		Next
		
		Return Width
	End Method
	
	Method TextHeight(Text:String, AdditionalLineSpace = 0)
		Local Height = 0
		Local Lines = (Text.Split("~n")).Length
		Return (Lines * LineHeight) + (Lines * AdditionalLineSpace)
	End Method
	
	
	
	
	
	Method LoadMetrics(Offset, HMetricsCount)
		Stream.SetPointer(Offset)
		Local Count = 0, LastAdv
		For Local i = 0 To GlyphNumber - 1
			If Count < HMetricsCount - 1 Then
				Glyph[i].Adv = Stream.ReadUInt(2)
				'HEZKORE: Quick fix for some FontScale
				If Glyph[i].Adv<=0 then Glyph[i].Adv=1024
				Glyph[i].Lsb = Stream.ReadInt(2)
				LastAdv = Glyph[i].Adv
			Else
				Glyph[i].Adv = LastAdv
				Glyph[i].Lsb = Stream.ReadInt(2)
			End If
			Count = Count + 1
		Next
	End Method
	Method LoadCmapData(Offset)
		Stream.SetPointer(Offset)
		Stream.ReadUInt(2)
		Local numTables = Stream.ReadUInt(2)
		Local PlatformId[numTables]
		Local EncodingId[numTables]
		Local TableOffset[numTables]
		
		'Load all tables and select windows format
		For Local t = 0 To numTables - 1
			PlatformId[t] = Stream.ReadUInt(2)
			EncodingId[t] = Stream.ReadUInt(2)
			TableOffset[t] = Stream.ReadUInt(4) + Offset
		Next
		
		'Load 3-1 first then other tables
		Local WindowsFontFound:Bool = False
		For Local t = 0 To numTables - 1
			If PlatformId[t] = 3 And EncodingId[t] = 1 Then
				WindowsFontFound = True
				Local Format = Stream.PeekUInt(2, TableOffset[t])
				If Format = 0 Then LoadCmapTable0(TableOffset[t] + 2)
				If Format = 4 Then LoadCmapTable4(TableOffset[t] + 2)
				If Format = 6 Then LoadCmapTable6(TableOffset[t] + 2)
			End If
		Next
		
		'Load 3 - Any first Then other tables
		For Local t = 0 To numTables - 1
			If PlatformId[t] = 3 And EncodingId[t] <> 1 Then
				Local Format = Stream.PeekUInt(2, TableOffset[t])
				If Format = 0 Then LoadCmapTable0(TableOffset[t] + 2)
				If Format = 4 Then LoadCmapTable4(TableOffset[t] + 2)
				If Format = 6 Then LoadCmapTable6(TableOffset[t] + 2)
			End If
		Next
		
		'Load Any first Then other tables
		For Local t = 0 To numTables - 1
			If PlatformId[t] <> 3 Then
				Local Format = Stream.PeekUInt(2, TableOffset[t])
				If Format = 0 Then LoadCmapTable0(TableOffset[t] + 2)
				If Format = 4 Then LoadCmapTable4(TableOffset[t] + 2)
				If Format = 6 Then LoadCmapTable6(TableOffset[t] + 2)
			End If
		Next
		
		'Revert additional to 0 - Just to make Sure
		For Local i = 0 To GlyphId.Length - 1
			If GlyphId[i] > GlyphNumber - 1 Then GlyphId[i] = 0
		Next
	End Method
	Method LoadCmapTable0(Offset)
		Stream.SetPointer(Offset)
		Stream.ReadUInt(2); Stream.ReadUInt(2)
		For Local g = 0 To 254
			Local GId = Stream.ReadUInt(1)
			If GlyphId[g] = 0 Then GlyphId[g] = GId
		Next
	End Method
	Method LoadCmapTable4(Offset)
		Local OffCount = Offset
		Stream.SetPointer(Offset)
		Stream.ReadUInt(2); Stream.ReadUInt(2)
		Local SegCount = Stream.ReadUInt(2) / 2
		Local SearchRange = Stream.ReadUInt(2)
		Local EntrySelector = Stream.ReadUInt(2)
		Local RangeShift = Stream.ReadUInt(2)
		OffCount = OffCount + 12
		Local EndCount[SegCount]
		For Local s = 0 To SegCount - 1
			EndCount[s] = Stream.ReadUInt(2)
			OffCount = OffCount + 2
		Next
		Local Reserved = Stream.ReadUInt(2); OffCount = OffCount + 2
		Local StartCount[SegCount]
		For Local s = 0 To SegCount - 1
			StartCount[s] = Stream.ReadUInt(2)
			OffCount = OffCount + 2
		Next
		Local IdDelta[SegCount]
		For Local s = 0 To SegCount - 1
			IdDelta[s] = Stream.ReadInt(2)
			OffCount = OffCount + 2
		Next
		Local IdRangeOffset[SegCount]
		Local IdRangeOffsetOffset[SegCount]
		For Local s = 0 To SegCount - 1
			IdRangeOffset[s] = Stream.ReadUInt(2)
			IdRangeOffsetOffset[s] = OffCount
			OffCount = OffCount + 2
		Next
		'Get Glyph Id
		For Local Char = 0 To GlyphNumber - 1
			Local NullFlag = 1
			'GetSegment
			Local CharSeg
			For Local s = 0 To SegCount - 1
				If EndCount[s] >= Char Then
					CharSeg = s
					If Char >= StartCount[s] Then NullFlag = 0
					Exit
				End If
			Next
			If NullFlag = 1 Then
				GlyphId[Char] = 0
				Continue
			End If
			
			If IdRangeOffset[CharSeg] = 0 Then
				If GlyphId[Char] = 0 Then GlyphId[Char] = IdDelta[CharSeg] + Char
			Else
				Local Location = (2 * (Char - StartCount[CharSeg])) + (IdRangeOffset[CharSeg] - IdRangeOffset[0]) + OffCount + (CharSeg * 2)
				If GlyphId[Char] = 0 Then GlyphId[Char] = Stream.PeekUInt(2, Location)
			End If
		Next
	End Method
	Method LoadCmapTable6(Offset)
		Stream.SetPointer(Offset)
		Stream.ReadUInt(2); Stream.ReadUInt(2)
		Local FirstCode = Stream.ReadUInt(2)
		Local EntryCount = Stream.ReadUInt(2)
		For Local g = FirstCode To EntryCount - 1
			Local GId = Stream.ReadUInt(2)
			If GlyphId[g] = 0 Then GlyphId[g] = GId
		Next
	End Method
	Method LoadLoca(Offset, Format, GlyfOffset)
		Stream.SetPointer(Offset)
		For Local i = 0 To GlyphNumber - 1
			If Format = 0 Then
				Glyph[i].FileAddress = (Stream.ReadUInt(2) * 2) + GlyfOffset
			Else
				Glyph[i].FileAddress = (Stream.ReadUInt(4)) + GlyfOffset
			End If
		Next
	End Method
	Method LoadGlyfData(Id, Offset)
		Stream.SetPointer(Offset)
		'Load contour Number and Position
		Local ContourNumber = Stream.ReadInt(2)
		If ContourNumber < 1 Then Return 0
		Glyph[Id].ContourNumber = ContourNumber
		Glyph[Id].xMin = Stream.ReadInt(2)
		Glyph[Id].yMin = Stream.ReadInt(2)
		Glyph[Id].xMax = Stream.ReadInt(2)
		Glyph[Id].yMax = Stream.ReadInt(2)
		Glyph[Id].W = Glyph[Id].xMax - Glyph[Id].xMin
		Glyph[Id].H = Glyph[Id].yMax - Glyph[Id].yMin
		
		
		
		
		'End Points
		Local EndPoints:Int[ContourNumber]
		For Local i = 0 To ContourNumber - 1
			EndPoints[i] = Stream.ReadUInt(2)
		Next
		Local PointNumber = EndPoints[ContourNumber - 1] + 1
		
		'Instructions
		Local insLen = Stream.ReadUInt(2)
		Stream.ReadString(insLen)
		
		'Flags
		Local Flags:Int[][] = New Int[PointNumber][]
		Local ContinueNumber = 0
		For Local i = 0 To PointNumber - 1
			'Is The Same
			If ContinueNumber > 0 Then
				Flags[i] = Flags[i - 1]
				ContinueNumber = ContinueNumber - 1
				Continue
			End If
			'Load in new flag
			Flags[i] = Stream.ReadBits(1)
			If Flags[i][3] = 1 Then ContinueNumber = Stream.ReadUInt(1)
		Next
		
		
		'XCoords
		Local XCoords:Int[PointNumber]
		For Local i = 0 To PointNumber - 1
			'Is the same as last
			If Flags[i][1] = 0 And Flags[i][4] = 1 Then
				If i > 0 Then XCoords[i] = XCoords[i - 1] Else XCoords[i] = -Glyph[Id].xMin
				Continue
			End If
			'XisByte
			If Flags[i][1] = 1 Then
				Local tmp = Stream.ReadUInt(1)
				If Flags[i][4] = 0 Then tmp = tmp * -1
				If i > 0 Then XCoords[i] = XCoords[i - 1] + tmp Else XCoords[i] = tmp - Glyph[Id].xMin
				Continue
			End If
			If Flags[i][1] = 0 And Flags[i][4] = 0 Then
				If i > 0 Then XCoords[i] = XCoords[i - 1] + Stream.ReadInt(2) Else XCoords[i] = Stream.ReadInt(2) - Glyph[Id].xMin
				Continue
			End If
		Next

		
		'YCoords
		Local YCoords:Int[PointNumber]
		For Local i = 0 To PointNumber - 1
			'Is the same as last
			If Flags[i][2] = 0 And Flags[i][5] = 1 Then
				If i > 0 Then YCoords[i] = YCoords[i - 1] Else YCoords[i] = Glyph[Id].yMax
				Continue
			End If
			'YisByte
			If Flags[i][2] = 1 Then
				Local tmp = Stream.ReadUInt(1)
				If Flags[i][5] = 0 Then tmp = tmp * -1
				If i > 0 Then YCoords[i] = YCoords[i - 1] - tmp Else YCoords[i] = Glyph[Id].yMax - tmp
				Continue
			End If
			If Flags[i][2] = 0 And Flags[i][5] = 0 Then
				If i > 0 Then YCoords[i] = YCoords[i - 1] - Stream.ReadInt(2) Else YCoords[i] = Glyph[Id].yMax - Stream.ReadInt(2)
				Continue
			End If
		Next
		
		'Transpose to xyList
		Glyph[Id].xyList = New Float[ContourNumber][]
		Local p1 = 0, Pend
		For Local i = 0 To ContourNumber - 1	
			If i > 0 Then
				p1 = EndPoints[i - 1] + 1
			End If
			Pend = EndPoints[i]
			Glyph[Id].xyList[i] = New Float[ ( (Pend - p1 + 1) * 3)]
			Local Count = 0
			For Local j = p1 To Pend
				'HEZKORE: Sharper scaling for fonts that aren't smooth
				If Not Smooth Then
					Glyph[Id].xyList[i][Count] = XCoords[j] * FontScale
					Glyph[Id].xyList[i][Count + 1] = YCoords[j] * FontScale
				Else
					Glyph[Id].xyList[i][Count] = XCoords[j] 
					Glyph[Id].xyList[i][Count + 1] = YCoords[j]
				EndIf
				Glyph[Id].xyList[i][Count + 2] = Flags[j][0]
				Count = Count + 3
			Next
		Next
		
	End Method
	Method QuadGlyph(Id)
		For Local c = 0 To Glyph[Id].ContourNumber - 1
			Local xyStack:Stack<Float> = New Stack<Float>
			For Local p0 = 0 To Glyph[Id].xyList[c].Length - 1 Step 3
				Local p1 = p0 + 3 If p1 > Glyph[Id].xyList[c].Length - 1 Then p1 = 0
				'Add p0
				'HEZKORE: Use Ints for fonts that aren't smooth
				If Not Smooth Then
					xyStack.Push(Floor(Glyph[Id].xyList[c][p0]))
					xyStack.Push(Floor(Glyph[Id].xyList[c][p0 + 1]))
					xyStack.Push(Floor(Glyph[Id].xyList[c][p0 + 2]))
				Else
					xyStack.Push(Glyph[Id].xyList[c][p0])
					xyStack.Push(Glyph[Id].xyList[c][p0 + 1])
					xyStack.Push(Glyph[Id].xyList[c][p0 + 2])
				Endif
				'If Double add a middle point
				If Glyph[Id].xyList[c][p0 + 2] = 0 And Glyph[Id].xyList[c][p1 + 2] = 0 Then
					Local tx:Float = (Glyph[Id].xyList[c][p0] + Glyph[Id].xyList[c][p1]) / 2.0
					Local ty:Float = (Glyph[Id].xyList[c][p0 + 1] + Glyph[Id].xyList[c][p1 + 1]) / 2.0
					xyStack.Push(tx)
					xyStack.Push(ty)
					xyStack.Push(1)
				End If
			Next
			Glyph[Id].xyList[c] = xyStack.ToArray()
		Next
	End Method
	Method SmoothGlyph(Id)
		For Local c = 0 To Glyph[Id].ContourNumber - 1
			Local xyStack:Stack<Float> = New Stack<Float>
			For Local p0 = 0 To Glyph[Id].xyList[c].Length - 1 Step 3
				Local p1 = p0 + 3 If p1 > Glyph[Id].xyList[c].Length - 1 Then p1 = 0
				Local p2 = p1 + 3 If p2 > Glyph[Id].xyList[c].Length - 1 Then p2 = 0
				
				If Glyph[Id].xyList[c][p0 + 2] = 0 Then Continue
				
				'Straight Line
				If Glyph[Id].xyList[c][p0 + 2] = 1 And Glyph[Id].xyList[c][p1 + 2] = 1
					xyStack.Push(Glyph[Id].xyList[c][p0])
					xyStack.Push(Glyph[Id].xyList[c][p0 + 1])
				Else
					'Bexier curve
					Local T:Float[] = CalculateCurve(Glyph[Id].xyList[c][p0], Glyph[Id].xyList[c][p0 + 1], Glyph[Id].xyList[c][p1], Glyph[Id].xyList[c][p1 + 1], Glyph[Id].xyList[c][p2], Glyph[Id].xyList[c][p2 + 1])
					For Local tt:Float = EachIn T
						xyStack.Push(tt)
					Next
				End If
			Next
			Glyph[Id].xyList[c] = xyStack.ToArray()
		Next
		
	End Method
	Method CalculateCurve:Float[] (x1, y1, x2, y2, x3, y3)
		Local Lst:Float[10]
		Local Counter = 0
		For Local t:Float = 0 To 0.8 Step 0.2
			Local tx:Float = (Pow(1.0 - t, 2) * x1) + (2 * ( (1.0 - t) * t * x2)) + (Pow(t, 2) * x3)
			Local ty:Float = (Pow(1.0 - t, 2) * y1) + (2 * ( (1.0 - t) * t * y2)) + (Pow(t, 2) * y3)
			Lst[Counter] = tx
			Lst[Counter + 1] = ty
			Counter = Counter + 2
		Next
		Return Lst
	End Method
	
	Method LoadGlyphImages()
		Local OrigColor:Float[] = GetColor()
		'Copy BG
		Local BW = ( (FontLimits[2] - FontLimits[0]) * FontScale) + 2
		Local BH = ( (FontLimits[3] - FontLimits[1]) * FontScale) + 2
		Local BG:Image = CreateImage(BW, BH)
		Local BGPixels:Int[BW * BH]
		ReadPixels(BGPixels, 0, 0, BW, BH)
		BG.WritePixels(BGPixels, 0, 0, BW, BH)
		
		'HEZKORE: Only scale here if it's a smooth font
		If Smooth Then
			PushMatrix
			Scale(FontScale, FontScale)
		EndIf
		For Local g = 0 To GlyphNumber - 1
			If Glyph[g].ContourNumber < 1 Then Continue
			Local W = Glyph[g].W * FontScale + 4
			Local H = Glyph[g].H * FontScale + 4
			If W < 1 Or H < 1 Then Continue
			
			'DrawBG
			SetColor(255, 255, 255)
			DrawRect(0, 0, W/FontScale, H/FontScale)
			'Draw solids
			
			SetColor(0, 0, 0)
			For Local i = 0 To Glyph[g].ContourNumber - 1
				If Glyph[g].Poly[i].Clockwise = FontClockwise Then
					Glyph[g].Poly[i].Draw()
				End If
			Next
			'Draw Cutouts
			SetColor(255, 255, 255)
			For Local i = 0 To Glyph[g].ContourNumber - 1
				If Glyph[g].Poly[i].Clockwise <> FontClockwise Then
					Glyph[g].Poly[i].Draw()
				End If
			Next
			
			
			'SaveImage
			Local pixels:Int[W * H]
			ReadPixels(pixels, 0, 0, W, H)
			
			'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
			Glyph[g].Img = CreateImage(W, H)
			Glyph[g].Img.WritePixels(pixels, 0, 0, W, H)
		Next
		If Smooth Then PopMatrix  'HEZKORE: Only needed for smooth fonts
		SetColor(255, 255, 255)
		DrawImage(BG, 0, 0)
		ImagesLoaded = True
		SetColor(OrigColor[0], OrigColor[1], OrigColor[2])
	End Method
End


Class TFont_Glyph
	Field Adv, Lsb
	Field FileAddress
	Field xMin, yMin, xMax, yMax, W, H
	Field ContourNumber
	Field Points:Int[][]
	Field xyList:Float[][]
	Field Poly:TFont_Poly[]
	Field Img:Image
End



CopperCircle(Posted 2015) [#17]
This is a great module, its a shame there is no anti-alias on Android/iOS, but looks nice on HTML5


Alex(Posted 2015) [#18]
Guys, why doesn't this module support non-latin characters?
Or do I do something wrong?


itto(Posted 2015) [#19]
I just stumbled upon this module and started using it. I downloaded the code therevills hosted on Google Code. The module has some serious bugs and visualization glitches. I've been able to fix (or so I think) some of them, but I suspect more will come. Does anyone have an updated version of the module? Or an alternative one which can load TTF fonts and display them correctly? Otherwise, we could maybe have the code hosted on GitHub to work on it together.


Hezkore(Posted 2015) [#20]
@itto my version (posted above) is "newer", and has the "sharp" way of loading TTF files which has worked great in my tests.


itto(Posted 2015) [#21]
@Hezkore tried it already, but I don't see any improvements over the original one using the smooth feature (only tested with the bundled Helvetica on a HTML5 target). And disabling the smooth feature renders them even worse than the original module, especially small fonts are illegible. Additionally, the rendering is still not great. For small fonts the letters are all misplaced on the Y axis :(