Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

Bitmap Fonts

poccil

Sponsor

Bitmap Fonts

Peter O., July 27, 2008

Introduction

This script supports bitmap-based fonts.  Bitmap fonts are useful for text-based effects that are normally
impossible or slow to achieve using RGSS's text facility alone.  For instance, a bitmap font can depict
red letters with a fancy gold trim.

A bitmap font can be used like any other font, since such fonts are integrated into the project.  A bitmap
font consists of a bitmap (PNG) file with the characters that the font consists of, and a supplementary
text file that details where the characters are located and placed.

Bitmap fonts do not support color or font size changes.

Script

First, put the following script section before the last one in the script editor (normally called "Main").

Code:
##################
# 
#  Bitmap Fonts, (C) 2008 Peter O.
#


##################
# 
#  Modified marshal class for reading raw data,
#  even within encrypted archives
#

module ::Marshal
 class << self
  if !@oldloadAliased
   alias oldload load
   @oldloadAliased=true
  end
  def neverload
   return @@neverload
  end
  @@neverload=false
  def neverload=(value)
   @@neverload=value
  end
  def load(port,*arg)
    if @@neverload
     if port.is_a?(IO)
       return port.read
     else
       return port
     end
    end
    oldpos=port.pos if port.is_a?(IO)
    begin
      oldload(port,*arg)
    rescue
      if port.is_a?(IO)
       port.pos=oldpos
       return port.read
      else
       return port
      end
    end
  end
 end
end

def loadRawData(file)
  oldload=Marshal.neverload
  begin
    ret=load_data(file)
    return ret
  rescue
    return ""
  ensure
    Marshal.neverload=oldload
  end
end


##################
# 
#  CSV Routines
#


def csvfield!(str)
  ret=""
  str.sub!(/^\s*/,"")
  return nil if str.length==0
  if str[0,1]=="\""
   str[0,1]=""
   escaped=false
   fieldbytes=0
   str.scan(/./) do |s|
    fieldbytes+=s.length
    break if s=="\"" && !escaped
    if s=="\\" && !escaped
     escaped=true
    else
     ret+=s
     escaped=false
    end
   end
   str[0,fieldbytes]=""
   # Remove everything before the comma
   str.sub!(/^[^,]*,?/,"")
  else
   if str[/,/]
    str[0,str.length]=$~.post_match
    ret=$~.pre_match
   else
    ret=str.clone
    str[0,str.length]=""
   end
   ret.gsub!(/\s+$/,"")
  end
  return ret
end

def csvGetPosInt(ret)
 if !ret || !ret[/^\d+$/]
  return nil
 end
 return ret.to_i
end

def csvGetInt(ret)
 if !ret || !ret[/^\-?\d+$/]
  return nil
 end
 return ret.to_i
end

def csvGetFields(str)
 str=str.dup
 ret=[]
 loop do
  field=csvfield!(str)
  if field==nil
   return ret
  end
  ret.push(field)
 end
end


