1

I'm trying to make a fortan code for displaying a colored ASCII art of 2D graphics in command prompt on Windows 10 systems, like a code distributed in the website below.

https://sites.google.com/site/akohlmey/random-hacks/text-mode-graphics-for-fortran

I have heard that command prompt on Windows 10 partially support ANSI escape sequences, and it is available if we enable the virtual terminal processing option.

There are some examples in C code, but I want to make a function or subroutine to enable ANSI escape sequences by Fortran code. For example, is it difficult to rewrite following simple C function by a Fortran function (or subroutine)?

bool enable_virtual_terminal_processing(FILE *stream) {
    HANDLE handle = (HANDLE)_get_osfhandle(_fileno(stream));
    DWORD mode = 0;
    if (!GetConsoleMode(handle, &mode)) {
        return false;
    }
    if (!SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
        return false;
    }
    return true;
}
kachigusa
  • 219
  • 1
  • 9
  • For the reader passing by, what you have heard about ANSI sequence is confirmed here: https://stackoverflow.com/questions/16755142/how-to-make-win32-console-recognize-ansi-vt100-escape-sequences I didn't try though. –  Nov 19 '19 at 11:12

2 Answers2

3

An example below of Fortran source calling the necessary Windows APIs to set the console to the correct mode. Calling Windows APIs makes this source inherently platform specific - there are also some compiler specific directives (ifort and gfortran variants provided) necessary to fully define the API interfaces.

The library for the Win32 kernel API's will also need to be accessible, but this is often installed (or required) as part of installing a compiler on Windows.

Some compilers provide modules with the relevant interfaces to the Windows API, saving you the trouble of having to write them yourself.

PROGRAM WriteAnsi
  IMPLICIT NONE

  CALL set_ansi

  PRINT "(A)",  &
      ACHAR(27) // '[31m' // 'H' //  &
      ACHAR(27) // '[32m' // 'e' //  &
      ACHAR(27) // '[33m' // 'l' //  &
      ACHAR(27) // '[34m' // 'l' //  &
      ACHAR(27) // '[35m' // 'o' //  &
      ACHAR(27) // '[0m'

CONTAINS
  SUBROUTINE set_ansi
    USE, INTRINSIC :: ISO_C_BINDING, ONLY:  &
        DWORD => C_LONG,  &    ! C_INT32_T really, but this is per the docs
        HANDLE => C_INTPTR_T,  &
        BOOL => C_INT

    INTEGER(HANDLE), PARAMETER :: INVALID_HANDLE_VALUE = -1_HANDLE

    INTERFACE
      FUNCTION GetStdHandle(nStdHandle) BIND(C, NAME='GetStdHandle')
        IMPORT :: DWORD
        IMPORT :: HANDLE
        IMPLICIT NONE
        INTEGER(DWORD), INTENT(IN), VALUE :: nStdHandle
        INTEGER(HANDLE) :: GetStdHandle
        !DEC$ ATTRIBUTES STDCALL :: GetStdHandle
        !GCC$ ATTRIBUTES STDCALL :: GetStdHandle
      END FUNCTION GetStdHandle
    END INTERFACE
    INTEGER(DWORD), PARAMETER :: STD_INPUT_HANDLE = -10_DWORD
    INTEGER(DWORD), PARAMETER :: STD_OUTPUT_HANDLE = -11_DWORD
    INTEGER(DWORD), PARAMETER :: STD_ERROR_HANDLE = -12_DWORD

    INTERFACE
      FUNCTION GetConsoleMode(hConsoleHandle, lpMode) BIND(C, NAME='GetConsoleMode')
        IMPORT :: HANDLE
        IMPORT :: DWORD
        IMPORT :: BOOL
        IMPLICIT NONE
        INTEGER(HANDLE), INTENT(IN), VALUE :: hConsoleHandle
        INTEGER(DWORD), INTENT(OUT) :: lpMode
        !DEC$ ATTRIBUTES REFERENCE :: lpMode
        INTEGER(BOOL) :: GetConsoleMode
        !DEC$ ATTRIBUTES STDCALL :: GetConsoleMode
        !GCC$ ATTRIBUTES STDCALL :: GetConsoleMode
      END FUNCTION GetConsoleMode
    END INTERFACE
    INTEGER(DWORD), PARAMETER :: ENABLE_ECHO_INPUT = INT(Z'0004', DWORD)
    INTEGER(DWORD), PARAMETER :: ENABLE_INSERT_MODE = INT(Z'0020', DWORD)
    INTEGER(DWORD), PARAMETER :: ENABLE_LINE_INPUT = INT(Z'0002', DWORD)
    INTEGER(DWORD), PARAMETER :: ENABLE_MOUSE_INPUT = INT(Z'0010', DWORD)
    INTEGER(DWORD), PARAMETER :: ENABLE_PROCESSED_INPUT = INT(Z'0001', DWORD)
    INTEGER(DWORD), PARAMETER :: ENABLE_QUICK_EDIT_MODE = INT(Z'0040', DWORD)
    INTEGER(DWORD), PARAMETER :: ENABLE_WINDOW_INPUT = INT(Z'0008', DWORD)
    INTEGER(DWORD), PARAMETER :: ENABLE_VIRTUAL_TERMINAL_INPUT = INT(Z'0200', DWORD)
    INTEGER(DWORD), PARAMETER :: ENABLE_PROCESSED_OUTPUT = INT(Z'0001', DWORD)
    INTEGER(DWORD), PARAMETER :: ENABLE_WRAP_AT_EOL_OUTPUT = INT(Z'0002', DWORD)
    INTEGER(DWORD), PARAMETER :: ENABLE_VIRTUAL_TERMINAL_PROCESSING = INT(Z'0004', DWORD)
    INTEGER(DWORD), PARAMETER :: DISABLE_NEWLINE_AUTO_RETURN = INT(Z'00008', DWORD)
    INTEGER(DWORD), PARAMETER :: ENABLE_LVB_GRID_WORLDWIDE = INT(Z'0010', DWORD)

    INTERFACE
      FUNCTION SetConsoleMode(hConsoleHandle, dwMode) BIND(C, NAME='SetConsoleMode')
        IMPORT :: HANDLE
        IMPORT :: DWORD
        IMPORT :: BOOL
        IMPLICIT NONE
        INTEGER(HANDLE), INTENT(IN), VALUE :: hConsoleHandle
        INTEGER(DWORD), INTENT(IN), VALUE :: dwMode
        INTEGER(BOOL) :: SetConsoleMode
        !DEC$ ATTRIBUTES STDCALL :: SetConsoleMode
        !GCC$ ATTRIBUTES STDCALL :: SetConsoleMode
      END FUNCTION SetConsoleMode
    END INTERFACE
    INTEGER(DWORD), PARAMETER :: ENABLE_EXTENDED_FLAGS = INT(Z'0080', DWORD)

    INTEGER(HANDLE) :: output_handle
    INTEGER(BOOL) :: api_result
    INTEGER(DWORD) :: mode

    output_handle = GetStdHandle(STD_OUTPUT_HANDLE)
    IF (output_handle == INVALID_HANDLE_VALUE) THEN
      ERROR STOP 'GetStdHandle failed'
    END IF

    api_result = GetConsoleMode(output_handle, mode)
    IF (api_result == 0_BOOL) THEN
      ERROR STOP 'GetConsoleMode failed'
    END IF

    api_result = SetConsoleMode(  &
        output_handle,  &
        IOR(mode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) )
    IF (api_result == 0_BOOL) THEN
      ERROR STOP 'SetConsoleMode failed'
    END IF

  END SUBROUTINE set_ansi

