TAJGA 2D graphic library

----------------------------------------------------------------------------------------------------

TABLE OF CONTENTS:

0 - About this document

1 - Library hierarchy

2 - Structures
  2.1 - bitmap
    2.1.1 - Members 
    2.1.2 - Flag values
  2.2 - videobitmap
  2.3 - rect (internal)

3 - Procedure bodies
  3.1 - Library controlling
    3.1.1 - Init
    3.1.2 - Close
  3.2 - Bitmaps
    3.2.1 - CreateBitmap
    3.2.2 - FreeBitmap
  3.3 - Screen
    3.3.1 - SetVideoMode
    3.3.2 - ResetVideoMode
    3.3.3 - AccessVideoMode
    3.3.4 - Lock
    3.3.5 - Unlock
    3.3.6 - Retrace

4 - Routines
  4.1 - rtn.clip
  4.2 - rtn.blit_rect

5 - OS access
  5.1 - os.memalloc
  5.2 - os.memfree
  5.3 - os.Init
  5.4 - os.Close
  5.5 - os.SetVideoMode
  5.6 - os.ResetVideoMode
  5.7 - os.AccessVideoMode
  5.8 - os.Lock
  5.9 - os.Unlock
  5.10 - os.Retrace

6 - Usage
  6.1 - Error Handling
    6.1.1 - User level error handling
    6.1.2 - Common error handling
    6.1.3 - OS access error handling
    6.1.4 - Common (platform independent) errors
    6.1.5 - Finding error source

----------------------------------------------------------------------------------------------------

0 - About this document
=======================
  This document describes platform independent part of tg2d. 

  It is meant for developers of tg2d and for people who use it's default interface (currently it is
  only interface, so this is manual for all users. Users should be interested only in chapters 0, 2,
  3 and 6). 

  NOTE:
  - Word "TODO" marks something in documentation is not done.

1 - Hierarchy
==============
  TODO (You should read document "hierarchy.txt" instead, for now)
  
  Currently there is no special interface, you must access library using interface of procedure
  bodies by including it into FASM project. This would be called "default interface".

2 - Structures
==============

  For all structures variable "sizeof.<structure name>" is defined.

  All structure names have "tg2d." (note the dot) prefix (if they arent aliased in interface to some
  other name). I write names without "tg2d." here for simplicity.

  Members are separated from structure name with dot ("."). For example full name to access relative
  address of member "xsz" in structure "bitmap" is "tg2d.bitmap.Xsz". Same for static structures, to
  access to xsz of structure defined with "BMP tg2d.bitmap" you use "BMP.Xsz".

  2.1 - bitmap
  ============
    This structure describes two dimensional array of pixels.
    
    2.1.1 - Members 
    ===============
      all are dwords, their pseudo C definition follows
      
      xsz, ysz (unsigned ints)
        sizes of bitmap (in pixel). Size 0 is valid, value of "bmp" member is then zero and bitmap
        is called "empty".
      
      bmp (*(pixel[][]))
        pointer to 2 dimensional array of pixels. Type (size) of pixel depends on "bpp" member.
        Sizes of array are specified in "xsz" and "ysz" member. Bitmap is stored by lines, so it is
        "(pixel[xsz])[ysz]", or "array[1..Ysz] of array[1..Xsz] of pixel". If this member holds
        value 0, bitmap is treated as "undefined".
      
      flags (bit[32])
        Set of flags that describe bitmap. Values are described below.
      
      bpp (unsigned int)
        Number of bytes per pixel (not bits!). Describes pixel in array pointed by "bmp" member.
        Only low-order byte can be used, rest must be zero. 

      clipx, clipy, clipxsz, clipysz (unsigned ints)
        Specifies clipping region of bitmap. Clipping on bitmap is performed only when
        TG2D_BITMAP_CLIP flag is set, otherwise these members are ignored. Clipping rectangle cant
        be outside bitmap, and can't be empty eg: 
          0 <= clipx < xsz
          0 <= clipy < ysz
          0 < clipxsz <= (xsz - clipx)
          0 < clipysz <= (ysz - clipy)

    2.1.2 - Flag values
    ===================

      user flags - you specify them as you like

        none

      internal flags - you do not need to care about them
      
        0x00000002 - TG2D_BITMAP_CLIP
          If set, all operations on bitmap will be clipped to rectangle specified by ".clipx", ".clipy",
          ".clipxsz", ".clipysz" members. If flag is clear clear, these members are ignored. Set this
          flag only when you are sure clipping is required, for example flag dont have to be set
          when you are bltitting rectangle from bitmap and you are sure rectangle won't be out of
          bitmap.
        
        0x80000000 - TG2D_BITMAP_INTERNAL_ONECHUNK
          Is set only on bitmaps whose header (not only pixel array) was allocated by tg2d.  When
          you pass 0 as bitmap header pointer to CreateBitmap, it will allocate header and pixel
          array in one chunk of memory (this is where the name comes from). So FreeBitmap should
          know how bitmap is allocated and this is what is this flag for.

  2.2 - videobitmap
  =================   
    TODO
    TODO - tg2d.vbflags role, must be valid all time
    TODO - dependecies (PRESENT <- videbitmap + some other flag, MUSTBELOCKED <- locked)
    TODO - pitch in Xsz, active resolution in visxsz, clip = vis on creation

  2.3 - rect (internal)
  =====================
    TODO

