nobuyukinyuu / angelfont-tryouts

bugfixes and optimizations for the Monkey pack-in AngelFont variable-width font module
2 stars 2 forks source link

unicode #1

Open gcmartijn opened 9 years ago

gcmartijn commented 9 years ago

H!

I'm using monkey and I was searching for a free compact (less code is better :)) font creator. The end combination is now: http://kvazars.com/littera/ There i'm exporting the bitmap as .txt for free.

And then I strip down your code (to test it first).

But I notice that everything works oke except special characters :( Are you planning to fix that in the near future ? If not, then i'm going to rewrite the code so it can handle the other characters.

Maybe you say, that you are planning to rewrite this yourself so I can concentrate on other code i;m working on.

Strict Import mojo

Class Font Global firstKp:IntMap Global secondKp:KernPair

Field iniText:String
Field image:Image[] = New Image[1]
Field chars:Char[256]
Field height:Int = 0
Field heightOffset:Int = 9999
Field kernPairs:IntMap<IntMap<KernPair>> = New IntMap<IntMap<KernPair>>
Field xOffset:Int
Field yOffset:Int
Field useKerning:Bool = True
Const ALIGN_LEFT:Int = 0
Const ALIGN_CENTER:Int = 1
Const ALIGN_RIGHT:Int = 2
Const ALIGN_FULL:Int = 3

Method LoadPlain:Void(font_atlas:String,font_img:String)
    iniText = LoadString(font_atlas)
    Local lines:String[] = iniText.Split(String.FromChar(10))
    Local attribs:String[] 'Placeholder for an individual line's attributes, split up
    Local pageCount:Int  'How many pages does this font contain?

    Local AttemptFallbackImageLoading:Bool

    For Local line:String = EachIn lines
        line = line.Trim()
        Print line

        If line.StartsWith("info") Then 'general info about the font in this line.
            Continue 'Next line
        ElseIf line.StartsWith("common") Then 'common info here
            attribs = line.Split(" ") 'Get each attrib in this line.
            For Local i:Int = 0 Until attribs.Length
                If attribs[i] = "" Then 'Residual .Split() cruft; Escape early
                    Continue 'Next attrib
                ElseIf attribs[i].StartsWith("pages=")
                    pageCount = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                    image = image.Resize(pageCount)
                End If
            Next
        ElseIf line.StartsWith("page") Then 'information about an image page.
            Local imgUrl:String, imgPage:Int = -1

            attribs = line.Split(" ") 'Get each attrib in this line.
            For Local i:Int = 0 Until attribs.Length
                If attribs[i] = "" Then 'Residual .Split() cruft; Escape early
                    Continue 'Next attrib
                ElseIf attribs[i].StartsWith("id=")
                    imgPage = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                ElseIf attribs[i].StartsWith("file=")
                    imgUrl = attribs[i][attribs[i].Find("~q") + 1 .. attribs[i].FindLast("~q")]
                End If
            Next

            If imgUrl <> "" And imgPage >= 0  'Metadata found!
                image[imgPage] = LoadImage(imgUrl)
            Else 'Oh no, something went wrong.  Fallback to the old method
                AttemptFallbackImageLoading = True
            End If

        ElseIf line.StartsWith("chars") 'number of chars available.
            Continue 'Next line
        ElseIf line.StartsWith("char ") Then 'Char info here. Parse.                
            'Char proto.
            Local id:Int, x:Int, y:Int, w:Int, h:Int, xOffset:Int, yOffset:Int, xAdvance:Int, page:Int
            attribs = line.Split(" ") 'Get each attrib in this line.

            For Local i:Int = 0 Until attribs.Length  'Split up the attribs
                attribs[i].Trim()
                If attribs[i] = "" Then 'Residual .Split() cruft; Escape early
                    Continue 'Next attrib
                ElseIf attribs[i].StartsWith("id=")
                    id = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                ElseIf attribs[i].StartsWith("x=")
                    x = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                ElseIf attribs[i].StartsWith("y=")
                    y = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                ElseIf attribs[i].StartsWith("width=")
                    w = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                ElseIf attribs[i].StartsWith("height=")
                    h = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                ElseIf attribs[i].StartsWith("xoffset=")
                    xOffset = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                ElseIf attribs[i].StartsWith("yoffset=")
                    yOffset = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                ElseIf attribs[i].StartsWith("xadvance=")
                    xAdvance = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                ElseIf attribs[i].StartsWith("page=")
                    page = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                    'If pageCount < page pageCount = page
                End If
            Next
            'note:  WARNING: This will crash on unicode chars with "index out of range"  -nobu
            chars[id] = New Char(x, y, w, h, xOffset, yOffset, xAdvance, page)

            Local ch:= chars[id]
            If ch.height > Self.height Self.height = ch.height  'Beaker's fix for descenders and ascenders
            If ch.yOffset < Self.heightOffset Self.heightOffset = ch.yOffset

        ElseIf line.StartsWith("kernings") 'number of kernings available.
            Continue 'Next line         
        ElseIf line.StartsWith("kerning ")  'Kern pair info.  Parse.
            'KernPair proto.
            Local first:Int, second:Int, amount:Int 
            attribs = line.Split(" ") 'Get each attrib in this line.

            For Local i:Int = 0 Until attribs.Length  'Split up the attribs
                attribs[i].Trim()
                If attribs[i] = "" Then 'Residual .Split() cruft; Escape early
                    Continue 'Next attrib
                ElseIf attribs[i].StartsWith("first=")
                    first = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                ElseIf attribs[i].StartsWith("second=")
                    second = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                ElseIf attribs[i].StartsWith("amount=")
                    amount = int(attribs[i][attribs[i].FindLast("=") + 1 ..])
                End If
            Next

            'Start adding what we know based on the attribs we got.
            firstKp = kernPairs.Get(first)
            If firstKp = Null Then 'nothing here. Start building the prototype.
                kernPairs.Add(first, New IntMap<KernPair>)
                firstKp = kernPairs.Get(first)  'Switch to the second char of the pair to add the rest.
            End             
            'Add the rest of the prototype.
            firstKp.Add(second, New KernPair(first, second, amount))
        End If

    Next

    'Old fallback method for loading images
    If AttemptFallbackImageLoading
        For Local page:= 0 Until pageCount
            If image.Length() < page + 1 Then image = image.Resize(page + 1)
            image[page] = LoadImage(font_img)
        End         
    End If

End Method

Method DrawText:Void(txt:String, x:Int, y:Int)

' Local prevChar:String = "" Local prevChar:Int = 0 xOffset = 0

    For Local i:= 0 Until txt.Length
        Local asc:Int = txt[i]
        Local ac:Char = chars[asc]

' Local thisChar:String = String.FromChar(asc) Local thisChar:Int = asc If ac <> Null If useKerning firstKp = kernPairs.Get(prevChar) If firstKp <> Null secondKp = firstKp.Get(thisChar) If secondKp <> Null xOffset += secondKp.amount ' Print prevChar+","+thisChar End Endif Endif ac.Draw(image[ac.page], x+xOffset,y) xOffset += ac.xAdvance prevChar = thisChar Endif Next End Method

Method DrawText:Void(txt:String, x:Int, y:Int, align:Int)
    xOffset = 0
    Select align
        Case ALIGN_CENTER
            DrawText(txt,x-(TextWidth(txt)/2),y)
        Case ALIGN_RIGHT
            DrawText(txt,x-TextWidth(txt),y)
        Case ALIGN_LEFT
            DrawText(txt,x,y)
    End Select
End Method

End Class

Class Char Field asc:Int Field x:Int Field y:Int

Field width:Int
Field height:Int = 0

Field xOffset:Int = 0
Field yOffset:Int = 0
Field xAdvance:Int = 0

Field page:Int = 0

Method New(x:Int,y:Int, w:Int, h:Int, xoff:Int=0, yoff:Int=0, xadv:Int=0, page:Int=0)
    Self.x = x
    Self.y = y
    Self.width = w
    Self.height = h

    Self.xOffset = xoff
    Self.yOffset = yoff
    Self.xAdvance = xadv
    Self.page = page
End

Method Draw:Void(fontImage:Image, linex:Float,liney:Float)
    DrawImageRect(fontImage, linex+xOffset,liney+yOffset, x,y, width,height)
End Method

Method toString:String()
    Return String.FromChar(asc)+"="+asc
End Method

End Class

Class KernPair Field first:String Field second:String Field amount:Int

Method New(first:Int, second:Int, amount:Int)
    Self.first = first
    Self.second = second
    Self.amount = amount
End

rem

Method toString:String()
    Return "first="+String.FromChar(first)+" second="+String.FromChar(second)+" amount="+amount

' Return "first="+first+" second="+second+" amount="+amount End Method

end

End Class

nobuyukinyuu commented 7 years ago

Hiya,

This is probably since been addressed long ago, but the reason why it doesn't currently support Unicode is due to the legacy usage of chars[]. This was addressed by switching the variable to a map in http://github.com/nobuyukinyuu/monkey-utf8 but I considered the loss of efficiency unacceptable considering Angelfont has to perform the lookup every frame (and for every character? I can't remember now...). You can use that as a workaround but it may not be acceptable depending on how much text you need to display.

I was experimenting with implementing a splay tree to reduce the lookup times of an Angelfont char but opted instead to write a new class entirely for baking/caching rendered bitmaps for strings intended for display in my angelfont successor Varifont. A prototype was being developed in late 2015 for my next project but went unreleased. At this point, many people have switched over to Mojo 2 and probably wouldn't benefit from an incomplete implementation...