END PROGRAM WriteAnsi
IanH
  • 21,026
  • 2
  • 37
  • 59
  • @lanH I'm sorry for my delayed response, and I really appreciate your "perfect" Fortran code example. I have compiled and tested it. Firstly, the calling of "SetConsoleMode" function was failed, because my command prompt environment was set to be legacy console mode. But, after I have disabled the legacy console mode, I could obtain correct results, in which colored characters were shown on a command prompt window. Thank you very much for your nice code again! – kachigusa Nov 20 '19 at 11:21
1

It is easy to call a C function from Fortran (hundreds of questions and answers here).

And it is almost impossible to write the above in Fortran, it is systems programming, you would need the right libraries Maybe some compilers supply them, but it is not standard.

Just call your C function from Fortran. The FILE * argument wonn't be available in Fortran directly, you need to get the pointer in C as well.

After enabling, you can write the sequences very easily How to print in the same position in fortran clear screen in Fortran

  • Thanks a lot for your prompt advice. According your comments, I feel that calling external C function from Fortran is a better way. But, although it is probably not standard and compatible ways, if there is a good library provided from compilers, I want to use it. Do you know whether or not such a library is provided by Intel Fortran Compiler? (I use Intel Compiler v18.) – kachigusa Nov 19 '19 at 07:03
  • I very much doubt there is a library for this specifically. There are other libraries for windows specific tasks (some easy windowing), but you have to check your manual. – Vladimir F Героям слава Nov 19 '19 at 08:58
  • I really appreciate your prompt reply. I would like to check the compiler's manual, together with trying the former idea (i.e., calling a C function from Fortran). – kachigusa Nov 19 '19 at 09:06
  • @kachigusa You may use [Windows' console API](https://learn.microsoft.com/en-us/windows/console/console-functions). Intel Fortran has many bindings to Windows API. To set text color there is the [SetConsoleTextAttribute function](https://learn.microsoft.com/en-us/windows/console/setconsoletextattribute) function, for instance. See also [SetConsoleCursorPosition](https://learn.microsoft.com/en-us/windows/console/setconsolecursorposition). And this: https://software.intel.com/en-us/fortran-compiler-developer-guide-and-reference-calling-windows-api-routines –  Nov 19 '19 at 11:06