I’m writing a cross-platform library for Delphi in Pascal for writing & reading from the terminal.
I use a combination of functions from the Winapi.Windows
unit and some Linux control characters to change the background and foreground colors of the text which is being echoed on the screen.
I’m experiencing a issue where the background color of the previous line is somehow overflowing onto the next line, but not overflowing on itself.
Image
Here is some code which outputs this exact result
for var I := 1 to 6 do
begin
var TextAttrib: integer;
var Buff: TConsoleScreenBufferInfo;
const Handle = TTextRec(Output).Handle;
// Red
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), Buff);
TextAttrib := Buff.wAttributes and $FF0F;
TextAttrib := TextAttrib or (Word(4) shl 4);
SetConsoleTextAttribute(Handle, TextAttrib);
System.WriteLn('Debug test');
SetConsoleTextAttribute(Handle, $0F);
// Green
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), Buff);
TextAttrib := Buff.wAttributes and $FF0F;
TextAttrib := TextAttrib or (Word(2) shl 4);
SetConsoleTextAttribute(Handle, TextAttrib);
System.WriteLn('Color filler test');
SetConsoleTextAttribute(Handle, $0F);
// Blue
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), Buff);
TextAttrib := Buff.wAttributes and $FF0F;
TextAttrib := TextAttrib or (Word(1) shl 4);
SetConsoleTextAttribute(Handle, TextAttrib);
System.WriteLn('Line overflow');
SetConsoleTextAttribute(Handle, $0F);
System.WriteLn('');
end;
On Windows, the issue only occurs after the 4th or so echo, but the Linux equivalent of this code always has this same issue. In any case, there is clearly an issue with my method.
So, what am I doing wrong?
8
When I try your code in a simple Delphi console application I get no coloured background at all.
When debugging I see that value for Handle
is 0. And that is different of what GetStdHandle(STD_OUTPUT_HANDLE)
returns.
So when your code is calling SetConsoleTextAttribute(Handle, TextAttrib);
it is actually trying to set text atributes to wrong handle.
In light of that I have modified your code so that it properly retrieves console handle right from the start
var Handle: THandle;
Handle := GetStdHandle(STD_OUTPUT_HANDLE);
for var I := 0 to 10 do
begin
var TextAttrib: integer;
var Buff: TConsoleScreenBufferInfo;
System.WriteLn('');
// Red
GetConsoleScreenBufferInfo(Handle, Buff);
TextAttrib := Buff.wAttributes and $FF0F;
TextAttrib := TextAttrib or (Word(4) shl 4);
SetConsoleTextAttribute(Handle, TextAttrib);
System.WriteLn('Debug test');
//SetConsoleTextAttribute(Handle, $0F);
// Green
GetConsoleScreenBufferInfo(Handle, Buff);
TextAttrib := Buff.wAttributes and $FF0F;
TextAttrib := TextAttrib or (Word(2) shl 4);
SetConsoleTextAttribute(Handle, TextAttrib);
System.WriteLn('Color filler test');
//SetConsoleTextAttribute(Handle, $0F);
// Blue
GetConsoleScreenBufferInfo(Handle, Buff);
TextAttrib := Buff.wAttributes and $FF0F;
TextAttrib := TextAttrib or (Word(1) shl 4);
SetConsoleTextAttribute(Handle, TextAttrib);
System.WriteLn('Line overflow');
//SetConsoleTextAttribute(Handle, $0F);
// Multicolored line
// Red text part
GetConsoleScreenBufferInfo(Handle, Buff);
TextAttrib := Buff.wAttributes and $FF0F;
TextAttrib := TextAttrib or (Word(4) shl 4);
SetConsoleTextAttribute(Handle, TextAttrib);
Write('Red text');
// Space without background
SetConsoleTextAttribute(Handle, $0F);
Write(' ');
// Green text part
GetConsoleScreenBufferInfo(Handle, Buff);
TextAttrib := Buff.wAttributes and $FF0F;
TextAttrib := TextAttrib or (Word(2) shl 4);
SetConsoleTextAttribute(Handle, TextAttrib);
Write('Green text');
// Space without background
SetConsoleTextAttribute(Handle, $0F);
Write(' ');
// Blue text part
GetConsoleScreenBufferInfo(Handle, Buff);
TextAttrib := Buff.wAttributes and $FF0F;
TextAttrib := TextAttrib or (Word(1) shl 4);
SetConsoleTextAttribute(Handle, TextAttrib);
Write('Blue text');
// Space without background
SetConsoleTextAttribute(Handle, $0F);
Write(' ');
// Red background colour for entire line
// We need to write string whose size matches the console line size
// which we can retrieve from TConsoleScreenBufferInfo by reading
// dwSize.X field
GetConsoleScreenBufferInfo(Handle, Buff);
TextAttrib := Buff.wAttributes and $FF0F;
TextAttrib := TextAttrib or (Word(4) shl 4);
SetConsoleTextAttribute(Handle, TextAttrib);
System.WriteLn('');
var S: String;
S := 'Red background for entire line';
SetLength(S, Buff.dwSize.X);
System.WriteLn(S);
System.WriteLn('');
end;
ReadLn;
Also I have added two more examples for of changing background colour.
First one displays of how you can get multiple different background colours in a single line. It makes use of Write
instead of WiteLn
in order to write individual parts of a single line. This is possible since SetConsoleTextAttribute
affects all text that is written afterwards.
Second one displays of how you can get coloured background for entire line. In order to do this you need to write string whose size matches the size of console line.
Do note that on Windows resizing console window causes text attributes of the last printable character in a line to be applied to the end of the line.
I’m not sure if this code will work the same on Linux as it does in Windows since I haven’t tested it out.
PS: If you want to split your text into multiple lines you can use Write
and add new line character into proper location within your string like this:
Write('First line'+sLineBreak+'Second line');
sLineBreak
is defined in system unit in a way so that proper platform specific new line character is used.
2