SHARE
TWEET

VXA Text Cache

Mithran Mar 28th, 2012 (edited) 7,943 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. # Text Cache v 1.04
  2. # by Mithran
  3. # hosted at forums.rpgmakerweb.com
  4.  
  5. # Instructions
  6. %Q(
  7. This script is a workaround for the Bitmap#draw_text issues in RPGMaker VX Ace.
  8.  
  9. By default, there are several errors with Bitmap#draw_text in Ace.
  10.  
  11. 1.  Text shrinking algorithm is overzealous.  Text drawn to a rect given by
  12.     its own text_size is reduced in size.  This is both counterintuitive, and not
  13.     the way it worked in previous versions (VX).
  14.  
  15. 2.  Text drawn to any rect wider than approx 640 pixels wraps around to the
  16.     beginning of the line, overwriting previous text.  This also causes center
  17.     and right alignments to fail.  This is both unnecessary and not how it worked
  18.     in VX.
  19.    
  20. 3.  Text drawn character by character with non true-type fonts has awkward spacing.
  21.     In addition, the text_size of a string of characters is not the same as the
  22.     sum of the text_size of each character.
  23.     This existed even in VX.
  24.    
  25. 4.  The first character of a Bitmap#draw_text command for certain letters on
  26.     certain fonts is not drawn correctly.  Since message window draws character
  27.     by character, this can become a major issue. (example: Verdana 20 pt font)
  28.    
  29. These errors can be demonstrated using my text draw debugger:
  30. http://pastebin.com/p55ukZP2
  31.    
  32. What this script does:
  33.  
  34. 1.  Adds 2 pixels to any draw_text width, so text can be intuitively drawn to its
  35.     own text_size rect. Offsets x coordinate where appropriate.
  36.     If SIMPLE_FIX is set to true, only this fix will be enabled.
  37.    
  38. 2.  Adds a text cache.  Instead of drawing text directly when called, a unique
  39.     bitmap is created for any potential text draw with buffers, drawn with extra
  40.     space around it.  The character is then copied whenever a text draw is
  41.     attempted.
  42.    
  43.     Text Caching can be turned off by setting SIMPLE_FIX to true.
  44.    
  45.     Text Caching also has the following features:
  46.     - Much faster processing than the original Bitmap#draw_text.
  47.       Trades a small amount of memory to accomodate faster processing speed.
  48.       The first time any letter is drawn takes approximately 3-4 times as long,
  49.       subsquently, any time this same letter and font is drawn it is upwards of
  50.       twice as fast.  The longer the string drawn, the bigger the difference.
  51.     - Accounts for a 3-length string when checking the size.  This makes single
  52.       characters drawn look more natural for the offending fonts.
  53.     Does not work with:
  54.     - Reduced size text.
  55.       If text is squeezed due to not being given enough room to draw, text caching
  56.       is bypassed in favor of the original method.  This is due to the text
  57.       squeezing algorithm reducing each character by a variable amount that can
  58.       not be determined with text_size.  Manually stretching or aligning this
  59.       "squeezed" text looks completely awful, so for now, this will have to
  60.       stay like this.
  61.       The exception to this is if the text has "just enough" room to draw,
  62.       it will be given the two extra pixels rather than squeezing it.
  63.     - If text extends beyond MAX_DRAW_WIDTH, text caching will be forced.
  64.       This disables the "squeeze" effect.  Using the default method means the text
  65.       would draw over itself anyway, so this is the lesser of two evils.
  66.      
  67. Changelog:
  68.  
  69. v 1.04
  70.   Fixed minor crash when a zero size character was attempted to be drawn.
  71.  
  72. v 1.03
  73.   Added an option to control how much buffer is given before text squeeze turns off.
  74.   Added an absolute width limit allowed for a draw_text operation to prevent a rare game.exe crash.
  75.   Added an option to completely disable the default squeezing method to always cache.
  76.  
  77. v 1.02
  78.   Fixed crash error when drawing a null/zero height character.
  79.  
  80. v 1.01
  81.   Fixed crash error when using F12 to reset. (Thanks Archiea_Nessiah)
  82.  
  83. v 1.0
  84. Official release.
  85. )
  86.  
  87.  
  88.   DISABLE_TEXT_SQUEEZE = false
  89.   # turning this to true completely disables all built in text squeezing methods
  90.  
  91.   TEXT_SQUEEZE_MIN_TRIGGER_RATE = 1.5
  92.   # the rate at which width of the text must be greater than the draw area
  93.   # in order to trigger the default draw method that "squeezes" text
  94.   # set to 1.0 to turn this feature off
  95.  
  96.  
  97. class Bitmap
  98.   TEXT_TOP_BUFFER = 2
  99.   TEXT_SIDE_BUFFER = 8 # buffer in pixels to draw text away from
  100.   # the edge of the bitmap, to prevent certain characters from being cut off
  101.   SIMPLE_FIX = false # just adds the two pixels to prevent unnecessary squeeze
  102.   # depricated, as doing so causes the other mentioned bugs to still appear
  103.   # 1.03 - changed to continue to draw text by character to prevent the crashing error
  104.   MAX_TEXT_DRAW_WIDTH = 640 # tests have shown the draw fails at around 640px
  105.   # if nil, no max width
  106.   MAX_TEXT_DRAW_WIDTH_ABSOLUTE = 2016 # the absolute limit accepted by draw_text
  107.   # this prevents a game.exe crash when the draw_text is called to a small space with a ton of text
  108.   # any text longer than this will be automatically drawn without squeezing
  109.   # this option should NEVER trigger either way
  110.   NO_FIX = false # completely disables the fix, for testing comparison
  111.  
  112.  
  113.   alias draw_text_vxa draw_text
  114.   def draw_text(*args)
  115.     return draw_text_vxa(*args) if NO_FIX
  116.     if args[0].is_a?(Rect)
  117.       rect = args[0]
  118.       x, y, width, height = rect.x, rect.y, rect.width, rect.height
  119.       text = args[1].to_s.clone || ""
  120.       align = args[2] || 0
  121.     else
  122.       x, y, width, height = *args[0..3]
  123.       text = args[4].to_s.clone || ""
  124.       align = args[5] || 0
  125.     end
  126.     if check_squeeze_allowed(x, y, width, height, text)
  127.       x -= align
  128.       # shift one pixels to the left if centering
  129.       # two if right right justified
  130.       # to offset the extra width given
  131.       draw_text_vxa(x, y, width + 2, height, text, align)
  132.     else
  133.       draw_text_cached(x, y, width, height, text, align)
  134.     end
  135.   end
  136.  
  137.   def check_squeeze_allowed(x, y, width, height, text)
  138.     return false if DISABLE_TEXT_SQUEEZE # completely disables squeeze
  139.     return false if MAX_TEXT_DRAW_WIDTH && width > MAX_TEXT_DRAW_WIDTH # will not squeeze if over size limit
  140.     text_width = text_size(text).width
  141.     return false if text_width >= MAX_TEXT_DRAW_WIDTH_ABSOLUTE # will not squeeze if over size limit
  142.     text_width > width * TEXT_SQUEEZE_MIN_TRIGGER_RATE # will not squeeze if over size limit
  143.   end
  144.  
  145.   def draw_text_cached(x, y, width, height, text, align, allow_squeeze = false)
  146.     text_rect = self.text_size(text)
  147.     text_width = text_rect.width
  148.     text_height = text_rect.height
  149.     # allow_squeeze - not recommended and completely hidden unless you are reading this
  150.     if allow_squeeze && text_width > width * TEXT_SQUEEZE_MIN_TRIGGER_RATE
  151.       ratio = width / text_width.to_f
  152.       ratio = 0.5 if ratio < 0.5
  153.       rect = Rect.new(0, 0, 0, 0)
  154.     else
  155.       ratio = nil
  156.     end
  157.     fontkey = self.font.to_a
  158.     case align
  159.     when 1; x += (width - text_width) / 2
  160.     when 2; x += width - text_width
  161.     end
  162.     y += (height - text_height) / 2 # horizontal center
  163.     buf = -TEXT_SIDE_BUFFER
  164.     buf *= ratio if ratio
  165.     text.each_char { |char|
  166.     letter = TextCache.letters(fontkey, char)
  167.     if SIMPLE_FIX  # swap with original method for debugging and simple fix
  168.       draw_text_vxa(x + buf, y, letter.rect.width + 2, letter.height, char)
  169.       buf += letter.rect.width - TEXT_SIDE_BUFFER * 2
  170.     elsif ratio # drawing squished text
  171.       w = (ratio * 10).to_i * letter.rect.width / 10
  172.       rect.set(x + buf, y, w, text_height)
  173.       self.stretch_blt(rect, letter, letter.rect)
  174.       buf += (letter.rect.width * ratio - TEXT_SIDE_BUFFER * 2 * ratio).to_i
  175.     else
  176.       self.blt(x + buf, y, letter, letter.rect)
  177.       buf += letter.rect.width - TEXT_SIDE_BUFFER * 2
  178.     end
  179.     }
  180.     nil
  181.   end
  182. end
  183.  
  184. module TextCache
  185.   BUFFER_DRAW = 300 # for drawing characters, to make sure there is enough room
  186.  
  187.   def self.canvas(font = nil)
  188.     @canvas = Bitmap.new(32, 32) if @canvas.nil? || @canvas.disposed?
  189.     #@canvas.font = font if font and font != @canvas.font
  190.     @canvas
  191.   end
  192.  
  193.   def self.letters(fontary, char)
  194.     @cache ||= {}
  195.     key = fontary + [char]
  196.     if include?(key)
  197.       return @cache[key]
  198.     elsif char.empty?
  199.       return empty_bitmap
  200.     else
  201.       return new_letter(fontary, char)
  202.     end
  203.   end
  204.  
  205.   def self.empty_bitmap # not used, added for completness in case the cache is accessed directly
  206.     @cache[:empty] = Bitmap.new(32, 32) unless include?(:empty)
  207.     @cache[:empty]
  208.   end
  209.  
  210.   def self.new_letter(fontary, char)
  211.     font = create_font(fontary)
  212.     # get the font
  213.     canvas.font = font
  214.     rect = canvas.text_size(char * 3)
  215.     key = fontary + [char]
  216.     return @cache[key] = empty_bitmap if (rect.height == 0 || rect.width == 0)
  217.     # get size of character between two other characters (for better kerning)
  218.     b = Bitmap.new((rect.width / 3) + Bitmap::TEXT_SIDE_BUFFER * 2, rect.height)
  219.     # create bitmap just big enough for one character
  220.     b.font = font
  221.     # get the font
  222.     b.draw_text_vxa(rect.x - b.text_size(" ").width + Bitmap::TEXT_SIDE_BUFFER, rect.y - Bitmap::TEXT_TOP_BUFFER, BUFFER_DRAW, rect.height + Bitmap::TEXT_TOP_BUFFER * 2, " #{char} ", 0)
  223.     # draw blank spaces before and after character, fix for cutting off the
  224.     # first pixel using draw_text
  225.     @cache[key] = b    
  226.   end
  227.  
  228.   def self.create_font(fontary)
  229.     font = Font.new(*fontary[0..1])
  230.     font.bold = fontary[2]
  231.     font.italic = fontary[3]
  232.     font.outline = fontary[4]
  233.     font.shadow = fontary[5]
  234.     font.color.set(*fontary[6..9])
  235.     font.out_color.set(*fontary[10..13])
  236.     font
  237.   end
  238.  
  239.  
  240.   def self.include?(key)
  241.     @cache[key] && !@cache[key].disposed?
  242.   end
  243.  
  244.   def self.clear
  245.     @cache ||= {}
  246.     @cache.clear
  247.     GC.start
  248.   end
  249.  
  250. end
  251.  
  252.  
  253.  
  254. class Font
  255.   # font's instance variables are not reflective, so this has to be defined explicitly
  256.   def to_a
  257.     [name, size, bold, italic, outline, shadow, color.red, color.green, color.blue, color.alpha, out_color.red, out_color.green, out_color.blue, out_color.alpha]
  258.   end
  259.  
  260. end
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Top