pyscripter / SynEdit

SynEdit is a syntax highlighting edit control, not based on the Windows common controls.
26 stars 11 forks source link

On Windows7 - OS error when painting images on gutter. #70

Closed Vizit0r closed 1 year ago

Vizit0r commented 1 year ago

##bugreport

Check all issues, found nothing. Very strange, noone using Win7?

Bug easily reproducing - compiled demo SimpleIDEDemo (which start to draws images on gutter immediately from form showing), launch it on Win7, and voila - raise error "System Error. Code: -2003292288."

After investigation, found that reason in ImageListDraw (SynDWrite.pas line 1629). I cant understant the reason, have zero experience with 2D code practical usage.

Checked on 4 different Win7 computers, Pro and home versions - no matter.

Win10 and Win 11 - no any problems.

##proposition

Today i made another ImageListDraw, which works fine on all computers (okey, not sure about all, but all previously faulty Win7 - now paints fine).

//this version works fine in Windows7, no errors, painting transparent.
procedure ImageListDraw(RT: ID2D1RenderTarget; IL: TCustomImageList;
  X, Y, Index: Integer);
var
  Bitmap: ID2D1Bitmap;
  BitmapInfo: TBitmapInfo;
  buf: array of Byte;
  BitmapProperties: TD2D1BitmapProperties;
  Hbmp: HBitmap;
  btBitmap: TBitmap;
  Icon: HIcon;
  IconInfo: TIconInfo;
  R: TRectF;
begin
  btBitmap := tbitmap.create;

//The PNG's in ImageList MUST be 32b with Transparency (Alpha) layer!
//otherwise nothing will be painted.

  try
    Icon := ImageList_GetIcon(IL.Handle, Index, ILD_NORMAL);
    GetIconInfo(Icon, IconInfo );

    btBitmap.Handle := IconInfo.hbmColor;
    btBitmap.PixelFormat := pf32bit;
    btBitmap.AlphaFormat := afPremultiplied;

    FillChar(BitmapInfo, SizeOf(BitmapInfo), 0);

    BitmapInfo.bmiHeader.biSize := Sizeof(BitmapInfo.bmiHeader);
    BitmapInfo.bmiHeader.biHeight := -btBitmap.Height;
    BitmapInfo.bmiHeader.biWidth := btBitmap.Width;
    BitmapInfo.bmiHeader.biPlanes := 1;
    BitmapInfo.bmiHeader.biBitCount := 32;

    SetLength(buf, btBitmap.Height * btBitmap.Width * 4);

    Hbmp := btBitmap.Handle;
    GetDIBits(btBitmap.Canvas.Handle, Hbmp, 0, btBitmap.Height, @buf[0], BitmapInfo, DIB_RGB_COLORS);

    BitmapProperties.dpiX := 0;
    BitmapProperties.dpiY := 0;
    BitmapProperties.pixelFormat.format := DXGI_FORMAT_B8G8R8A8_UNORM;
    BitmapProperties.pixelFormat.alphaMode := D2D1_ALPHA_MODE_PREMULTIPLIED;

    CheckOSError(RT.CreateBitmap(D2D1SizeU(Cardinal(btBitmap.Width), Cardinal(btBitmap.Height)),
                                 @buf[0], 4*btBitmap.Width, BitmapProperties, Bitmap));

    R := Rect(X, Y, X + IL.Width, Y + IL.Height);
    RT.DrawBitmap(Bitmap, @R, 1);
  finally
    DestroyIcon(Icon);
    btBitmap.Free;
  end;
end;

not sure about speed, didnt make any tests\comparisions. If my version is much slower - maybe need to add OS version check, and use it only on Win 7.

P.S. -2003292288 means "Bitmap pixel format is not supported"

Vizit0r commented 1 year ago

similar error (System Error. Code: -2003283966.) coming from TSynDWrite.GDIInterop.CreateFontFromLOGFONT, also on Windows 7. file SynDWrite.pas: "TSynTextFormat.Create" and "IsFontMonospacedAndValid"

Already received 3 errors from different systems, all Win7 SP1, lang of last is Chinese, 2 previous - i forgot to look :) Will make some additional wrappers...

P.S. -2003283966 means here "Indicates the specified font does not exist.". Need additional checks in code, nothing critical. F.E. in method "DefaultFontName" need to check for font existance (if Screen.Fonts.IndexOf(AFont.Name) = -1 then)

