Bitmap FontsPeter O., July 27, 2008
IntroductionThis 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.
ScriptFirst, put the following script section before the last one in the script editor (normally called "Main").
##################
#
# 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
Expand to see the code.
Defining Bitmap FontsAfter 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.
# 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
Expand to see the code.
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 DescriptionThe 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.