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").
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.
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.
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.