[RMXP] Script that allows you to load fonts as spritesheets like in GameMaker Studio?

AndreWharn

Italian dude who makes games and blablablah...
Veteran
Joined
Aug 30, 2017
Messages
120
Reaction score
3
First Language
Italian
Primarily Uses
RMXP
Bump, I'm still searching for a method.
 

KK20

Just some XP Scripter
Veteran
Joined
Oct 11, 2018
Messages
447
Reaction score
184
First Language
English
Primarily Uses
RMXP
I am only aware of this script by poccil:

The example files are broken, but the idea is you put all the characters horizontally next to each other in the graphic, then create a .TXT file defining the start and width of the glyph.

Then I think you're good to go with just setting the font name prior to the draw_text call, e.g.
Code:
self.contents.font.name = 'my_bitmap_font'
 

AndreWharn

Italian dude who makes games and blablablah...
Veteran
Joined
Aug 30, 2017
Messages
120
Reaction score
3
First Language
Italian
Primarily Uses
RMXP
I am only aware of this script by poccil:

The example files are broken, but the idea is you put all the characters horizontally next to each other in the graphic, then create a .TXT file defining the start and width of the glyph.

Then I think you're good to go with just setting the font name prior to the draw_text call, e.g.
Code:
self.contents.font.name = 'my_bitmap_font'

Thank you, but can you maybe please show me how the text file should be? Because I have no idea of what to write for specify the start and the witdh.
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,342
Reaction score
1,548
First Language
English
Primarily Uses
Other
Is it just the text or is it everything that is blurry?
 

AndreWharn

Italian dude who makes games and blablablah...
Veteran
Joined
Aug 30, 2017
Messages
120
Reaction score
3
First Language
Italian
Primarily Uses
RMXP
Is it just the text or is it everything that is blurry?

Only full-screen mode (or any screen resize methods) and text, but to be honest I want this system for making who plays my game to not be forced to download a .ttf file for using the custom font, many of them don't even do that or know that and they play the game with Arial (which is orribile since it's not a pixel font in a pixel game).
 
Last edited:

KK20

Just some XP Scripter
Veteran
Joined
Oct 11, 2018
Messages
447
Reaction score
184
First Language
English
Primarily Uses
RMXP
Attached screenshot of an example another user gave me. Both the bitmap graphic and the text file are named the same and placed inside the Pictures folder. This font only has numbers (and a forward slash) in it, so this is a minor example. Because all the characters are on a single row, this means all your glyphs will share the same height.

Looking at the text file, the first entry is
Code:
0,0,0,24
As the website explains, the first field indicates the character this glyph will replace (so every '0' will be replaced with the bitmap '0' graphic).
The second field is the left-most x-coordinate of the glyph, which for this being the first glyph in the sheet should be 0.
The third field kind of indicates how much of a shift the glyph should have. Normally this value matches the above, but for some cases you might want to make it larger (move left) or smaller (move right). For example, if you take a look at glyph '2', changing the 42 to a 46 will move it left 4 pixels when rendered in-game.
Fourth field indicates the width of the glyph. So in this example the '0' has a width of 24 pixels.

Repeat the process for each glyph thereafter.
 

Attachments

  • bitmapfont_example.png
    bitmapfont_example.png
    34.4 KB · Views: 6

AndreWharn

Italian dude who makes games and blablablah...
Veteran
Joined
Aug 30, 2017
Messages
120
Reaction score
3
First Language
Italian
Primarily Uses
RMXP
Attached screenshot of an example another user gave me. Both the bitmap graphic and the text file are named the same and placed inside the Pictures folder. This font only has numbers (and a forward slash) in it, so this is a minor example. Because all the characters are on a single row, this means all your glyphs will share the same height.

Looking at the text file, the first entry is
Code:
0,0,0,24
As the website explains, the first field indicates the character this glyph will replace (so every '0' will be replaced with the bitmap '0' graphic).
The second field is the left-most x-coordinate of the glyph, which for this being the first glyph in the sheet should be 0.
The third field kind of indicates how much of a shift the glyph should have. Normally this value matches the above, but for some cases you might want to make it larger (move left) or smaller (move right). For example, if you take a look at glyph '2', changing the 42 to a 46 will move it left 4 pixels when rendered in-game.
Fourth field indicates the width of the glyph. So in this example the '0' has a width of 24 pixels.

Repeat the process for each glyph thereafter.

Ok, thank you so much. I spent this entire day making it, and it works. The only problem though is that when a long character is at the end of a line, it cuts or half of it renders on the next line instead, I have no idea of why this happends (I use UMS). Another problem is, how do I specify the space? I tried to do like " ,cords,cords,width" but it doesn't work, and if i don't do it, the characters just appear all attached.

If you want to check, I can send you in private the file and the image of the font.

EDIT: Fixed both problems.
 
Last edited:

KK20

Just some XP Scripter
Veteran
Joined
Oct 11, 2018
Messages
447
Reaction score
184
First Language
English
Primarily Uses
RMXP
As the instructions say:
Code:
(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: "\"" )
which would mean you would do
Code:
" ",cords,cords,width
Note it also mentions about a fifth field:
Code:
# 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
If you still can't get anything to work right, then yes, I would need the bitmap.
 

AndreWharn

Italian dude who makes games and blablablah...
Veteran
Joined
Aug 30, 2017
Messages
120
Reaction score
3
First Language
Italian
Primarily Uses
RMXP
As the instructions say:
Code:
(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: "\"" )
which would mean you would do
Code:
" ",cords,cords,width
Note it also mentions about a fifth field:
Code:
# 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
If you still can't get anything to work right, then yes, I would need the bitmap.

It worked! Thank you so much!
Also, for the cut long letters problem of earlier, it was just a coords problem (I had to redo all the coords calculation from 0 sadly, but now it works).
 

KK20

Just some XP Scripter
Veteran
Joined
Oct 11, 2018
Messages
447
Reaction score
184
First Language
English
Primarily Uses
RMXP
Yeah, I still have the project lying around. The "detailed" instructions are gone though, so you'll just have to infer from my posts above.
Ruby:
##################
#
#   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
 

schM0ggi

Villager
Member
Joined
Jul 26, 2012
Messages
17
Reaction score
13
First Language
German
Primarily Uses
Hey,

oh yeah, nice!
The instructions provided by you in the posts above are good enough so I should be fine :).

Thank you!
 

Latest Threads

Latest Posts

Latest Profile Posts

Camping inside a hidden cave.
LzSHByh.jpg
I don’t believe in astrology. I’m a Sagittarius and we’re skeptical.
senary_concept_art.png
The concept art for the sprite of Senary, a major character in my upcoming (as-of-yet-unnamed) project. She is known as the Timelord because she can "stop time" by making eye contact with a person; Paralyzing them for a short period of time through a type of hypnosis. Tell me what you think about her. I'm thinking of changing the color of her coat.

Forum statistics

Threads
124,368
Messages
1,163,129
Members
163,164
Latest member
kingfathom
Top