Submit Your Article


 
RPG Maker

Welcome Guest ( Log In | Register )


  Games Resources RPG Maker VX RPG Maker XP Scripts Tutorials Downloads

 
Reply to this topicStart new topic
> Paragraph Formatter, an easy and tidy way to display long strings
modern algebra
post Nov 3 2007, 10:00 AM
Post #1


Level 11
Group Icon

Group: Revolutionary
Posts: 191
Type: Scripter
RM Skill: Skilled




Pargraph Formatter
Version: 1.0
Author: modern algebra
Date: November 2, 2007

Version History




Version 1.0 - Original Script: Current Algorithms:
  • Formatter (generic)
  • Artist (generic)
  • Formatter (Zeriab's, written for fonts with unvarying character width)
Planned Future Versions
  • Probably add some better formatting and Artist classes as I get better at scripting. As well, any other scripter is welcome to add their own formatting methods to the script.
Description



This is a scripting tool. It's purpose is to allow the user to input an unbroken string and have returned a paragraph which has the ends of each line in line with each other. It serves roughly the same function as does the justifying option in Microsoft Word. See the screenshots if you are still confused.

Features
  • A nice, easy way to display long strings in a paragraph format
  • Easy to add and modify at runtime, allowing for the switching between formatting classes for each situation
  • You can write your own formatter or artist classes and use the paragraphing tool to suit your situation.
  • Highly customizable.
Screenshots



Instructions


As a scripter's tool, it can be quite heavy for non-scripters to use. That is why I wrote a facade for common use of the tool. Naturally, you will still need some scripting knowledge, but the facade allows for this code:

CODE
bitmap.new_paragraph (x, y, max_width, max_height, string)


where bitmap is the bitmap you are drawing to. This can be self.contents in a window, or any instance of the bitmap class.

For scripters, the way the paragraph is basically used is like this:
CODE
    
    formatter = <formatter class you want to use>
    artist = <artist class you want to use>
    specifications = <max width, or bitmap>
    pg = Paragrapher.new (formatter, artist)
    text_bitmap = pg.paragraph (string, specifications)
    bitmap.blt (x, y, text_bitmap, Rect.new (0,0,text_bitmap.width, text_bitmap.height))


Basically, you choose your formatter and artist class at runtime. This means that if you want to use Paragraph::Formatter_2, because you are using a font with set width for all characters, then you would choose that here. Currently, there is only one Artist class, Paragraph::Artist, but of course you can make your own if it does not suit you. You can either specify a bitmap or a fixnum. The fixnum would just be the max width, and the paragrapher would create a bitmap which was at font_size 22, default font name, and it would space each line 32 pixels. With a bitmap, you specify max_width, max_height, font and font size, and anything else that has an effect. Naturally, bitmap in the code is the bitmap you are drawing the paragraph on. If you have any questions, just ask.

Also, the text_size method of Bitmap does not, in fact, work properly. In a little while I will post a way to get around this problem as it can get in the way of drawing nice paragraphs.

Put the scripts above main and below the default scripts.


Script



CODE
#==============================================================================
# ** Paragrapher
#------------------------------------------------------------------------------
#  Module containing the objects for the Paragrapher
#==============================================================================
module Paragrapher
#============================================================================
  # Allows the 'Paragrapher.new' command outside of the module to be used
  # rather than having to use 'Paragrapher::Paragrapher.new'
#============================================================================
  class << self
    def new(*args, &block)
      return Paragrapher.new(*args, &block)
    end
  end
  
#============================================================================
  # * The Paragrapher class
#============================================================================
  class Paragrapher
    def initialize(formatter, artist)
      @formatter = formatter
      @artist = artist
    end
    
    def paragraph(string, specifications)
      f = @formatter.format(string, specifications)
      return @artist.draw(f)
    end
  end
  
#============================================================================
  # * The Formatter class
#============================================================================
  class Formatter
    # The format method takes two arguments, the unbroken string, and the bitmap
    # or desired length, and formats the text to fit within those specifications
    def format(string, specifications)
      # Initializes Formatted_Text object
      f = Formatted_Text.new
      # Checks whether specifications is a bitmap or a number. It then sets
      # max_width and f.bitmap accordingly
      if specifications.class == Bitmap
        f.bitmap = specifications
        max_width = specifications.width - 2
      elsif specifications.class == Fixnum || specifications.class == Float
        max_width = specifications - 2
        f.bitmap = Bitmap.new (max_width, 32)
      else
        # Error Catching
        p 'Specifications Error: Please Pass Fixnum, Float or Bitmap'
        f.lines = [['Specifications Error'],['Please Pass Fixnum or Bitmap']]
        f.blank_width = [0,0]
        f.bitmap = Bitmap.new (200, 64)
        return f
      end
      # Breaks the given string into an array of all it's characters
      temp_word_array = string.scan (/./)
      position = 0
      line_break = 0
      # Initializes f.lines
      f.lines = []
      f.blank_width = []
      for i in 0...temp_word_array.size
        character = temp_word_array[i]
        # If at a new word
        if character == " " || i == temp_word_array.size - 1
          # Take into account the last character of the string
          if i == temp_word_array.size - 1
            i += 1
          end
          # If this word fits on the current line
          if f.bitmap.text_size (string[line_break,i-line_break]).width <= max_width
            position = i
          else
            line = temp_word_array[line_break, position-line_break]
            # Adds the first lines to f.lines
            f.lines.push (line)
            # Calculates the blank space left to cover in the line
            line_blank = max_width - f.bitmap.text_size(string[line_break,position-line_break]).width
            # Calculates the necessary distance between letters to make up for
            # line_blank and adds that value to the f.blank_width array
            f.blank_width.push (line_blank.to_f / (line.size.to_f-1.0))
            # Keeps track of the position in the array of each line
            line_break = position + 1
            position = i
          end
        end
      end
      # Adds the last line to f.lines
      f.lines.push (temp_word_array[line_break, temp_word_array.size - line_break])
      # Since the last line is drawn normally, blank_width should be 0
      f.blank_width.push (0)
      if specifications.class == Fixnum
        # Sets up the bitmap if it was unspecified.
        f.bitmap = Bitmap.new(max_width, f.lines.size*32)
      end
      # Returns the Formatted_Text object
      return f
    end
  end
  
#============================================================================
  # * Zeriab Formatter
  #----------------------------------------------------------------------------
  #  The algorithm was written by Zeriab for fonts which have characters of the
  #  same width. This is like Courier New and fonts of that sort. This not
  #  only makes all the lines the same width, but also writes them in such
  #  a way that each line is the same width as the longest line, rather then
  #  the max width. This makes the difference in white space much smaller
  #  Adapted to fit into Paragraph Formatter by modern algebra
#============================================================================
  class Formatter_2
    #-----------------------------------------------------------------------
    # * Format
    #-----------------------------------------------------------------------
    def format (string, specifications)
      f = Formatted_Text.new
      f.lines = []
      f.blank_width = []
      word_lengths = []
      words = []
      tracker = 0
      for i in 0...string.size
        if string[i,1] == " " || i == string.size - 1
          if i == string.size - 1
            i += 1
          end
          word_lengths.push (i - tracker)
          words.push (string[tracker, i - tracker])
          tracker = i + 1
        end
      end
      if specifications.class == Bitmap
        max_width = specifications.width
        f.bitmap = specifications
      elsif specifications.class == Fixnum || specifications.class == Float
        max_width = specifications
        f.bitmap = Bitmap.new (1,1)
      end
      tw = f.bitmap.text_size('a').width
      max_width /= tw
      position = line_break (word_lengths, max_width)
      lines = give_lines (position, position.size - 1, words)
      max_width *= tw
      for i in 0...lines.size
        line = lines[i]
        f.lines.push (line.scan (/./))
        if i == lines.size - 1
          f.blank_width.push (0)
        else
          text_width = line.size * tw
          extra_space = max_width - text_width
          f.blank_width.push (extra_space.to_f / (line.size.to_f - 1.0))
        end
      end
      if f.bitmap != specifications
        f.bitmap = Bitmap.new (max_width, f.lines.size*32)
      end
      return f
    end
    #------------------------------------------------------------------------
    # * Line Break
    #------------------------------------------------------------------------
    def line_break(word_lengths, max_length)
        return false  if max_length > 180
        word_lengths.unshift(nil)
        extra_spaces = Table.new(word_lengths.size,word_lengths.size)
        line_prices = Table.new(word_lengths.size,word_lengths.size)
        word_price = []
        position = []
        inf = max_length*max_length + 1
        for i in 1...word_lengths.size
          extra_spaces[i,i] = max_length - word_lengths[i]
          for j in (i+1)..[word_lengths.size-1, max_length/2+i+1].min
              extra_spaces[i,j] = extra_spaces[i,j-1] - word_lengths[j]-1
          end
        end
        for i in 1...word_lengths.size
          for j in i..[word_lengths.size-1, max_length/2+i+1].min
              if extra_spaces[i,j] < 0
                line_prices[i,j] = inf
              elsif j == word_lengths.size-1 and extra_spaces[i,j] >= 0
                line_prices[i,j] = 0
              else
                line_prices[i,j] = extra_spaces[i,j]*extra_spaces[i,j]
              end
        end
        end
        word_price[0] = 0
        for j in 1...word_lengths.size
          word_price[j] = inf
          for ik in 1..j
              i = j - ik + 1
              break  if line_prices[i,j] == inf
              if word_price[i-1] + line_prices[i,j] < word_price[j]
                word_price[j] = word_price[i-1] + line_prices[i,j]
                position[j] = i
              end
          end
        end
        return position
    end
    #-----------------------------------------------------------------------
    # * Give_Lines
    #-----------------------------------------------------------------------
    def give_lines(position,last_index,words)
        first_index = position[last_index]
        word_array = []
        if first_index != 1
          word_array = give_lines(position, first_index - 1,words)
        end
        str = ""
        for x in first_index..last_index
          str += ' '  if x != first_index
          str += words[x-1]
        end
        word_array << str
        return word_array
    end
  end

#============================================================================
  # * The Artist class
#============================================================================
  class Artist
    # The draw method takes a Formatted_Text object and draws the text to the
    # bitmap of the object.
    def draw(f)
      # Calculates the distance between lines.
      line_distance = f.bitmap.height.to_f / f.lines.size.to_f
      line_distance = [f.bitmap.font.size  + 10, line_distance].min
      # For all lines in the lines array
      for i in 0...f.lines.size
        blank_space = f.blank_width[i]
        position = 0
        # For all indices of the line array
        for j in 0...f.lines[i].size
          word = f.lines[i][j]
          ws = f.bitmap.text_size (word)
          position += blank_space if j != 0
          # Adds blank_space and position, and draws the string located at each index
          f.bitmap.draw_text (position, line_distance*i,ws.width+1,ws.height+1,word)
          # Keeps track of the position we are in pixels
          position += ws.width
        end
      end
      return f.bitmap
    end
  end
  
#============================================================================
  # * The Formatted_Text class containing the results of the formatter
#============================================================================
  class Formatted_Text
    attr_accessor :lines
    attr_accessor :blank_width
    attr_accessor :bitmap
  end
end



The facade for easy use:

CODE
class Bitmap
  #------------------------------------------------------------------------
  # * The Facade, which uses default Formatter and Artist to draw the formatted
  # text directly to a bitmap, such as self.contents
  #------------------------------------------------------------------------
  def new_paragraph (x, y, max_width, max_height, string)
    bitmap = Bitmap.new (max_width, max_height)
    bitmap.font = self.font
    pg = Paragrapher.new(Paragrapher::Formatter.new, Paragrapher::Artist.new)
    new_bitmap = pg.paragraph (string, bitmap)
    blt (x, y, new_bitmap, Rect.new (0,0,new_bitmap.width,new_bitmap.height))
  end
end


Credit
  • Zeriab, for the idea, the motivation, and the instruction, as well as one of the formatting algorithms
  • modern algebra, for the sloppy code tongue.gif
Support



Support will be provided anywhere that I (modern algebra) posts the script. At forums where it is posted by anyone else, I make no guarantees. I am willing to write formatting or artist methods to deal with any instances for which current algorithms are inapplicable or inefficient, as well as dealing with any bugs in the current algorithms.

Author's Notes


This script was inspired by Zeriab, and pretty much everything that is good about this script is due to Zeriab. Zeriab deserves more credit for this script then I do, rightly, but since the world isn't just... tongue.gif Anyway, he deserves all my thanks for being an excellent teacher.

This post has been edited by modern algebra: Nov 11 2007, 12:23 PM
Go to the top of the page
 
+Quote Post
   
Guest_RPG Wizard_*
post Nov 5 2007, 01:04 AM
Post #2





Guests





Thank you for submitting your helpful script to us!

I have added it to the main site's database here:

http://www.rpgrevolution.com/script/rgss/12

If there is any detail you want fixed or any updates, etc, feel free to contact us about it, or you can simply post it here. Thanks!
Go to the top of the page
 
+Quote Post
   
modern algebra
post Nov 5 2007, 03:41 PM
Post #3


Level 11
Group Icon

Group: Revolutionary
Posts: 191
Type: Scripter
RM Skill: Skilled




I'm honoured to have you host it on the site. Thanks.
Go to the top of the page
 
+Quote Post
   

Reply to this topicStart new topic
1 User(s) are reading this topic (1 Guests and 0 Anonymous Users)
0 Members:

 

Lo-Fi Version Time is now: 22nd May 2013 - 03:09 PM
RPG RPG Revolution is an Privacy Policy and Legal
eXTReMe Tracker