Kaynağa Gözat

Revamped font so it can show all chars.

Mark Sibly 9 yıl önce
ebeveyn
işleme
260458f912

+ 49 - 21
modules/mojo/graphics/canvas.monkey2

@@ -837,36 +837,64 @@ Class Canvas
 	#end
 	Method DrawText( text:String,tx:Float,ty:Float,handleX:Float=0,handleY:Float=0 )
 	
+		If Not text.Length Return
+	
 		tx-=_font.TextWidth( text ) * handleX
 		ty-=_font.Height * handleY
 		
-		Local image:=_font.Image
-		Local sx:=image.Rect.min.x,sy:=image.Rect.min.y
-		Local tw:=image.Texture.Width,th:=image.Texture.Height
+		Local gpage:=_font.GetGlyphPage( text[0] )
+		If Not gpage gpage=_font.GetGlyphPage( 0 )
+
+		Local sx:Float,sy:Float
+		Local tw:Float,th:Float
 		
-		AddDrawOp( image.Shader,image.Material,image.BlendMode,image.TextureFilter,4,text.Length )
+		Local i0:=0
 		
-		For Local char:=Eachin text
+		while i0<text.Length
 		
-			Local g:=_font.GetGlyph( char )
+			Local i1:=i0+1
+			Local page:GlyphPage
 			
-			Local s0:=Float(g.rect.min.x+sx)/tw
-			Local t0:=Float(g.rect.min.y+sy)/th
-			Local s1:=Float(g.rect.max.x+sx)/tw
-			Local t1:=Float(g.rect.max.y+sy)/th
+			While i1<text.Length
 			
-			Local x0:=Round( tx+g.offset.x )
-			Local y0:=Round( ty+g.offset.y )
-			Local x1:=x0+g.rect.Width
-			Local y1:=y0+g.rect.Height
-
-			AddVertex( x0,y0,s0,t0 )
-			AddVertex( x1,y0,s1,t0 )
-			AddVertex( x1,y1,s1,t1 )
-			AddVertex( x0,y1,s0,t1 )
+				page=_font.GetGlyphPage( text[i1] )
+				If page And page<>gpage Exit
+				
+				i1+=1
+			Wend
+
+			Local image:=gpage.image
+			sx=image.Rect.min.x;sy=image.Rect.min.y
+			tw=image.Texture.Width;th=image.Texture.Height
+			AddDrawOp( image.Shader,image.Material,image.BlendMode,image.TextureFilter,4,i1-i0 )
 			
-			tx+=g.advance
-		Next
+			For Local i:=i0 Until i1
+			
+				Local g:=_font.GetGlyph( text[i] )
+			
+				Local s0:=Float(g.rect.min.x+sx)/tw
+				Local t0:=Float(g.rect.min.y+sy)/th
+				Local s1:=Float(g.rect.max.x+sx)/tw
+				Local t1:=Float(g.rect.max.y+sy)/th
+				
+				Local x0:=Round( tx+g.offset.x )
+				Local y0:=Round( ty+g.offset.y )
+				Local x1:=x0+g.rect.Width
+				Local y1:=y0+g.rect.Height
+	
+				AddVertex( x0,y0,s0,t0 )
+				AddVertex( x1,y0,s1,t0 )
+				AddVertex( x1,y1,s1,t1 )
+				AddVertex( x0,y1,s0,t1 )
+				
+				tx+=g.advance
+			Next
+			
+			gpage=page
+			
+			i0=i1
+		Wend
+
 	End
 	
 	#rem monkeydoc Adds a light to the canvas.

+ 83 - 64
modules/mojo/graphics/font.monkey2

@@ -24,41 +24,15 @@ Struct Glyph
 
 End
 
-#rem monkeydoc The Font class.
+Class GlyphPage
 
-Mojo fonts use an image atlas system for storing font data.
+	Field image:Image
+	Field glyphs:Glyph[]
 
-The glyph struct is used to store the location, size and advance for individual characters within a font.
-All character image data for a font must A font must occupy a single image, 
+End
 
-#end
 Class Font Extends Resource
 
