Embarcadero has broken owner drawing of TStringGrid several times. To get the text positioned correctly you need version-specific X and Y offsets. Here is the case statement GExperts uses (from GX_StringGridDrawFix.pas):
procedure DetermineTextOffsets(_Focused: Boolean; out _XOffset, _YOffset: Integer);
begin
case GetBorlandIdeVersion of
ideD600..ideRSXE1U1: begin
// Versions before XE2 worked fine with an offset of 2 for both, x and y
_XOffset := 2;
_YOffset := 2;
end;
ideRSXE2..ideRS103U3: begin
// from XE2 onwards we need a different y offset
_XOffset := 2;
_YOffset := 0;
end;
ideRS104: begin
// fix for original bug in Delphi 10.4
_XOffset := 0;
_YOffset := 0;
end;
ideRS104P2: begin
// Embarcadero managed to bungle the StringGrid redraw fix in RS 10.4 patch 2.
// Now we have to check whether the grid is focused and use a different x offset in that case.
if _Focused then begin
_XOffset := 6;
_YOffset := 2;
end else begin
_XOffset := 2;
_YOffset := 2;
end;
end;
ideRS104U1, ideRS104U2: begin
// in RS 10.4 Update 1 they apparently fixed the fix (or at least made it consistently wrong again)
_XOffset := 6;
_YOffset := 2;
end;
ideRS11..ideRS12U1: begin
_XOffset := 6;
_YOffset := 2;
end;
else
// we optimistically assume that they won't break it again in future versions
_XOffset := 6;
_YOffset := 2;
end;
end;
These offsets are then applied in the drawing procedure:
cnv.TextRect(_Rect, _Rect.Left + XOffset, _Rect.Top + YOffset, _Text);
The full unit is in the GExperts repository as Source/Utils/GX_StringGridDrawFix.pas. It is also possible that this misbehavior is restricted to projects using runtime packages, such as IDE plugins. I have not extensively tested it outside of that context. And I have not tested the C#Builder, C++Builder, or Kylix variants at all, but the code assumes they behave like pre-XE2 Delphi (X=2, Y=2). If anyone still using those finds otherwise, let me know.