3 - Procedure bodies
====================
  All procedure body names have "tg2d." prefix.

  3.1 - Library controlling
  =========================
  
    3.1.1 - Init
    ============
      This procedure initializes library (makes it usuable) and also returns you current video
      framebuffer so you can modify screen, not only change it whole. 

      args: none
      returns: 
        eax = -1 on error, otherwise undefined
        other GPRs preserved
        tg2d.vbflags filled
      notes:
        - this procedure must be called before any other procedure. Otherwise other procedures
          returns TG2D_ERROR_NOTINITIALIZED (value 1) error. 
      errors:
        TG2D_ERROR_INITIALIZED (value 2)
          this value is returned when function is called when library is already initialized. You
          are calling Init twice. You can ignore this error.
        + errors returned by os.Init
  
    3.1.2 - Close
    =============
      This procedure uninitializes library. After it is called, library cannot be used and only
      procedure you can call then is Init to intialize it again.
  
      args: none
      returns: 
        eax = -1 on errors, otherwise value is undefined
        other GPRs preserved
      notes:
        - If dislay mode was changed and wasnt restored, then it is restored now.
        - If video bitmap is locked, it will be unlocked
        - Close should be able to clean up even after tg2d error occured
        - If Close returns error (except TG2D_ERROR_NOTINITIALIZED) then library is still
          initialized and you can use it.
      errors:
        TG2D_ERROR_NOTINITIALIZED (value 1)
          Procedure was called when library is not initialized. You must call Init before.
        + errors returned by os.Close
        + errors returned by ResetVideoMode if video mode is changed when called.
        + errors returned by Unlock if screen is locked when called

  3.2 - Bitmaps
  =============    

    3.2.1 - CreateBitmap
    ====================
      args:
        ebx, ecx (unsigned ints)
          sizes of bitmap to create. Value must be in range 0 to 7FFFFFFFh.
        edx (unsigned int)
          bytes per pixel of bitmap to create. Value must be in range 0 to 0FFh
        eax (bit[])
          flags of bitmap. Only user flags are allowed here, other types of flags are defined by
          this procedure
        edi (bitmap*)
          0 or pointer to bitmap header. 
      returns:
        eax = -1 on errors, otherwise pointer to new bitmap
        other GPRs are reserved
      notes:
        - if you specify edi other than 0, then value returned in eax remains in edi too (of course)
        - If edi contains 0, CreateBitmap will allocate header in same block of memory with pixel
          array and set TG2D_BITMAP_INTERNAL_ONECHUNK flag in "flags" member. If header is specified
          in edi only pixel array is allocated.
        - If TG2D_BITMAP_CLIP flag is specified in eax, then clip rectangle is set to entire bitmap
          (clipx = 0, clipy = 0, clipxsz = xsz = ebx, clipysz = ysz = ecx)
      errors:
        TG2D_ERROR_NOTINITIALIZED (value 1)
          Init wasnt called
        TG2D_ERROR_OUTOFRANGE (value 4)
          ebx or ecx above 7FFFFFFFh, some position wouldnt be addressable. You can't have such big
          bitmap, but probably you have bug in your code.
        TG2D_ERROR_OVERFLOW (value 5)
          size of pixel array too big: xsz*ysz*bytes_per_pixel >= 2^32, again like previous one.
        TG2D_ERROR_INVALIDARGS (value 6)
          non-user flags specified in eax. Call was ignored and no bitmap was created.
        + errors returned by memalloc

    3.2.2 - FreeBitmap
    ==================
      args:
        edi (bitmap*)
          pointer to bitmap to free
      returns:
        eax = -1 on errors, undefined otherwise
        other GPRs are reserved
      notes:
        - if bitmap has TG2D_BITMAP_INTERNAL_ONECHUNK flag set, then header is freed too, otherwise
          all header members are zeroed. (see CreateBitmap for more info on this)
        - you cannot free video bitmap, if you try so TG2D_ERROR_INVALIDARGS error is returned.
      errors:
        TG2D_ERROR_NOTINITIALIZED (value 1)
          Init wasnt called
        TG2D_ERROR_UNDEFINEDBITMAP (value 9)
          bitmap you entered was undefined ("bmp" member is 0). You are freeing bitmap twice or you
          are trying to free uncreated bitmap. You can ignore this error.
        TG2D_ERROR_INVALIDARGS (value 6)
          you are trying to free video bitmap. You can ignore this error.
        + errors returned by memfree

  
  3.3 - Screen
  ============
    Screen is called video framebuffer. Screen is described by video bitmap (generally called video
    framebuffer, but it is described by tg2d.bitmap structure so i use name video bitmap). You
    receive video bitmap pointer from Init or SetVideoMode.

    3.3.1 - SetVideoMode
    ====================
      Sets another display mode (resolution and bit depth)

      args:
        ebx = X resolution of mode to set
        ecx = Y resolution of mode to set
        edx = bytes per pixel of mode to set. (BYTES, not bits!)
      returns:
        eax = -1 on error, otherwise it is pointer to "tg2d.videobitmap"
      notes:
        - Xsz of returned video bitmap can be higher than one specified in ebx, see video bitmap
          description for more informations on this.
      errors:
        TG2D_ERROR_NOTINITIALIZED (value 1)
          Init wasnt called
        + errors returned by os.SetVideoMode
    
    3.3.2 - ResetVideoMode
    ======================
      Returns display to original video mode (the one it was in when Init was called).

      args: none
      returns:
        eax = -1 on error, pointer to "tg2d.videobitmap" if supported, or 0 if access to screen is
          not supported now
      notes:
        - called automaticaly by Close if you changed display mode and didnt restore it. But better
          do not rely on this behavior, it would be unclear for anyone reading your code.
        - in version for win32 ddraw it returns to state when first SetVideoMode was called (instead
          of Init), but this shouldn't matter very much.
        - Xsz of returned video bitmap can be higher than resolution's X, see video bitmap
          description for more informations on this.
        - Note this sets original video mode, but doesn't get access to it. This is done by
          AccessVideoMode
      errors:
        TG2D_ERROR_NOTINITIALIZED (value 1)
          Init wasnt called
        TG2D_ERROR_ALREADYDONE (value 10)
          You are in original display mode already, this call is obsolete. You can ignore this
          error.
        + errors returned by os.ResetVideoMode

    3.3.3 - AccessOriginalMode
    ==========================
      This call fives you access to screen in without changing video mode. It can be only used to
      get access to original mode (in which display was when Init was called). If display isn't in
      original mode (SetVideoMode was called, without restoring with ResetVideoMode), then
      ResetVideoMode will be called automaticaly
      
      args: none
      returns:
        eax = -1 on error, pointer to tg2d.videobitmap otherwise
      notes:
        - if you use this proc to restore original mode, then also check ResetVideoMode's
          description
        - on some platforms returned video bitmap structure has "bmp" member set to 0 and must be
          locked before accessing. This should be detected by checking TG2D_VIDEOBITMAP_MUSTBELOCKED
          flag in "tg2d.vbflags".
        - Xsz of returned video bitmap can be higher than current resolution's one, see video bitmap
          description for more informations on this.
      errors:
        TG2D_ERROR_NOTINITIALIZED (value 1)
          Init wasnt called
        TG2D_ERROR_ALREADYDONE (value 10)
          You already called AccessOriginalMode, use GetVideoMode <TODO - unsupported> to get video
          bitmap again (but better store it after call to AccessOriginalMode). You can ignore this
          error.
        + errors returned by os.AccessOriginalMode
        + errors returned by ResetVideoMode if proc wasn't called in original video mode
        

    
    3.3.4 - Lock
    ============
      On some platforms video bitmap must be "locked" before you can use it. When you use video
      bitmap in tg2d routine then tg2d locks it for you if it is needed, but in some cases you want
      to lock it yourself (like when you want to modify it with non-tg2d routine)
      
      args: none
      returns:
        tg2d.videobitmap.bmp = valid pointer to displayed pixel array. "tg2d.videobitmap" is pointed
          by return value of Init and SetVideoMode.
        eax = -1 on error, undefined otherwise
        other GPRs are reserved
      notes:
        - On platforms where locking is not required this procedure does nothing, otherwise it sets
          "tg2d.videobitmap.bmp" to valid pointer.
        - Locking takes lot of time, so video bitmap should be locked only once per drawing loop. If
          you make more consectutive draws onto video bitmap in one loop, you should call Lock
          before, otherwise video bitmap is locked and unlocked by tg2d for each draw.
      errors:
        TG2D_ERROR_NOTINITIALIZED (value 1)
          Init wasnt called
        TG2D_ERROR_NOVIDEOBITMAP (value 11)
          Video bitmap is not accessible now. You didn't get access to screen with
          AccessOriginalMode or SetVideoMode. Nothing is locked, otherwise you can ignore this
          error.
        TG2D_ERROR_ALREADYDONE (value 10)
          Video bitmap is locked already. You can ignore this error.
        + errors returned by os.Lock


    3.3.5 - Unlock
    ==============
      Unlocks locked bitmap.

      args: none
      returns:
        tg2d.videobitmap.bmp = 0 on platforms on which locking is required. "tg2d.videobitmap" is
          pointed by return value of Init and SetVideoMode.
        eax = -1 on error, undefined otherwise
        other GPRs are reserved
      notes:
        - you should try to spend as few time as possible between Lock and Unlock, because purpose
          of locking is to suspend other processes to make sure no one else will change screen. So
          while video bitmap is locked multitasking is disabled.
        - video bitmap is unlocked automaticaly when Close is called when it is locked
        - if locking is not required, this procedure just returns (before checking for any errors
          except TG2D_ERROR_NOTINITIALIZED).
      errors:
        TG2D_ERROR_NOTINITIALIZED (value 1)
          Init wasnt called
        TG2D_ERROR_NOVIDEOBITMAP (value 11)
          Video bitmap is not accessible now. You didn't get access to screen with
          AccessOriginalMode or SetVideoMode. Nothing is unlocked, otherwise you can ignore this
          error.
        TG2D_ERROR_ALREADYDONE (value 10)
          Video bitmap is unlocked already
        + errors returned by os.Unlock

    3.3.6 - Retrace
    ===============
      Gets vertical retrace status. For smooth animation you should write screen only during
      vertical retrace

      args: none
      returns:
        eax = -1 on error, 0 when retrace is not in progress, 1 during retrace
        other GPRs are reserved
      errors: 
        TG2D_ERROR_NOTINITIALIZED (value 1)
          Init wasnt called
        + errors returned by os.Retrace


