module MiniGL
# This class represents a font and exposes most of the methods from +Gosu::Font+,
# but allows the font to be created from an image, allowing for better customization
# and also using the +retro+ option. Moreover, this class can be passed to the
# +TextHelper+ constructor as if it was a +Gosu::Font+.
#
# The image used to load the font must meet these criteria:
# * The characters should be laid out in lines of the same height in pixels.
# * The full image must have a height that is a multiple of that line height.
# * The characters should occupy the maximum available space in each line, i.e.,
# if a character fits in the current line it must not be placed in the next
# one. In the last line there can be any amount of free space at the end.
class ImageFont
# The height of this font in pixels.
attr_reader :height
# Creates an +ImageFont+.
#
# Parameters:
# [img_path] Identifier of an image fitting the description in the class documentation,
# as used in +Res.img+.
# [chars] A string containing all characters that will be present in the image, in the
# same order as they appear in the image. Do not include white space.
# [widths] An integer representing the width of the chars in pixels, if this is a fixed
# width font, or an array containing the width of each char, in the same order
# as they appear in the +chars+ string.
# [height] The height of the lines in the image (see description above).
# [space_width] The width of the white space character in this font.
# [global] Parameter that will be passed to +Res.img+ when loading the image.
# [ext] Parameter that will be passed to +Res.img+ when loading the image.
# [retro] Parameter that will be passed to +Res.img+ when loading the image.
def initialize(img_path, chars, widths, height, space_width, global = true, ext = '.png', retro = nil)
retro = Res.retro_images if retro.nil?
img = Res.img(img_path, global, false, ext, retro)
@chars = chars
@images = []
@height = height
@space_width = space_width
wa = widths.is_a?(Array)
if wa && widths.length != chars.length
raise 'Wrong widths array size!'
end
x = y = 0
(0...chars.length).each do |i|
@images.push(img.subimage(x, y, wa ? widths[i] : widths, height))
new_x = x + (wa ? widths[i] : widths)
if i < chars.length - 1 && new_x + (wa ? widths[i+1] : widths) > img.width
x = 0
y += height
else
x = new_x
end
end
end
# Returns the width, in pixels, of a given string written by this font.
# Note: Markup is not supported, this method is named this way to match
# Gosu::Font
's signature.
#
# Parameters:
# [text] The string to be measured
def markup_width(text)
text.chars.reduce(0) { |w, c| if c == ' '; w += @space_width; else; i = @chars.index(c); w += i ? @images[i].width : 0; end }
end
# See Gosu::Font#draw_markup_rel
for details.
# Note: Markup is not supported, this method is named this way to match
# Gosu::Font
's signature.
def draw_markup_rel(text, x, y, z, rel_x, rel_y, scale_x, scale_y, color)
text = text.to_s unless text.is_a?(String)
if rel_x != 0
x -= scale_x * markup_width(text) * rel_x
end
if rel_y != 0
y -= scale_y * @height * rel_y
end
text.each_char do |c|
if c == ' '
x += scale_x * @space_width
next
end
i = @chars.index(c)
next if i.nil?
@images[i].draw(x, y, z, scale_x, scale_y, color)
x += scale_x * @images[i].width
end
end
# See Gosu::Font#draw_markup
for details.
# Note: Markup is not supported, this method is named this way to match
# Gosu::Font
's signature.
def draw_markup(text, x, y, z, scale_x, scale_y, color)
draw_markup_rel(text, x, y, z, 0, 0, scale_x, scale_y, color)
end
alias :draw_text_rel :draw_markup_rel
alias :draw_text :draw_markup
alias :text_width :markup_width
end
# This class provides methods for easily drawing one or multiple lines of
# text, with control over the text alignment and coloring.
class TextHelper
# Creates a TextHelper.
#
# Parameters:
# [font] A Gosu::Font
or ImageFont
that will
# be used to draw the text.
# [line_spacing] When drawing multiple lines, the default distance, in
# pixels, between each line.
# [scale_x] The default horizontal scale of the font.
# [scale_y] The default vertical scale of the font.
def initialize(font, line_spacing = 0, scale_x = 1, scale_y = 1)
@font = font
@line_spacing = line_spacing
@scale_x = scale_x
@scale_y = scale_y
end
# Draws a single line of text.
#
# Parameters:
# [text] The text to be drawn. No line breaks are allowed. You can use the
# `` tag for bold, `` for italic and `` for colors.
# [x] The horizontal reference for drawing the text. If +mode+ is +:left+,
# all text will be drawn from this point to the right; if +mode+ is
# +:right+, all text will be drawn from this point to the left; and if
# +mode+ is +:center+, the text will be equally distributed to the
# left and to the right of this point.
# [y] The vertical reference for drawing the text. All text will be drawn
# from this point down.
# [mode] The alignment of the text. Valid values are +:left+, +:right+ and
# +:center+.
# [color] The color of the text, in hexadecimal RRGGBB format.
# [alpha] The opacity of the text. Valid values vary from 0 (fully
# transparent) to 255 (fully opaque).
# [effect] Effect to add to the text. It can be either +nil+, for no effect,
# +:border+ for bordered text, or +:shadow+ for shadowed text (the
# shadow is always placed below and to the right of the text).
# [effect_color] Color of the effect, if any.
# [effect_size] Size of the effect, if any. In the case of +:border+, this
# will be the width of the border (the border will only look
# good when +effect_size+ is relatively small, compared to the
# size of the font); in the case of +:shadow+, it will be the
# distance between the text and the shadow.
# [effect_alpha] Opacity of the effect, if any. For shadows, it is usual to
# provide less than 255.
# [z_index] The z-order to draw the object. Objects with larger z-orders
# will be drawn on top of the ones with smaller z-orders.
# [scale_x] The horizontal scaling of the text. If +nil+, this instance's
# +@scale_x+ value will be used.
# [scale_y] The vertical scaling of the text. If +nil+, this instance's
# +@scale_y+ value will be used.
#
# *Obs.:* This method accepts named parameters, but +text+, +x+ and +y+ are
# mandatory.
def write_line(text, x = nil, y = nil, mode = :left, color = 0, alpha = 0xff,
effect = nil, effect_color = 0, effect_size = 1, effect_alpha = 0xff,
z_index = 0, scale_x = nil, scale_y = nil)
if text.is_a? Hash
x = text[:x]
y = text[:y]
mode = text.fetch(:mode, :left)
color = text.fetch(:color, 0)
alpha = text.fetch(:alpha, 0xff)
effect = text.fetch(:effect, nil)
effect_color = text.fetch(:effect_color, 0)
effect_size = text.fetch(:effect_size, 1)
effect_alpha = text.fetch(:effect_alpha, 0xff)
z_index = text.fetch(:z_index, 0)
scale_x = text.fetch(:scale_x, nil)
scale_y = text.fetch(:scale_y, nil)
text = text[:text]
end
scale_x = @scale_x if scale_x.nil?
scale_y = @scale_y if scale_y.nil?
color = (alpha << 24) | color
rel =
case mode
when :left then 0
when :center then 0.5
when :right then 1
else 0
end
if effect
effect_color = (effect_alpha << 24) | effect_color
if effect == :border
@font.draw_markup_rel text, x - effect_size, y - effect_size, z_index, rel, 0, scale_x, scale_y, effect_color
@font.draw_markup_rel text, x, y - effect_size, z_index, rel, 0, scale_x, scale_y, effect_color
@font.draw_markup_rel text, x + effect_size, y - effect_size, z_index, rel, 0, scale_x, scale_y, effect_color
@font.draw_markup_rel text, x + effect_size, y, z_index, rel, 0, scale_x, scale_y, effect_color
@font.draw_markup_rel text, x + effect_size, y + effect_size, z_index, rel, 0, scale_x, scale_y, effect_color
@font.draw_markup_rel text, x, y + effect_size, z_index, rel, 0, scale_x, scale_y, effect_color
@font.draw_markup_rel text, x - effect_size, y + effect_size, z_index, rel, 0, scale_x, scale_y, effect_color
@font.draw_markup_rel text, x - effect_size, y, z_index, rel, 0, scale_x, scale_y, effect_color
elsif effect == :shadow
@font.draw_markup_rel text, x + effect_size, y + effect_size, z_index, rel, 0, scale_x, scale_y, effect_color
end
end
@font.draw_markup_rel text, x, y, z_index, rel, 0, scale_x, scale_y, color
end
# Draws text, breaking lines when needed and when explicitly caused by the
# "\n" character.
#
# Parameters:
# [text] The text to be drawn. Line breaks are allowed. You can use the
# `` tag for bold, `` for italic and `` for colors.
# [x] The horizontal reference for drawing the text. Works like in
# +write_line+ for the +:left+, +:right+ and +:center+ modes. For the
# +:justified+ mode, works the same as for +:left+.
# [y] The vertical reference for drawing the text. All text will be drawn
# from this point down.
# [width] The maximum width for the lines of text. Line is broken when
# this width is exceeded.
# [mode] The alignment of the text. Valid values are +:left+, +:right+,
# +:center+ and +:justified+.
# [color] The color of the text, in hexadecimal RRGGBB format.
# [alpha] The opacity of the text. Valid values vary from 0 (fully
# transparent) to 255 (fully opaque).
# [z_index] The z-order to draw the object. Objects with larger z-orders
# will be drawn on top of the ones with smaller z-orders.
# [scale_x] The horizontal scaling of the text. If +nil+, this instance's
# +@scale_x+ value will be used.
# [scale_y] The vertical scaling of the text. If +nil+, this instance's
# +@scale_y+ value will be used.
# [line_spacing] The spacing between lines, in pixels. If +nil+, this
# instance's +@line_spacing+ value will be used.
def write_breaking(text, x, y, width, mode = :left, color = 0, alpha = 0xff, z_index = 0, scale_x = nil, scale_y = nil, line_spacing = nil)
line_spacing = @line_spacing if line_spacing.nil?
scale_x = @scale_x if scale_x.nil?
scale_y = @scale_y if scale_y.nil?
color = (alpha << 24) | color
text.split("\n").each do |p|
if mode == :justified
y = write_paragraph_justified p, x, y, width, color, z_index, scale_x, scale_y, line_spacing
else
rel =
case mode
when :left then 0
when :center then 0.5
when :right then 1
else 0
end
y = write_paragraph p, x, y, width, rel, color, z_index, scale_x, scale_y, line_spacing
end
end
end
private
def write_paragraph(text, x, y, width, rel, color, z_index, scale_x, scale_y, line_spacing)
line = ''
line_width = 0
text.split(' ').each do |word|
w = @font.markup_width(word)
if line_width + w * scale_x > width
@font.draw_markup_rel line.chop, x, y, z_index, rel, 0, scale_x, scale_y, color
line = ''
line_width = 0
y += (@font.height + line_spacing) * scale_y
end
line += "#{word} "
line_width += @font.markup_width("#{word} ") * scale_x
end
@font.draw_markup_rel line.chop, x, y, z_index, rel, 0, scale_x, scale_y, color unless line.empty?
y + (@font.height + line_spacing) * scale_y
end
def write_paragraph_justified(text, x, y, width, color, z_index, scale_x, scale_y, line_spacing)
space_width = @font.text_width(' ') * scale_x
spaces = [[]]
line_index = 0
new_x = x
words = text.split(' ')
words.each do |word|
w = @font.markup_width(word)
if new_x + w * scale_x > x + width
space = x + width - new_x + space_width
index = 0
while space > 0
spaces[line_index][index] += 1
space -= 1
index += 1
index = 0 if index == spaces[line_index].size - 1
end
spaces << []
line_index += 1
new_x = x
end
new_x += @font.markup_width(word) * scale_x + space_width
spaces[line_index] << space_width
end
index = 0
spaces.each do |line|
new_x = x
line.each do |s|
@font.draw_markup(words[index], new_x, y, z_index, scale_x, scale_y, color)
new_x += @font.markup_width(words[index]) * scale_x + s
index += 1
end
y += (@font.height + line_spacing) * scale_y
end
y
end
end
end