-	#rem monkeydoc Creates a new font.
-
-	@param image The image atlas.
-
-	@param height The height of the font in pixels.
-
-	@param firstChar The first character contained in the font.
-
-	@param glyphs An array of glyph structs describing the characters in the font.
-
-	#end
-	Method New( image:Image,height:Float,firstChar:Int,glyphs:Glyph[] )
-		_image=image
-		_height=height
-		_firstChar=firstChar
-		_glyphs=glyphs
-	End
-	
-	#rem monkeydoc The font image atlas.
-	#end
-	Property Image:Image()
-	
-		Return _image
-	End
-	
 	#rem monkeydoc The font height in pixels.
 	#end
 	Property Height:Float()
@@ -66,63 +40,108 @@ Class Font Extends Resource
 		Return _height
 	End
 	
-	#rem monkeydoc The first character contained in the font.
+	#rem monkeydoc Measures the width of some text when rendered by the font.
 	#end
-	Property FirstChar:Int()
-	
-		Return _firstChar
+	Method TextWidth:Float( text:String )
+		Local w:=0.0
+		For Local char:=Eachin text
+			w+=GetGlyph( char ).advance
+		Next
+		Return w
 	End
+
+	#rem monkeydoc @hidden
 	
-	#rem monkeydoc The number of characters in the font.
-	#end
-	Property NumChars:Int()
+	Gets the glyph page for a given char.
+	
+	Returns null if char does not have a glyph.
 	
-		Return _glyphs.Length
+	#end	
+	Method GetGlyphPage:GlyphPage( char:Int )
+		Local page:=char Shr 8
+		If page<0 Or page>=_pages.Length Return Null
+		
+		Local gpage:=_pages[page]
+		If Not gpage Return Null
+		
+		If Not gpage.image LoadGlyphPage( page )
+				
+		Local index:=char & 255
+		If index>=gpage.glyphs.Length Return Null
+		
+		Return gpage
 	End
 	
 	#rem monkeydoc @hidden
-	#end
-	Property Glyphs:Glyph[]()
 	
-		Return _glyphs
-	End
-	
-	#rem monkeydoc Gets a glyph from the font.
+	Gets the glyph for a given char.
+
 	#end
 	Method GetGlyph:Glyph( char:Int )
-		If char>=_firstChar And char<_firstChar+_glyphs.Length Return _glyphs[ char-_firstChar ]
-		Return _glyphs[0]
-	End
-	
-	#rem monkeydoc Measures the width of some text when rendered by the font.
-	#end
-	Method TextWidth:Float( text:String )
-		Local w:=0.0
-		For Local char:=Eachin text
-			w+=GetGlyph( char ).advance
-		Next
-		Return w
+		Local page:=char Shr 8
+		If page<0 Or page>=_pages.Length Return _nullGlyph
+
+		Local gpage:=_pages[page]
+		If Not gpage Return _nullGlyph
+
+		If Not gpage.image LoadGlyphPage( page )
+				
+		Local index:=char & 255
+		If index>=gpage.glyphs.Length Return _nullGlyph
+		
+		Return gpage.glyphs[index]
 	End
 	
-	'Make this ALWAYS work!	
-	#rem monkeydoc Loads a font from a ttf file.
+	#rem monkeydoc Loads a font from a file.
 	#end
 	Function Load:Font( path:String,height:Float,shader:Shader=Null )
 	
 		If Not shader shader=Shader.GetShader( "font" )
 		
-		Local font:=fontloader.LoadFont( path,height,shader )
-		If Not font And Not ExtractRootDir( path ) font=fontloader.LoadFont( "font::"+path,height,shader )
+		Local font:=FreeTypeFont.Load( path,height,shader )
+		If Not font And Not ExtractRootDir( path ) font=FreeTypeFont.Load( "font::"+path,height,shader )
 		
 		Return font
 	End
-
+	
+	Protected
+	
+	Method OnLoadGlyphPage( page:Int,gpage:GlyphPage ) Abstract
+	
+	Method InitFont( height:Float,pages:GlyphPage[] )
+	
+		_height=height
+		_pages=pages
+		
+		LoadGlyphPage( 0 )
+		
+		_nullGlyph=GetGlyph( 0 )
+	End
+	
+	Method OnDiscard() Override
+	
+		For Local page:=Eachin _pages
+			If page And page.image page.image.Discard()
+		Next
+		
+		_pages=Null
+		
+	End
+	
 	Private
 	