4 - Routines
============
  Routines are procedures which do one time-consuming or often-used task. They are called from
  procedure bodies. Routines can't call any other procedures, only other routines. Routines have
  'tg2d.rtn.' name prefix, in this text only 'rtn.' will be used.
  
  4.1 - rtn.clip
  ==============
    This routines clips source (draw) rectangle into destination rectangle.

    TODO
      
  
  4.2 - rtn.blit_rect
  ===================
    TODO

5 - OS access
=============
  This information is important only for people who want to develop TG2D port for some unsupported
  platform. It describes interface which is used for wrapped platform access. Example of such wrap
  is file "os_access\ddraw.inc". 

  (Almost) all symbols you define must have "tg2d.os." prefix. I write only "os." in this document.
  
  You shouldn't define any macros, if you do then you should purge them.
    
  Do not await that some standard library headers are included, look up constants in library's
  include files and write them as a numeric value with their name in comment, like:
    push 32512 ;IDI_APPLICATION

  Of course, some symbols have to be defined (system procedure pointers), but try to keep their
  number as small as possible. Do not force user to include something he may not want to have
  included (if he includes tg2d into his project he includes everything that tg2d includes).

  You must support few OS access procedures for every platform. On error you must fill error info
  structure and return with CF flag set. If no error error occured, you must return with CF flag
  clear. Errors which are listed here are ones that OS access procedure must support. It can also
  return other errors, which must then be described in platform dependant docs.

  OS access procedure can change all general purpose registers (except ESP of course).

  5.1 - os.memalloc
  =================      
    Allocates memory
      
    args:
      eax = size of memory block to allocate, in bytes
    returns:
      CF = 1 on error
      eax = pointer to allocated block      

  5.2 - os.memfree
  =================
    Frees memory allocated by os.memalloc
    
    args:
      eax = pointer to memory block to free, returned by os.memalloc
    returns:
      CF = 1 on error
    
  5.3 - os.Init
  ==============
    OS dependant part of Init. That means it
      1. Fills variables defined by OS-dependant part with default value. This is important, all
         values must be filled here.
      2. Gains information about user of library needed later, initializes programs used by
         OS-dependant part (like ddraw).
      3. If possible, gives user access to modify screen.
    
    args: none
    returns: 
      CF = 1 on error
      tg2d.vbflags filled
        
  5.4 - os.Close
  ============
    OS dependant part of Close. It should just undo all that Init did.
    
    args: none
    returns:
      CF = 1 on error
    notes:
      - remember that library should be operable after calling Close and then calling Init again!
      - os.Close should be able to clean up even after tg2d error occured, remember this when
        writing other procedures!
  
  5.5 - os.SetVideoMode
  =====================
    This one should set display mode and fill tg2d.videobitmap and tg2d.vbflags.

    args:
      ebx = x resoultion
      ecx = y resolution
      edx = bytes per pixel (bytes, not bits!)
    returns:
      CF = 1 on error
      tg2d.videobitmap and tg2d.vbflags
    notes:
      - be sure to have tg2d.videobitmap filled with valid values even on case of error. For
        example if Init failed during getting access to screen, flag TG2D_VIDEOBITMAP_PRESENT
        should be clear, if it failed later flag should be set and tg2d.videobitmap filled.
    errors:
      TG2D_ERROR_UNSUPPORTEDMODE (value 7)
        Mode you specified isn't supported by graphic driver / platform. No mode is set and you
        remain in original video mode. If access to mde was gained (via AccessOriginalMode) then it
        is lost.
  
  5.6 - os.ResetVideoMode
  =======================
    This procedure should restore display changed by SetVideoMode to state it was in when Init was
    called (last time).

    args: none
    returns:
      CF = 1 on error
      tg2d.vbflags filled
    notes:
      - access isn't gained here, AccessOriginalMode does this. This procedure must clear 
        TG2D_VIDEOBITMAP_PRESENT flag in tg2d.vbflags.
      - called only when display isn't in original mode (this is checked in ResetVideoMode)

  5.7 - os.AccessVideoMode
  ========================
    This procedure is called only when display is in original mode. It gains access to original mode
    
    args: none
    returns:
      CF = 1 on error
      tg2d.videobitmap and tg2d.vbflags
    notes:
      - be sure to have tg2d.videobitmap filled with valid values even on case of error. For
        example if Init failed during getting access to screen, flag TG2D_VIDEOBITMAP_PRESENT
        should be clear, if it failed later flag should be set and tg2d.videobitmap filled.
      - you can predict access isn't gained already (this is checked in AccessVideoMode)
    errors:
      TG2D_ERROR_UNSUPPORTEDMODE (value 7)
        Accessing original mode isn't supported by platform.
      
  
  5.8 - os.Lock
  =============
    This procedure should lock video bitmap, set flag TG2D_VIDEOBITMAP_LOCKED in tg2d.vbflags and
    set tg2d.videobitmap.bmp.

    args: none
    returns:
      CF=1 on error
    notes:
      - if locking is not required on platform, and thus TG2D_VIDEOBITMAP_MUSTBELOCKED flag then
        it is enough to define label for procedure (It will never be called).
    
  5.9 - os.Unlock
  ===============
    This procedure should unlock video bitmap, and set flag TG2D_VIDEOBITMAP_LOCKED in
    tg2d.vbflags.

    args: none
    returns: CF=1 on error
    notes:
      - if locking is not required on platform, and thus TG2D_VIDEOBITMAP_MUSTBELOCKED flag then
        it is enough to define label for procedure (It should never be called).

  5.10 - os.Retrace
  ================
    This procedure should check vertical retrace status and return it: eax = 1 during retrace, 0
    otherwise.
    
    args: none
    returns: 
      CF=1 on error
      eax = 1 during retrace, 0 otherwise
    


