Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added a font texture generation system

This generates font textures and fonts.<locale>.cfg files based on
the character used in a locale's po file. Currently it only works
for Simplified Chinese, but it's expected to be easy to add support
for other languages.
  • Loading branch information...
commit 7f29a5421e10906ce5c535ae7cdf951b656b62a5 1 parent 12ceaa3
hagabaka authored
View
83 FONT_GENERATION
@@ -0,0 +1,83 @@
+=====================
+Font Generation Guide
+=====================
+
+Audience
+--------
+
+This guide is for translators or developers interested in adding
+automatically generated Frogatto font textures so that it can support a
+new language, or updating an existing font texture to reflect changes
+to a translation.
+
+Background
+----------
+
+Currently Frogatto uses, instead of TrueType or fonts in other common
+formats, bitmap font textures -- images containing the rendering of
+each character, plus FML files describing the location of each glyph in
+the image.
+
+Frogatto uses four groups of fonts: numbers, dialog, label and
+outline, where "number" is used for floating number popups, score and
+coin amounts, "dialog" for dialog lines, "label" for button labels,
+and "outline" mainly for the title screen tips. The font generation
+system only deals with the latter three, and refers to them as "styles".
+The "number" font is not included in the automatic generation because
+it can be usually reused in any language.
+
+The reason why an automatic font texture generation system is needed
+is because certain languages, such as Chinese and Japanese, have huge
+"alphabets", so it is infeasible to manually draw all the characters
+for them within a short amount of time.
+
+Requirements
+------------
+
+The system requires a *nix environment, Ruby 1.9, and ImageMagick.
+
+Usage
+-----
+
+The font generation system should be used when a new language with
+a huge alphabet is added, or when the translation for an existing
+such language is updated. In either case, the system only requires
+access to a TrueType font and the po file for the language.
+
+Before using the system, it may be needed to edit the top section of
+Rakefile.fonts, to specify fonts used in each language.
+
+To use the system, simply run::
+
+ rake fonts
+
+Which will perform all the needed tasks based on the configured
+languages. You can also selectively run tasks; use the following
+to see how::
+
+ rake -T
+
+Implementation
+--------------
+
+The system mainly works in three steps: extracting a list of characters,
+generating a glyph image for each character, and generating a font
+texture. The first step is done for each language, while the next two
+steps are done for each language and font style.
+
+This is a "make" like system, which avoids repeating completed work by
+checking the modification timestamps of existing files. For example,
+if a translation is updated and uses 5 new characters, then only the
+glyphs for these characters will be generated and the font texture will
+be generated. To force regeneration of a file, delete the file, or "touch"
+one of its dependent files, before running the system.
+
+The files used or generated by the system are listed below, in an order
+showing their inter-dependencies:
+
+* po/<locale>.po: translation file
+* tmp/fonts/<locale>/characters.txt: list of characters
+* tmp/fonts/<locale>/{dialog,outline,label}/<codepoint>.png: glyph image
+* images/gui/{dialog,outline,label}_font.<locale>.png: font texture
+* data/fonts.<locale>.cfg: glyph location data
+
View
196 Rakefile
@@ -0,0 +1,196 @@
+# Languages to generate font texture for
+LANGUAGES = {
+ 'zh_CN' => 'myuppygb-medium.ttf'
+}
+
+BACKGROUND_COLOR = '#6f6d51'
+# Command template to generate glyph images for each font style
+FONT_STYLES = {
+ :dialog => "convert -background '#{BACKGROUND_COLOR}' \
+ -font '%{font}' -pointsize 16 \
+ -fill '#f5f5f5' label:'%{character}' %{glyph}",
+
+ :outline => "convert -size 300x300 xc:#{BACKGROUND_COLOR} \
+ -font '%{font}' -pointsize 12 \
+ +antialias -fill '#550b0b' -annotate +51+49 '%{character}' \
+ -annotate +49+51 '%{character}' \
+ -annotate +51+51 '%{character}' \
+ -annotate +49+49 '%{character}' \
+ -antialias -fill '#e9f9f9' -annotate +50+50 '%{character}' \
+ -trim %{glyph}",
+
+ :label => "convert -size 300x300 xc:'#{BACKGROUND_COLOR}' \
+ -font '%{font}' -pointsize 12 \
+ +antialias -fill '#000000' -annotate +50+51 '%{character}' \
+ -antialias -fill '#ffffff' -annotate +50+50 '%{character}' \
+ -trim %{glyph}"
+}
+
+FONT_CFG_IDS = {
+ :dialog => 'default',
+ :label => 'door_label',
+ :outline => 'white_outline'
+}
+
+# Naming scheme of font textures
+def font_texture(style, language)
+ "images/gui/#{style}_font.#{language}.png"
+end
+
+# Naming scheme of fonts.cfg snippets
+def font_cfg_snippet(style, language)
+ "data/#{style}_font.#{language}.cfg"
+end
+
+# Naming scheme of character lists
+def character_list(language)
+ "tmp/fonts/#{language}/characters.txt"
+end
+
+# Naming scheme of po files
+def po_file(language)
+ "po/#{language}.po"
+end
+
+# Naming scheme of font generation working path
+def work_path(style, language)
+ "tmp/fonts/#{language}/#{style}"
+end
+
+# Naming scheme of glyphs
+def glyph_image(style, language, character)
+ File.join work_path(style, language), "#{codepoint_of(character)}.png"
+end
+
+# Naming scheme of font tasks
+def font_task(style, language)
+ "font:#{style}:#{language}"
+end
+
+# Naming scheme of glyph tasks
+def glyphs_task(style, language)
+ "glyphs:#{style}:#{language}"
+end
+
+# Width and height of an image
+def image_size(path)
+ `file #{path}` =~ /(\d+)\s*x\s*(\d+)/
+ [$1, $2].map(&method(:Integer))
+end
+
+# Unicode codepoint of a character
+def codepoint_of(character)
+ character.unpack('U*').first
+end
+
+# Character with given unicode codepoint
+def character_with_codepoint(codepoint)
+ [codepoint].pack('U*')
+end
+
+# Quote a character in FML
+def fml_quote(character)
+ case character
+ when '"'
+ %("\\"")
+ when "'"
+ %("'")
+ else
+ %("#{character}")
+ end
+end
+
+task :default => :fonts
+
+desc 'Generate all font textures and fonts.cfg snippets'
+task :fonts => (LANGUAGES.keys.map do |language|
+ desc "Generate #{language} font textures and fonts.cfg snippets"
+ task language => (
+ FONT_STYLES.keys.map do |style|
+ [glyphs_task(style, language), font_task(style, language)]
+ end.flatten
+ )
+end)
+
+# We need to generate a texture and snippet for each pair of (language, font style)
+LANGUAGES.each_pair do |language, font|
+ character_list = character_list(language)
+ msgstr_text = "#{character_list}.raw"
+ po_file = po_file(language)
+ language_work_path = work_path('', language)
+ directory language_work_path
+ desc "Generate #{language} character list"
+ file character_list => [po_file, language_work_path] do
+ sh <<-SCRIPT
+ cat /dev/null > #{msgstr_text}
+ msgfilter --keep-header -o /dev/null tee -a #{msgstr_text} < #{po_file}
+ cat #{msgstr_text} | utils/strip_po_markup.sh \
+ | utils/uniq_chars.rb > #{character_list}
+ SCRIPT
+ end
+
+ FONT_STYLES.each_pair do |style, command|
+ glyph_pattern =
+ Regexp.new(Regexp.escape(glyph_image(style, language, 'a')).sub(/\d+/, '.+'))
+ rule glyph_pattern do |t|
+ glyph = t.name
+ character = character_with_codepoint(Integer(glyph[/(\d+)/, 1]))
+ sh(command % {:font => font, :character => character, :glyph => glyph})
+ end
+
+ work_path = work_path(style, language)
+ directory work_path
+
+ glyphs = {}
+ sizes = {}
+
+ glyphs_task = glyphs_task(style, language)
+ task glyphs_task => character_list do
+ File.read(character_list).chars.each do |character|
+ glyph = glyph_image(style, language, character)
+ glyphs[glyph] = character
+ task font_task(style, language) => glyph
+ end
+ end
+
+ desc "Generate #{language} #{style} font texture and fonts.cfg snippet"
+ task font_task(style, language) => [work_path, glyphs_task] do
+ font_texture = font_texture(style, language)
+ sh <<-COMMAND
+ montage -background '#6f6d51' -label '' \
+ -geometry '1x1+0+0<' #{glyphs.keys.join(' ')} #{font_texture}
+ COMMAND
+ (texture_width, texture_height) = image_size(font_texture)
+
+ glyphs.each_key {|glyph| sizes[glyph] = image_size(glyph)}
+ (glyph_width, glyph_height) = sizes.values.transpose.map &:max
+ columns = texture_width / glyph_width
+
+ font_cfg_snippet = font_cfg_snippet(style, language)
+ File.open(font_cfg_snippet, 'w') do |cfg|
+ cfg.write <<-HEAD
+ [font]
+ id="#{FONT_CFG_IDS[style]}"
+ texture=#{font_texture.sub %r'^images/', ''}
+ pad=0
+ HEAD
+
+ glyphs.values.each_slice(columns).each_with_index do |characters, row|
+ characters.each_with_index do |character, column|
+ left = column * glyph_width
+ top = row * glyph_height
+ rect = [left, top, left + glyph_width, top + glyph_height]
+ cfg.write <<-CHARS
+ [chars]
+ chars=#{fml_quote(character)}
+ rect=#{left},#{top},#{left + glyph_width - 1},#{top + glyph_height - 1}
+ [/chars]
+ CHARS
+ end
+ end
+ cfg.write '[/font]'
+ end
+ end
+ end
+end
+
View
5 utils/strip_po_markup.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# Strip {stuff like this} from stdin and output everything else
+# as is
+sed -e 's/{[^}]*}//g'
View
14 utils/uniq_chars.rb
@@ -0,0 +1,14 @@
+#!/usr/bin/env ruby
+
+# Read text from stdin, and output unique characters in it, as well
+# as some additional characters needed in font textures
+
+# all languages need these characters for missing translations and
+# untranslatable strings
+add = ['a'..'z', 'A'..'Z', '0'..'9',
+ '`~!@#$%^&*()-_=+[]{};:",.<>/?|'.chars].map(&:to_a).reduce(&:+) +
+
+# these don't need glyphs, and will just mess up the font texture
+minus = ["\n"]
+
+STDOUT.write (ARGF.read.chars.to_a.uniq + add - minus).join
Please sign in to comment.
Something went wrong with that request. Please try again.