I make draft solution like

function IsFontExists(FontName : String) : Boolean;
begin
  Result := Screen.Fonts.IndexOf(FontName) >= 0;
end;

function DefaultFontName: string;
const DefaultFontNames : array of String = ['Consolas', 'MS Sans Serif', 'Courier New', 'Segoe UI', 'Arial'];
begin
  if CheckWin32Version(6) then
  begin
    for var FontName in DefaultFontNames do
      if IsFontExists(FontName) then
        Exit(FontName);
    MessageBox(0, 'Cannot find default fonts! Report to developer NOW!', 'Error', MB_ICONERROR);
  end
  else
    Result := 'Courier New';
end;

P.P.S. Error codes: https://learn.microsoft.com/en-us/windows/win32/com/com-error-codes-10

pyscripter commented 1 year ago

Today i made another ImageListDraw, which works fine on all computers (okey, not sure about all, but all previously faulty Win7 - now paints fine).

Thanks! This error has been reported by a couple of Win 7 PyScripter users. I will have a close look at your solution.

pyscripter commented 1 year ago

2003283966 means here "Indicates the specified font does not exist."

Strange. Wikipedia states that Consolas has been included in Windows since Windows Vista. (6.0). So it should be available in Windows 7,

Does the following in SynEditMiscProcs work?

function DefaultFontName: string;
begin
  if Screen.Fonts.IndexOf('Consolas') >= 0 then
    Result := 'Consolas'
  else
    Result := 'Courier New';
end;

Note: Courier New should be available in all supported versions (was introduced in Windows 3.1).

Vizit0r commented 1 year ago

2003283966 means here "Indicates the specified font does not exist."

Strange. Wikipedia states that Consolas has been included in Windows since Windows Vista. (6.0). So it should be available in Windows 7,

Does the following in SynEditMiscProcs work?

function DefaultFontName: string;
begin
  if Screen.Fonts.IndexOf('Consolas') >= 0 then
    Result := 'Consolas'
  else
    Result := 'Courier New';
end;

Note: Courier New should be available in all supported versions (was introduced in Windows 3.1).

I know, it must be included, but we have what we have.

I cant check directly, those errror report sent from some chinese PCs, i have no communication with them. What i do atm in code to prevent this situation (cant say, if it okey - will release new version with update tomorrow or day after tomorrow):

procedure TSynGutter.OnFontChange(Sender: TObject);
const DefaultFontNames : array of String = ['Consolas', 'Courier New', 'Courier', 'Lucida Console'];
var FontIsFontMonospacedAndValid : Boolean;
begin
  FFont.OnChange := nil;  // avoid recursion
  if Assigned(FOwner) then
    FFont.Quality := TCustomSynEdit(FOWner).FontQuality;
  // revert to default font if not monospaced or invalid
  FontIsFontMonospacedAndValid := False;

  try
    if IsFontMonospacedAndValid(FFont) then
      FontIsFontMonospacedAndValid := True;
  except
  end;

  if not FontIsFontMonospacedAndValid then
    for var FontName in DefaultFontNames do
      try
        Font.Name := FontName;
        if IsFontMonospacedAndValid(FFont) then
        begin
          FontIsFontMonospacedAndValid := True;
          Break;
        end
        else
          Continue;
      except
        Continue;
      end;

  Font.OnChange := OnFontChange;
  if FontIsFontMonospacedAndValid then
    try
      FTextFormat.Create(FFont, 1, 0, 0);
      FCharWidth := FTextFormat.CharWidth;
    except
      FCharWidth := 0;
    end;
  Changed;
end;

+

function IsFontExists(FontName : String) : Boolean;
begin
  Result := Screen.Fonts.IndexOf(FontName) >= 0;
end;

function DefaultFontName: string;
const DefaultFontNames : array of String = ['Consolas', 'Courier New', 'Courier', 'Lucida Console'];
begin
  if CheckWin32Version(6) then
  begin
    for var FontName in DefaultFontNames do
      if IsFontExists(FontName) then
        Exit(FontName);
    MessageBox(0, 'Cannot find default fonts! Report to developer NOW!', 'Error', MB_ICONERROR);
  end
  else
    Result := 'Courier New';
end;

After release will wait for new error reports related with subj

pyscripter commented 1 year ago

@Vizit0r

Could you please try the following with Windows 7?