def pbFileEachPreppedLine(filename)
 data=loadRawData(filename)
 lineno=1
 data.each_line {|line|
   if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF
    line=line[3,line.length-3]
   end
   if !line[/^\#/] && !line[/^\s*$/]
    yield line, lineno
   end
   lineno+=1
 }
end

def getBitmapFontRecords(filename)
 records={}
# rr=[]
 pbFileEachPreppedLine(filename) {|line, lineno|
  record=csvGetFields(line)
  # converting values to integers
  record[1]=csvGetPosInt(record[1]) # glyph start X
  record[2]=csvGetInt(record[2]) # start X
  record[3]=csvGetPosInt(record[3]) # width
  record[4]=csvGetInt(record[4]) # end X (if unequal to glyph start X + width) 
  # checking required string
  if !record[0] || record[0].length==0
   next
  end
  # checking required values
  if !record[1] || !record[2] || !record[3]
   next
  end
  # checking optional value
  if !record[4]
   record[4]=record[1]+record[3] 
  end
  records[record[0]]=[
   # Offset from end of last glyph to start of this glyph
   record[1]-record[2],
   # Offset from end of this glyph to start of next glyph
   record[4]-(record[1]+record[3]),
   record[1], # source start X
   record[3]  # source width
  ]
 }
 return records
end


def csvquote(str)
 return "" if !str || str==""
 if str[/[,\"]/] || str[/^\s/] || str[/\s$/] || str[/^#/]
  str=str.sub(/[\"]/,"\\\"")
  str="\"#{str}\""
 end
 return str
end

##################
# 
#  High-speed bitmap access
#

class Bitmap
  # Fast methods for retrieving bitmap data
  RtlMoveMemory_pi = Win32API.new('kernel32', 'RtlMoveMemory', 'pii', 'i')
  RtlMoveMemory_ip = Win32API.new('kernel32', 'RtlMoveMemory', 'ipi', 'i')
  def setData(x)
    RtlMoveMemory_ip.call(self.address, x, x.length)    
  end
  def getData
    data = "rgba" * width * height
    RtlMoveMemory_pi.call(data, self.address, data.length)
    return data
  end
  def swap32(x)
   return ((x>>24)&0x000000FF)|
          ((x>>8)&0x0000FF00)|
          ((x<<8)&0x00FF0000)|        
          ((x<<24)&0xFF000000)
  end
  def saveToPng(filename)
   bytes=[
    0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A,
    0x00,0x00,0x00,0x0D
   ].pack("CCCCCCCCCCCC")
   ihdr=[
    0x49,0x48,0x44,0x52,
    swap32(self.width),
    swap32(self.height),
    0x08,0x06,0x00,0x00,0x00
   ].pack("CCCCVVCCCCC")
   crc=Zlib::crc32(ihdr)
   ihdr+=[swap32(crc)].pack("V")
   bytesPerScan=self.width*4
   row=(self.height-1)*bytesPerScan
   data=self.getData
   width=self.width
   x=""
   while row>=0
    x+="\0"
    thisRow=data[row,bytesPerScan]
    i=0;while i<width
     b=i<<2
     bp2=b+2
     t=thisRow[b]
     thisRow[b]=thisRow[bp2]
     thisRow[bp2]=t
     i+=1
    end
    x+=thisRow
    row-=bytesPerScan
   end
   x=Zlib::Deflate.deflate(x)
   length=x.length
   x="IDAT"+x
   crc=Zlib::crc32(x)
   idat=[swap32(length)].pack("V")
   idat+=x
   idat+=[swap32(crc)].pack("V")
   idat+=[0,0x49,0x45,0x4E,0x44,0xAE,0x42,0x60,0x82].pack("VCCCCCCCC")
   File.open(filename,"wb"){|f|
    f.write(bytes)
    f.write(ihdr)
    f.write(idat)
   }
  end
  def address
    if !@address
      buffer, ad = "rgba", object_id * 2 + 16
      RtlMoveMemory_pi.call(buffer, ad, 4)
      ad = buffer.unpack("L")[0] + 8
      RtlMoveMemory_pi.call(buffer, ad, 4)
      ad = buffer.unpack("L")[0] + 16
      RtlMoveMemory_pi.call(buffer, ad, 4)
      @address=buffer.unpack("L")[0]
    end
    return @address
  end
end

##################
# 
#  Draws and saves a bitmap font
#

def drawBitmapFont(bitmap,filename)
 x=0
 height=bitmap.text_size("X").height
 for i in 0x20..0xFF
  next if i>=0x7F && i<0xA0
  s=[i].pack("U")
  x+=bitmap.text_size(s).width+2
 end
 width=x
 x=0
 rbitmap=Bitmap.new([width,1].max,[height,1].max)
 rbitmap.font.name=bitmap.font.name
 rbitmap.font.color=bitmap.font.color
 rbitmap.font.size=bitmap.font.size
 File.open("#{filename}.txt","wb"){|f|
  for i in 0x20..0xFF
   next if i>=0x7F && i<0xA0
   s=[i].pack("U")
   w=rbitmap.text_size(s).width
   f.write(csvquote(s)+",#{x},#{x},#{w+2},#{x+w}\r\n")
   rbitmap.draw_text(x,0,w+2,height,s,0)
   x+=w+2
  end
 }
 rbitmap.saveToPng("#{filename}.png")
end

##################
# 
#  Bitmap Font Class
#

class BitmapFont
 def initialize(bmfont)
  @bitmap=Bitmap.new("Graphics/Pictures/"+bmfont+".png")
  @records=getBitmapFontRecords("Graphics/Pictures/"+bmfont+".txt")
 end
 def textSize(string)
  x=0
  textEndX=0
  records=@records
  string.scan(/./m){|c|
   rec=records[c]
   next if !rec
   x+=rec[0] # move x by offset from end of last glyph
   x+=rec[3] # add glyph width
   textEndX=x # set total width to x
   x+=rec[1] # add offset from end of this glyph
  }
  return Rect.new(0,0,textEndX,@bitmap.height)
 end
 def drawText(bitmap,x,y,width,height,string,alignment=0,opacity=255)
  return if !string || string.length==0
  positions=[]
  records=@records
  realEndX=x+width
  realEndY=y+height
  return if y>=bitmap.height || height<=0
  return if x>=bitmap.width || width<=0
  dstY=y+(height/2)-(@bitmap.height/2)
  srcHeight=height<@bitmap.height ? height : @bitmap.height
  textStartX=x
  first=true
  textEndX=x
  string.scan(/./m){|c|
   rec=records[c]
   next if !rec
   x+=rec[0] # move x by offset from end of last glyph
   textStartX=x if first
   first=false
   break if x>=realEndX || y>=realEndY
   endx=x+rec[3] # x plus glyph width
   endx=width if endx>realEndX
   next if x>=endx
   positions.push([
     # destination
     x,dstY,
     # source
     Rect.new(rec[2],0,endx-x,srcHeight)
   ])
   x+=rec[3] # add glyph width
   textEndX=x
   x+=rec[1] # add offset from end of this glyph
  }
  totalWidth=textEndX-textStartX
  offset=0
  if alignment==1
    offset=(width/2)-(totalWidth/2)
  elsif alignment==2
    offset=width-totalWidth    
  end
  for pos in positions
   bitmap.blt(
    pos[0]+offset,pos[1],@bitmap,pos[2]
   )
  end
 end
end

module BitmapFontCache
  @cache={}
  def self.load(name)
    if @cache.include?(name)
      return @cache[name]
    end
    bmfont=nil
    begin
      bmfont=BitmapFont.new(name)
    rescue
      bmfont=nil
    end
    @cache[name]=bmfont
    return bmfont
  end
end

class Font
  def checkBitmapFont(name)
    return nil if !name
    if name.is_a?(Array)
      for i in name
       bmfont=BitmapFontCache.load(i)
       return bmfont if bmfont
      end
      return nil
    else
     return BitmapFontCache.load(name)
    end
  end
  def bitmapFont
    return checkBitmapFont(self.name)
  end
end

class Bitmap
  if !defined?(petero_bitmapfont_text_size)
    alias petero_bitmapfont_text_size text_size
    alias petero_bitmapfont_draw_text draw_text
  end
 def text_size(str)
  bmfont=self.font.bitmapFont
  if bmfont
   return bmfont.textSize(str)
  else
   return petero_bitmapfont_text_size(str)
  end
 end
 def draw_text(*args)
  bmfont=self.font.bitmapFont
  if bmfont
   if args.length==2 || args.length==3
    rc=args[0]
    bmfont.drawText(self,rc.x,rc.y,rc.width,rc.height,
      args[1],args[2] ? args[2] : 0)
   elsif args.length==5 || args.length==6
    bmfont.drawText(self,args[0],args[1],args[2],args[3],
     args[4],args[5] ? args[5] : 0)   
   end
  else
   return petero_bitmapfont_draw_text(*args)   
  end
 end
end

Defining Bitmap Fonts

After installing the script, you can now define the fonts for the system to use.

The following is an example of how a bitmap font would look:

http://upokecenter.com/projects/myfont.png[/img]

Bitmap fonts must be in PNG format and placed in the Graphics/Pictures/ folder of the project.

And here is an example of how the font's description would look.  The comments in each file
give further detail on the fields used in the description.  Note that the description corresponds
to the bitmap font above.

Code:
# This is a comment
# Each line has four fields.
# The first field specifies the character to
# draw.  (If the character includes a space or is 
# any one of: # , "  then the character must be
# in quotation marks.  To specify a quotation mark,
# put a backslash before it, like this: "\"" )
# It must be a single character, as multiple
# characters at once are not supported.
# The second field specifies the X coordinate of the
# beginning of the character in the bitmap font.
# The third field specifies the X coordinate of the
# character's starting point.  This is normally set
# to field 2, but can be made
# slightly larger if the character hangs to the left
# of the previous character (e.g., cursive f, j), 
# in order to tighten the spacing comfortably.
# The fourth field specifies the width of the character
# in the bitmap font.
#
# First character is A, located 0 pixels from the image
# and with a width of 18
A,0,0,18
# The next character is B, located 18 pixels from
# the beginning of the image and with a width of 17
B,18,18,17
C,35,35,15
D,50,50,17
E,67,67,15
F,82,82,13
# The fifth field, which is optional, specifies the X
# coordinate of where this character ends and the next 
# character begins, relative to the beginning of the bitmap
# font image.  If not specified, this field is equal
# to the sum of fields 1 and 3.
# Space glyph (notice the quotes)
" ",0,0,0,6

Descriptions of bitmap fonts must be in TXT format and placed in the Graphics/Pictures/ folder of the project.

Bitmap fonts must be placed in the Graphics/Pictures/ folder of the project, and they must have the
same name except for the file extension. For instance, if the font was placed in
"Graphics/Pictures/MyFont.png" and "Graphics/Pictures/MyFont.txt", you would
use the name "MyFont" to refer to it.

Bitmap fonts override those installed in Windows.  For instance, a bitmap font named "Arial" will
take precedence over the installed font "Arial".

Details on the Font Description

The following picture will graphically demonstrate the meaning
of each field in the font description.  It illustrates a bitmap
font with two characters, an italic "f" and a regular "m".

http://upokecenter.com/projects/bmfont2.png[/img]

Fields A and C specify the starting point and the width of
a character in the bitmap font.  Fields B and D indicate where
the character begins and ends.  Notice that the "f" character
ends much earlier than its shape ends (field D).  This ending
will mark the beginning of the next character in the text. 
Notice also that the "f" character begins much later than its
shape begins (field B).

Notice that the "m" character has only two lines, the beginning
and the end.  This is so because fields A and B have the same
value and D is set to the sum of fields A and C (81 + 78 = 159),
making the presence of D optional in this case. Most unslanted
(non-italic) fonts will have such a configuration.

Notice that fields A, B, and D are specified relative to the
beginning of the bitmap font's image.  For example, fields A
and B for the character "m" are set to 81 pixels from the left
edge of the bitmap font.

Field C specifies the width of each character. (69 pixels for
"f" and 78 pixels for "m". Note that the character "m" is given
a little extra space after the end of its shape, to prevent the
text from appearing too tight.
 
Reminds me of the way both professional typewriters create their fonts, I do my fonts (I'd dislike adding that to the first ;) ) and major font creating programs work... all you missed really is the 'Export as .ttf' option :p (which'd be totally unnecessary for a game, but yeah)

What can I say, bitmap fonts should be very useful if used correctly... the games running through my mind right now are Duke Nukem 64 (which bitmap font I always faved among all of those), and Banjo Kazooie... especially the shaking and slightly turning left and right letters are something I'm temped to resemble right now, but yeah... before that, I guess I'll work on my own scripts and let you implement this into your script ^^

I didn't test it, and you didn't write the RMversion one's gotta have, but it looks like a multicompatible thing from the quick look I had... excellent stuff either way!
 
Wow a bitmap font, did this script change the whole text or a specific text (i do prefer specific text), this reminds me like Picture damage display on RTAB.

if this script works with specific text, i need an better instruction to create bitmap fonts in specific scene or text.

Thanks for sharing this good script
 
Hey guys, i worked out how to use this and i got all my text working except on character... the number 0? no matter what i do it will not display. any idea why?
 
lol never mind i worked it out. i had a . insted of a , sorry. If anyone needs further instructions on how to use this post it here and ill tell ya from how i worked it out. great script Poccil
 
Okay... i know this is an old topic, but the images at the top that help explain how to use this script are gone, and i'm trying to understand how this script works.
Is their a demo someplace that i can look at and see exactly how to implement this?
I can sit here and read his explanation until i'm blue in the face, and i still won't get it, i usually do better to look at a demo.. anyone got one?
I mean, i don't even see how the image should be laid out when drawing it in like photoshop or what ever.
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top