def _preformat( text, layers, markup_settings = None ):
   ''' Formats given text, adding a special ( ANSI-terminal compatible ) markup
       to the annotations of given layers, and returns formatted text as a
       string.
       *) layers is a list containing names of the layers to be preformatted in 
          the text (these layers must be present in Text);
       *) markup_settings should be a list containing annotation options for each
          layer: one dict with options per layer;
          One dict can contain the following visualization options:
          * 'bracket' : True -- annotations will be surrounded with brackets; This 
                                works in any terminal;
          * 'underline' : True -- annotations will be underlined; This works in ANSI 
                                  compatible terminal;
          * 'color' : ('red', 'green', 'blue' etc. ) -- annotated text will be 
                      displayed in given color; this works in ANSI compatible 
                      terminal;
       *) Alternatively, if markup_settings is undefined, up to 12 layers can be 
          visualized following the default settings;
        
       Parameters
       ----------
       text: Text
          a text object. Must contain given layers;
       layer: list of str
          list of layer names to be visualised;
       markup_settings: list of dict
          list of dictionaries containing user-defined visualization options;
          (one dict per layer)
        
       Returns
       -------
       text: str
          preformatted text, where elements of given layers have been marked up, using 
          an ANSI-terminal compatible markup;
   '''
   if markup_settings and len(layers) != len(markup_settings):
       raise Exception(' Input arguments layers and markup_settings should be equal size lists.')
   elif not markup_settings and len(layers) <= len(default_markup_settings):
       # Use default markup settings
       markup_settings = default_markup_settings[0:len(layers)]
   elif not markup_settings:
       raise Exception(' Input argument markup_settings not defined.')
   #
   # 1) Construct the index of annotations (for each layer); 
   #    Annotations are indexed by their start positions;
   #    The index also contains start and end tags of each annotation;
   spansStartingFrom = {}
   for i in range( len(layers) ):
       layer    = layers[i]
       settings = markup_settings[i]
       spansStartingFrom = _construct_start_index(text, layer, settings, spansStartingFrom)
   #
   # 2) Fix overlapping graphic annotations in the index
   #    (to ensure continuous formatting of annotations)
   _fix_overlapping_graphics( spansStartingFrom )
   #
   # 3) Index the annotations by their end positions
   endTags = _construct_end_index( spansStartingFrom )
   
   #
   # 4) Construct the output string
   return_str = []
   for i in range( len(text[TEXT]) ):
       c = text[TEXT][i]
       emptyTags = []
       if i in endTags:
          for tag in endTags[i]:
              if tag[1] != i:
                 # Non-empty tag
                 return_str.append( tag[0] )
              else:
                 # Empty tag
                 emptyTags.append( tag )
       if i in spansStartingFrom:
          for span in spansStartingFrom[i]:
              return_str.append( span[3] )
              if span[0] == span[1]:
                 # Empty tag: Add the closing tag
                 for emptyEndTag in emptyTags:
                     if span[2] == emptyEndTag[2]:
                        return_str.append( emptyEndTag[0] )
       return_str.append( c )
   if len(text[TEXT]) in spansStartingFrom:
       for span in spansStartingFrom[len(text[TEXT])]:
           return_str.append( span[3] )
   if len(text[TEXT]) in endTags:
       for tag in endTags[len(text[TEXT])]:
           return_str.append( tag[0] )
   # Hack: fix for a potential overflow / unclosed graphics
   if return_str and '\033' in return_str[-1] and \
      not return_str[-1].endswith('\033[0m'):
      return_str.append( '\033[0m' )
   return ''.join(return_str)
Formats given text, adding a special ( ANSI-terminal compatible ) markup
       to the annotations of given layers, and returns formatted text as a
       string.
       *) layers is a list containing names of the layers to be preformatted in 
          the text (these layers must be present in Text);
       *) markup_settings should be a list containing annotation options for each
          layer: one dict with options per layer;
          One dict can contain the following visualization options:
          * 'bracket' : True -- annotations will be surrounded with brackets; This 
                                works in any terminal;
          * 'underline' : True -- annotations will be underlined; This works in ANSI 
                                  compatible terminal;
          * 'color' : ('red', 'green', 'blue' etc. ) -- annotated text will be 
                      displayed in given color; this works in ANSI compatible 
                      terminal;
       *) Alternatively, if markup_settings is undefined, up to 12 layers can be 
          visualized following the default settings;
        
       Parameters
       ----------
       text: Text
          a text object. Must contain given layers;
       layer: list of str
          list of layer names to be visualised;
       markup_settings: list of dict
          list of dictionaries containing user-defined visualization options;
          (one dict per layer)
        
       Returns
       -------
       text: str
          preformatted text, where elements of given layers have been marked up, using 
          an ANSI-terminal compatible markup;