procedure ImageListDraw(RT: ID2D1RenderTarget; IL: TCustomImageList;
  X, Y, Index: Integer);
var
  Bitmap: ID2D1Bitmap;
  BitmapInfo: TBitmapInfo;
  Buf: array of Byte;
  BitmapProperties: TD2D1BitmapProperties;
  Icon: HIcon;
  IconInfo: TIconInfo;
  R: TRectF;
  DC: HDC;
begin
  Icon := ImageList_GetIcon(IL.Handle, Index, ILD_NORMAL);
  try
    if not GetIconInfo(Icon, IconInfo) then
      Exit;

    DC := CreateCompatibleDC(0);
    try
      FillChar(BitmapInfo, SizeOf(BitmapInfo), 0);
      BitmapInfo.bmiHeader.biSize := Sizeof(BitmapInfo.bmiHeader);
      // call with nil to get the Bitmap Info filled.
      if (GetDIBits(DC, IconInfo.hbmColor, 0, IL.Height, nil, BitmapInfo, DIB_RGB_COLORS) = 0) or
        (BitmapInfo.bmiHeader.biBitCount <> 32)
      then
        Exit;  // Exit if it fails or if biBitCount <> 32
      BitmapInfo.bmiHeader.biCompression := BI_RGB; // set to uncompressed
      SetLength(Buf, IL.Height * IL.Width * 4);
      if GetDIBits(DC, IconInfo.hbmColor, 0, IL.Height, @Buf[0], BitmapInfo, DIB_RGB_COLORS) = 0 then
        Exit;
    finally
      DeleteDC(DC);
    end;
  finally
    DestroyIcon(Icon);
  end;

  BitmapProperties := D2D1BitmapProperties(D2D1PixelFormat(
    DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), 0, 0);

  CheckOSError(RT.CreateBitmap(D2D1SizeU(IL.Width, IL.Height), @Buf[0],
                4 * IL.Width, BitmapProperties, Bitmap));

  R := Rect(X, Y, X + IL.Width, Y + IL.Height);
  RT.DrawBitmap(Bitmap, @R, 1);
end;

If it works I will commit the change.

Vizit0r commented 1 year ago

If it works I will commit the change.

tested by 3 peoples with Win 7 - works fine, no remarks.

pyscripter commented 1 year ago

@Vizit0r I committed the ImageListDraw replacement. I have also changed the drawing of SynGlyph and SynInternalImage. Regarding fonts, I am not sure whether the font names are localized. Is "Courier" the same in chinese Windows for instance? See https://learn.microsoft.com/en-us/windows/apps/design/globalizing/loc-international-fonts and https://stackoverflow.com/questions/14717877/are-font-names-on-windows-english-only for instance.

Also in Windows 7 the Platform Update for Windows 7 is probably needed.

By the way what kind of application are you using SynEdit for?

Vizit0r commented 1 year ago

@Vizit0r I committed the ImageListDraw replacement. I have also changed the drawing of SynGlyph and SynInternalImage. Regarding fonts, I am not sure whether the font names are localized. Is "Courier" the same in chinese Windows for instance?

Russian, Spanish,Ukrainian, Italian etc. Windows have no any localization of font name. Thats core system fonts, they cannot be localized in any case.

Anyway, today will try to contact with few chinese guys, to check this.

By the way what kind of application are you using SynEdit for?

Client for one old game with support of scripting, so editor using for writing\modify sripts. I will open most of the sources this year (maybe autumn, donknow yet).


BTW, some users are very angry because of strange error raised afer about 10 minutes of work with editor, and the biggest problem that application dying earliers than those reports sent to my server. One man told me that and drop me copy of those unsent report, you can fin d it attached. I ask community - and few peoples told me that they have similar problems.

Stealth_75697E7B_20230623232218.zip On English error message is "The Parameter is Incorrect". As you can see from report - Windows 10.

No any normal info, almost empty call stack, NOTHING. Have no idea, what to do, and where to look for error source. Start thinking about rolling back from SynEdit.


P.S. Chinese guy confirm, that last release with SynEdit has no problems with starting on his computer. So, in common for chinese windows same fonts etc.
But from my practice - chinese guys generate most of strange system errors :)

pyscripter commented 1 year ago

Are these the guys having problem with the fonts? Could you please also ask them

Vizit0r commented 1 year ago

error reports with fonts problems, which we talking about - coming from unknown peoples, i have no contact with any ot them.