-	Field _image:Image
 	Field _height:Float
-	Field _firstChar:Int
-	Field _glyphs:Glyph[]
+
+	Field _pages:GlyphPage[]
+	
+	Field _nullGlyph:Glyph
+	
+	Method LoadGlyphPage( page:Int )
+	
+		Local gpage:=_pages[page]
+		
+		If Not gpage.image OnLoadGlyphPage( page,gpage )
+	End
 
 End
 

+ 0 - 145
modules/mojo/graphics/fontloader.monkey2

@@ -1,145 +0,0 @@
-
-Namespace mojo.graphics.fontloader
-
-#Import "<freetype>"
-
-Private
-
-Using freetype
-
-Global FreeType:FT_Library
-
-Public
-
-#rem monkeydoc @hidden
-#end
-Function LoadFont:Font( path:String,fheight:Float,shader:Shader )
-
-	Local ext:=ExtractExt( path )
-	If Not ext
-		Local font:=LoadFont( path+".otf",fheight,shader )
-		If Not font font=LoadFont( path+".ttf",fheight,shader )
-		Return font
-	Endif
-
-	If Not FreeType And FT_Init_FreeType( Varptr FreeType ) Return Null
-	
-	Local data:=DataBuffer.Load( path )
-	If Not data Return Null
-	
-	Local face:FT_Face
-	If FT_New_Memory_Face( FreeType,data.Data,data.Length,0,Varptr face ) 
-		data.Discard()
-		Return Null
-	Endif
-	
-	Local size_req:FT_Size_RequestRec
-	
-	size_req.type=FT_SIZE_REQUEST_TYPE_REAL_DIM
-	size_req.width=0
-	size_req.height=fheight * 64
-	size_req.horiResolution=0
-	size_req.vertResolution=0
-	
-	If FT_Request_Size( face,Varptr size_req )
-		data.Discard()
-		Return Null
-	Endif
-	
-	Local height:=(face->size->metrics.height+32) Shr 6
-	Local ascent:=(face->size->metrics.ascender+32) Shr 6
-
-'	Print face->size->metrics.height/64.0
-'	Print face->size->metrics.ascender/64.0
-'	Print face->size->metrics.descender/64.0
-
-	Local firstChar:=32,numChars:=96
-	Local glyphs:=New Glyph[numChars]
-
-	Local slot:=face->glyph
-	
-	'Measure atlas first
-	'
-	'Would really rather not render glyphs here, but can't see how...
-	'
-	Local tx:=0,ty:=0,texw:=0,texh:=0,maxh:=0
-	
-	Const MaxTexWidth:=1024
-
-	For Local i:=0 Until numChars
-
-		If FT_Load_Char( face,firstChar+i,FT_LOAD_RENDER|FT_LOAD_FORCE_AUTOHINT )
-			Continue
-		Endif
-
-		Local gw:=Int( slot->bitmap.width )
-		Local gh:=Int( slot->bitmap.rows )
-		
-		If tx+gw+1>MaxTexWidth
-			texw=Max( texw,tx )
-			texh+=maxh
-			maxh=0
-			tx=0
-		Endif
-		
-		maxh=Max( maxh,gh+1 )
-		tx+=gw+1
-		
-	Next
-	
-	texw=Max( texw,tx )
-	If tx texh+=maxh
-	
-	'round up texw, texh to ^2 in case we're mipmapping on mobile/webgl.
-	texw=1 Shl Int( Ceil( Log2( texw ) ) )
-	texh=1 Shl Int( Ceil( Log2( texh ) ) )
-	
-'	Print "path="+path+", height="+fheight+", texw="+texw+", texh="+texh
-	
-	Local pixmap:=New Pixmap( texw,texh,PixelFormat.A8 )
-	pixmap.Clear( Color.None )
-	
-	tx=0;ty=0;maxh=0
-	
-	For Local i:=0 Until numChars
-
-		If FT_Load_Char( face,firstChar+i,FT_LOAD_RENDER|FT_LOAD_FORCE_AUTOHINT )
-			Continue
-		Endif
-		
-		Local gw:=Int( slot->bitmap.width )
-		Local gh:=Int( slot->bitmap.rows )
-
-		Local tmp:=New Pixmap( gw,gh,PixelFormat.A8,slot->bitmap.buffer,slot->bitmap.pitch )
-		
-		If tx+gw+1>pixmap.Width
-			ty+=maxh
-			maxh=0
-			tx=0
-		Endif
-		
-		pixmap.Paste( tmp,tx,ty )
-		
-		tmp.Discard()
-		
-		glyphs[i]=New Glyph( New Recti( tx,ty,tx+gw,ty+gh ),New Vec2f( slot->bitmap_left,ascent-slot->bitmap_top ),slot->advance.x Shr 6 )
-
-		maxh=Max( maxh,gh+1 )
-		tx+=gw+1
-		
-	Next
-	
-	FT_Done_Face( face )
-	
-	data.Discard()
-	
-	Local image:=New Image( pixmap,Null,shader )
-	
-	Local font:=New Font( image,height,firstChar,glyphs )
-	
-	font.AddDependancy( image )
-	
-	pixmap.Discard()
-	
-	Return font
-End

