Copy Link
Add to Bookmark
Report
NULL mag Issue 05 15 Ansi parsing with FreePascal
__ _ __ _ _ __
______\ \_\\_______________________\///__________________________//_/ /______
\___\ /___/
| .__ __ |
| | ___ __________/ |________ |
| \ \/ / ____/\ __\_ __ \ |
; > < <_| | | | | | \/ ;
: /__/\_ \__ | |__| |__| :
. \/ |__| .
. .
: H/Q Another Droid BBS - andr01d.zapto.org:9999 :
; ;
+ --- -- - . - --- --- --- - . - -- --- +
: :
| FreePascal ANSI Viewer |
: :
` --- -- - . - --- --- --- - . - -- --- '
One of the "musts" back then, was for a program to display ansi files,
graphics. From then, many of us have forgotten how to do so and
others, because they use external tools, they think its easy. So lets
take a dive into programming and check the logic/algorithm on how to
manipulate ansi files.
ANSI codes start with an ESCape char (d27) and end with a letter. So
in our code we have to check if we found an Escape char and read until
we find a valid ansi code letter/code. You can find many ansi codes in
this link: https://conemu.github.io/en/AnsiEscapeCodes.html
We will use this document to "translate" ansi codes.
So we begin by opening the file and reading it byte to byte. If we
encounter an escape char then we fill a string buffer, until we reach
a valid ansi code. If we don't find an escape, then we just display
the character.
while (not eof(f)) and (done=false) do begin
blockread(f,b,1); // read file one byte at a time
case b of
#27: doesc; // found ESC? process code
#13: delay(d); // Found end of line? add some delay to make the
// ansi more easy to view
else
write(b); // plain char? then show it on screen
lastch:=b; // save the last char we display (for ansi code)
end;
end;
All the work is done in the DOESC procedure. In here we will process
any ansi code and accordingly will show a character, move the cursor
etc. So lets see how this works.
procedure doesc;
var
buf:string[255];
j:byte;
begin
buf:='';
blockread(f,b,1); // read the file and fill our
while length(buf)<255 do begin // buffer until we reach a
blockread(f,b,1); // valid ansi code
buf:=buf+b;
if b in
// All these are valid ansi codes
['m','J','H','f','A','B','C','D','u','s','K','@','F','E','G','X','b','d']
then break;
end;
if length(buf)<1 then exit;
cnt:=strwordcount(buf,';'); // Some ANSI codes have parameters
c:=buf[length(buf)]; // We count how many we have,
delete(buf,length(buf),1); // store the code in C and clean
case c of // the buffer from any code.
'd': gotoline(buf);
'b': repeatlastchar(buf); // Read the link above to see what
'X': erasechars(buf); // commands do. The most important
'm': ansicoloring(buf); // is "m" which alters the colors
'K': clearline(buf);
'A': cursorup(buf);
'B': cursordown(buf);
'C': cursorright(buf);
'D': cursorleft(buf);
'E': linesdown(buf);
'F': linesup(buf);
'G': gotocol(buf);
'f','H': cursormove(buf);
's': begin
savex:=wherex;
savey:=wherey;
end;
'u' : gotoxy(savex,savey);
'@' : insertspaces(buf);
'J' : begin
if str2int(strwordget(1,buf,';')) = 2 then ClrScr;
End;
end;
end;
We use a CASE statement to check what command we have to parse and
execute its code. The parameters for some codes are in a format like:
1;30;44. To get each parameter i use the StrWordGet command which is
not the perfect way to do, but its more easy to understand. This
command will give as string between the semicolon ; that we ask for.
For example if we give strwordget(2,buffer,';') it will give us the
string 30, from the example above, which is a color code. This way we
have an easy way to get the parameters.
From now on, its just "translating" what the ANSI command does, into
code. If for example we encounter a command that moves the cursor x
lines below, we parse this code and move the cursor with the GOTOXY
command.
The most "bizarre" command is "m" which changes the color to be
displayed and has many parameters and features. I am not going to
explain it here, cause it needs a whole article by its self.
Grab the program below, compile it with FreePascal and you have an
easy ANSI viewer for your system. Don't forget to change the terminal
encoding to CP437 or a similar... it will not show correct in UTF8
terminals... which is also a subject for another article... enjoy!
+ --- -- - . - --- --- --- - . - -- --- '
COMPLETE PROGRAM - COPY/PASTE and COMPILE with FREEPASCAL
+ --- -- - . - --- --- --- - . - -- --- '
program dispansi;
{$mode objfpc}{$H-}
uses crt,dos;
Function Str2Int (Str: String): LongInt;
Var
N : LongInt;
T : LongInt;
Begin
Val(Str, T, N);
Str2Int := T;
End;
Function Int2Str (N: LongInt): String;
Var
T : String;
Begin
Str(N, T);
Int2Str := T;
End;
Function strWordCount (Str: String; Ch: Char) : Byte;
Var
Start : Byte;
Res : Byte;
Begin
Res := 0;
If Ch = ' ' Then
While Str[1] = Ch Do
Delete (Str, 1, 1);
If Str = '' Then Exit;
Res := 1;
While Pos(Ch, Str) > 0 Do Begin
Inc (Res);
Start := Pos(Ch, Str);
If Ch = ' ' Then Begin
While Str[Start] = Ch Do
Delete (Str, Start, 1);
End Else
Delete (Str, Start, 1);
End;
strWordCount := Res;
End;
Function strWordGet (Num: Byte; Str: String; Ch: Char) : String;
Var
Count : Byte;
Temp : String;
Start : Byte;
Begin
strWordGet := '';
Count := 1;
Temp := Str;
If Pos(Ch,Str)<=0 Then Begin
strWordGet:=str;
Exit;
End;
If Ch = ' ' Then
While Temp[1] = Ch Do
Delete (Temp, 1, 1);
While Count < Num Do Begin
Start := Pos(Ch, Temp);
If Start = 0 Then Exit;
If Ch = ' ' Then Begin
While Temp[Start] = Ch Do
Inc (Start);
Dec(Start);
End;
Delete (Temp, 1, Start);
Inc (Count);
End;
If Pos(Ch, Temp) > 0 Then
strWordGet := Copy(Temp, 1, Pos(Ch, Temp) - 1)
Else
strWordGet := Temp;
End;
procedure dansi(fn:string;d:integer);
const
AnsiColors: Array[0..7] of Integer = (0, 4, 2, 6, 1, 5, 3, 7);
var
done:boolean;
key:char;
f:file;
b:char;
c:char;
cnt:byte;
savex:byte;
savey:byte;
lastch:char;
procedure ansicoloring(s:string);
var
i:byte;
cl:byte;
w:byte;
Colour:byte;
begin
for i:= 1 to cnt do begin
w:=str2int(strwordget(i,s,';'));
case w of
0: TextAttr:=7;
1: begin
cl:=textattr mod 16;
if cl < 8 then cl:=cl+8;
textcolor(cl);
end;
7: TextAttr:= ((TextAttr and $70) shr 4) + ((TextAttr and $07)
shl 4);
8: TextAttr:= 0; { Video Off }
30..37: Begin
Colour := AnsiColors[w - 30];
if (TextAttr mod 16 > 7) then
Inc(Colour, 8);
TextColor(Colour);
End;
40..47: TextBackground(AnsiColors[w - 40]);
End;
end;
end;
procedure linesdown(s:string);
var
y:byte;
begin
try
y:=str2int(s);
except
y:=1;
end;
gotoxy(1,wherey+y);
end;
procedure linesup(s:string);
var
y:byte;
begin
try
y:=str2int(s);
except
y:=1;
end;
gotoxy(1,wherey-y);
end;
procedure cursorup(s:string);
var
y:byte;
begin
try
y:=str2int(s);
except
y:=1;
end;
gotoxy(wherex,wherey-y);
end;
procedure cursordown(s:string);
var
y:byte;
begin
try
y:=str2int(s);
except
y:=1;
end;
gotoxy(wherex,wherey+y);
end;
procedure cursorleft(s:string);
var
x:byte;
begin
try
x:=str2int(s);
except
x:=1;
end;
gotoxy(wherex-x,wherey);
end;
procedure cursorright(s:string);
var
x:byte;
begin
try
x:=str2int(s);
except
x:=1;
end;
gotoxy(wherex+x,wherey);
end;
procedure gotocol(s:string);
var
x:byte;
begin
try
x:=str2int(s);
except
x:=wherex;
end;
gotoxy(x,wherey);
end;
procedure cursormove(s:string);
Begin
gotoxy(str2int(strwordget(2,s,';')),str2int(strwordget(1,s,';')));
End;
procedure insertspaces(s:string);
var
j,a:byte;
begin
try
a:=str2int(strwordget(1,s,';'));
except
a:=1;
end;
for j:=1 to a do write(' ');
end;
procedure clearline(s:string);
var j,a:byte;
begin
try
a:=str2int(strwordget(1,s,';'));
except
a:=0;
end;
case a of
0: for j:=wherex to 80 do write(' ');
1: for j:=1 to wherex do write(' ');
2: begin ClrEOL;Gotoxy(1,wherey);End;
end;
end;
procedure erasechars(s:string);
var
j,a:byte;
begin
try
a:=str2int(strwordget(1,s,';'));
except
a:=1;
end;
for j:=1 to a do write(' ');
end;
procedure repeatlastchar(s:string);
var
j,a:byte;
begin
try
a:=str2int(strwordget(1,s,';'));
except
a:=1;
end;
for j:=1 to a do write(lastch);
end;
procedure gotoline(s:string);
var
j,a:byte;
begin
try
a:=str2int(strwordget(1,s,';'));
except
a:=1;
end;
gotoxy(wherex,a);
end;
procedure doesc;
var
buf:string[255];
j:byte;
begin
buf:='';
blockread(f,b,1);
while length(buf)<255 do begin
blockread(f,b,1);
buf:=buf+b;
if b in
['m','J','H','f','A','B','C','D','u','s','K','@','F','E','G','X','b','d']
then break;
end;
if length(buf)<1 then exit;
cnt:=strwordcount(buf,';');
c:=buf[length(buf)];
//writeln('C:> '+buf +'C: '+c);
delete(buf,length(buf),1);
//writeln(buf+'=='+int2str(cnt));
case c of
'd': gotoline(buf);
'b': repeatlastchar(buf);
'X': erasechars(buf);
'm': ansicoloring(buf);
'K': clearline(buf);
'A': cursorup(buf);
'B': cursordown(buf);
'C': cursorright(buf);
'D': cursorleft(buf);
'E': linesdown(buf);
'F': linesup(buf);
'G': gotocol(buf);
'f','H': cursormove(buf);
's': begin
savex:=wherex;
savey:=wherey;
end;
'u' : gotoxy(savex,savey);
'@' : insertspaces(buf);
'J' : begin
if str2int(strwordget(1,buf,';')) = 2 then ClrScr;
End;
end;
end;
begin
savex:=1;
savey:=1;
assign(f,fn);
reset(f,1);
while (not eof(f)) and (done=false) do begin
blockread(f,b,1);
if keypressed then begin
key:=readkey;
Case key of
'+' : d := d + 3;
'-' : begin
d := d - 3;
if d<0 then d:=0;
end;
'*' : d := 20;
'/' : d := 5;
#27 : Done:=true;
End;
end;
case b of
#27: doesc;
#13: delay(d);
else
write(b);
lastch:=b;
end;
end;
close(f);
end;
begin
dansi(paramstr(1),10);
end.
+ --- -- - . - --- --- --- - . - -- --- '
_____ _ _ ____ _ _
| _ |___ ___| |_| |_ ___ ___ | \ ___ ___|_|_| | 8888
| | | . | _| | -_| _| | | | _| . | | . | 8 888888 8
|__|__|_|_|___|_| |_|_|___|_| |____/|_| |___|_|___| 8888888888
8888888888
DoNt Be aNoTHeR DrOiD fOR tHe SySteM 88 8888 88
8888888888
/: HaM RaDiO /: ANSi ARt! /: MySTiC MoDS /: DooRS '88||||88'
/: NeWS /: WeATheR /: FiLEs /: SPooKNet ''8888"'
/: GaMeS /: TeXtFiLeS /: PrEPardNeSS /: FsxNet 88
/: TuTors /: bOOkS/PdFs /: SuRVaViLiSM /: ArakNet 8 8 88888888888
888 8888][][][888
TeLNeT : andr01d.zapto.org:9999 [UTC 11:00 - 20:00] 8 888888##88888
SySoP : xqtr eMAiL: xqtr@gmx.com 8 8888.####.888
DoNaTe : https://paypal.me/xqtr 8 8888##88##888