6 - Usage
=========

  6.1 - Error Handling
  ====================
    There are severals layers of error handling.
    
    6.1.1 - User level error handling
    =================================
      This chapter describes default error handling provided to user. This can be changed by
      interface of library, so every interface description should contain description of it's error
      handling or note that it uses default error handling.
      
      After calling any TG2D procedure, user should check value of eax register for value -1. If it
      contains error, then value of dword "tg2d.error' is nonzero and specifies error. Common
      (platform independant) errors are enumrated from 1 upwards. Platform dependant errors are
      enumerated from -1 downwards. For each error constant TG2D_ERROR_<error name> is defined.

      If some error occured then dword variable "tg2d.error.proc" contains pointer to procedure in
      which error occured. Altough user must check for error after each procedure call, so he should
      know which procedure he called when error occured, this information is useful in case when
      called tg2d procedure calls another tg2d procedure. Both procedures can be returning same
      error, but with different meaning so this way you can differentiate. 
      I know this system is not 100%, it would require call stack of tg2d procedures to fully
      describe error placement, but in most cases this is enough and it is much easier to use.
      
      Values in "tg2d.error.nameA" and "tg2d.error.nameW" are pointers to ASCII and unicode error
      name. Name is same, as name in library sources, for example 'TG2D_ERROR_NOTINITIALIZED'.

      If you are using some 3rd party engine (like directdraw, including OS) which returns some
      errors that are not translated to TG2D_ERROR_ form, then you must define platform dependant
      error (with negative value), like TG2D_ERROR_WINDOWS, which means that value of dword variable
      "tg2d.error.originial" contains error returned by 3rd party engine. Meaning of contents of
      "tg2d.error.originial" depends on TG2D_ERROR_ value and should be described in platform
      dependant documentation. In this case "tg2d.error.name*" variables should better contain
      original error name (like 'DDERR_INVALIDMODE'), not tg2d's (like 'TG2D_ERROR_DIRECTDRAW')
      <TODO - same for error desription>

      NOTES:
        - User is responsible for handling returned error. If you call some procedure after error
          occured in previous procedure, library wouldnt check this case and would act is if no
          error occured. It's on user to check for error after EVERY tg2d procedure call.
        - It is possible that in future there will be some procedure which will return -1 as valid
          value, and so that i will change error handling. So it would be better checking testing
          (tg2d.error != 0) instead of (eax == -1). However, currently i cant imagine such procedure
          so i still use (eax == -1) way. If you use (tg2d.error != 0) way, it would only be good
          for you, because you will be forward - compatible. I wont like breaking backwards
          compatibility, but maybe i'll be forced to.
        - After tg2d error occured and wasn't handled user should call tg2d.Close to clean up.
        - When looking for descrption of meaning of error returned by some procedure, always check
          ALL links (and all their links etc.). Same error returned by one procedure can have
          multiple meanings (if procedure calls another tg2d procedure for example). You can find
          procedure which really caused error by looking at "tg2d.error.proc". (Sorry for links, but
          this way docs are much easier to maintain).
          
    6.1.2 - Common error handling
    =============================
    TODO - bout this chapter, OnError

    6.1.3 - OS access error handling
    ================================
      This chapter describes standard used in passing errors from OS access part to OS independant
      part of library.
      
      When error occured in OS-access procedure, it must return with CF set, otherwise it must
      return with CF clear. If it returns error, eax must contain -1 and all error informations must
      be filled, so that procedure body will only return to interface. Values of other tg2d
      variables should too validly describe situation.

      TODO - TEMPORARY:
      OS access part must define string table for platform dependant error names. First member of
      table is for error -1, second is for error -2 etc. There should be two tables, one for ASCII
      and one for unicode, named "tg2d.os.error.nametableA" and "tg2d.os.error.nametableW". (String
      table is array of pointers to strings).
      
    6.1.4 - Common (platform independent) errors
    ============================================
      TODO
    
vim: tw=100 expandtab