+ 204 - 0
modules/mojo/graphics/freetypefont.monkey2

@@ -0,0 +1,204 @@
+
+Namespace mojo.graphics
+
+#Import "<freetype>"
+
+Private
+
+Using freetype
+
+Global FreeType:FT_Library
+
+Function FontError()
+	RuntimeError( "Font error" )
+End
+
+Public
+
+#rem monkeydoc @hidden
+#end
+Class FreeTypeFont Extends Font
+
+	Function Load:FreeTypeFont( path:String,fheight:Float,shader:Shader )
+	
+		Local ext:=ExtractExt( path )
+		If Not ext
+			Local font:=Load( path+".otf",fheight,shader )
+			If Not font font=Load( path+".ttf",fheight,shader )
+			If Not font font=Load( path+".fon",fheight,shader )
+			Return font
+		Endif
+	
+		If Not FreeType And FT_Init_FreeType( Varptr FreeType ) Return Null
+		
+		Local data:=DataBuffer.Load( path )
+		If Not data Return Null
+		
+		Local face:FT_Face
+		If FT_New_Memory_Face( FreeType,data.Data,data.Length,0,Varptr face ) 
+			data.Discard()
+			Return Null
+		Endif
+		
+		Local font:=New FreeTypeFont( data,face,fheight,shader )
+		
+		Return font
+	End
+	
+	Protected
+	
+	Method OnDiscard() Override
+	
+		FT_Done_Face( _face )
+	
+		_data.Discard()
+		
+		Super.OnDiscard()
+	End
+	
+	Method OnLoadGlyphPage( page:Int,gpage:GlyphPage ) Override
+	
+		Const MaxTexWidth:=1024
+	
+		Local firstChar:=page * 256
+		Local numChars:=256
+	
+		Local slot:=_face->glyph
+		
+		'Measure atlas first
+		'
+		'Would really rather not render glyphs here, but can't see how...
+		'
+		Local tx:=0,ty:=0,texw:=0,texh:=0,maxh:=0
+		
+		For Local i:=-1 Until numChars
+		
+			If i<0
+				If FT_Load_Char( _face,0,FT_LOAD_RENDER|FT_LOAD_FORCE_AUTOHINT ) FontError()
+			Else
+				If Not FT_Get_Char_Index( _face,firstChar+i ) Or FT_Load_Char( _face,firstChar+i,FT_LOAD_RENDER|FT_LOAD_FORCE_AUTOHINT ) Continue
+			Endif
+	
+			Local gw:=Int( slot->bitmap.width )
+			Local gh:=Int( slot->bitmap.rows )
+			
+			If tx+gw+1>MaxTexWidth
+				texw=Max( texw,tx )
+				texh+=maxh
+				maxh=0
+				tx=0
+			Endif
+			
+			maxh=Max( maxh,gh+1 )
+			tx+=gw+1
+		Next
+		
+		texw=Max( texw,tx )
+		If tx texh+=maxh
+		
+		'round up texw, texh to ^2 in case we're mipmapping on mobile/webgl.
+		texw=1 Shl Int( Ceil( Log2( texw ) ) )
+		texh=1 Shl Int( Ceil( Log2( texh ) ) )
+		
+		Local pixmap:=New Pixmap( texw,texh,PixelFormat.A8 )
+		pixmap.Clear( Color.None )
+	
+		Local glyphs:=New Glyph[numChars],nullGlyph:Glyph
+		
+		tx=0;ty=0;maxh=0
+		
+		For Local i:=-1 Until numChars
+		
+			If i<0
+				If FT_Load_Char( _face,0,FT_LOAD_RENDER|FT_LOAD_FORCE_AUTOHINT ) FontError()
+			Else
+				If Not FT_Get_Char_Index( _face,firstChar+i ) Or FT_Load_Char( _face,firstChar+i,FT_LOAD_RENDER|FT_LOAD_FORCE_AUTOHINT ) 
+					glyphs[i]=nullGlyph
+					Continue
+				Endif
+			Endif
+	
+			Local gw:=Int( slot->bitmap.width )
+			Local gh:=Int( slot->bitmap.rows )
+	
+			Local tmp:=New Pixmap( gw,gh,PixelFormat.A8,slot->bitmap.buffer,slot->bitmap.pitch )
+			
+			If tx+gw+1>pixmap.Width
+				ty+=maxh
+				maxh=0
+				tx=0
+			Endif
+			
+			pixmap.Paste( tmp,tx,ty )
+			
+			tmp.Discard()
+			
+			Local glyph:=New Glyph( New Recti( tx,ty,tx+gw,ty+gh ),New Vec2f( slot->bitmap_left,_ascent-slot->bitmap_top ),slot->advance.x Shr 6 )
+	
+			If i>=0 glyphs[i]=glyph Else nullGlyph=glyph
+	
+			maxh=Max( maxh,gh+1 )
+			tx+=gw+1
+		Next
+		
+		gpage.image=New Image( pixmap,Null,_shader )
+		gpage.glyphs=glyphs
+		
+		pixmap.Discard()
+		
+'		Print "Loading glyph page "+page+", image size="+gpage.image.Rect.Size
+	End
+
+	Private
+	
+	Field _data:DataBuffer
+	Field _face:FT_Face
+	Field _shader:Shader
+	Field _height:Int
+	Field _ascent:Int
+	
+	Method New( data:DataBuffer,face:FT_Face,fheight:Float,shader:Shader )
+		_data=data
+		_face=face
+		_shader=shader
+
+		Local size_req:FT_Size_RequestRec
+		
+		size_req.type=FT_SIZE_REQUEST_TYPE_REAL_DIM
+		size_req.width=0
+		size_req.height=fheight * 64
+		size_req.horiResolution=0
+		size_req.vertResolution=0
+		
+		If FT_Request_Size( face,Varptr size_req ) FontError()
+		
+		_height=(face->size->metrics.height+32) Shr 6
+		_ascent=(face->size->metrics.ascender+32) Shr 6
+
+		Local gindex:FT_UInt,nchars:=0
+		Local charcode:=FT_Get_First_Char( face,Varptr gindex )
+		
+		Local pages:=New GlyphPage[256]
+		
+		Local maxpage:=0
+		
+		While gindex
+		
+			Local page:Int=charcode Shr 8
+			
+			If page>=0 And page<pages.Length
+			
+				maxpage=Max( maxpage,page )
+
+				If Not pages[page] pages[page]=New GlyphPage
+				
+			Endif
+			
+			charcode=FT_Get_Next_Char( face,charcode,Varptr gindex )
+
+		Wend
+		
+		InitFont( _height,pages.Slice( 0,maxpage+1 ) )
+	End
+	
+End

+ 2 - 1
modules/mojo/mojo.monkey2

@@ -8,6 +8,7 @@ Namespace mojo
 #Import "<sdl2>"
 #Import "<gles20>"
 #Import "<openal>"
+#Import "<freetype>"
 
 #Import "app/app"
 #Import "app/event"
@@ -23,7 +24,7 @@ Namespace mojo
 
 #Import "graphics/canvas"
 #Import "graphics/font"
-#Import "graphics/fontloader"
+#Import "graphics/freetypefont"
 #Import "graphics/glutil"
 #Import "graphics/graphicsdevice"
 #Import "graphics/image"