Opened 9 months ago

Last modified 4 weeks ago

#18564 accepted defect

HTML renders incorrectly with PMv2 DPI Awareness

Reported by: pb101 Owned by: vadz
Priority: normal Milestone: 3.2.0
Component: wxHtml Version: dev-latest
Keywords: HiDPI wxMSW Cc: pbfordev@…
Blocked By: Blocking: #18474
Patch: no

Description

I have discovered this accidentally by launching sample widget.exe instead of widgets.exe but I assume the issue is not within samples.

In certain situations HTML is rendered quite badly, as if the spaces between words did not exist/were negative, see the top window in the attached screenshot.

How to reproduce:

  1. Have monitors with different scalings (my primary is scaled at 125% / 120 DPI, the secondary is at 100%)
  2. Build wxWidgets and samples with per-monitor DPI awareness.
  3. Launch any wxHTML sample (such as widget.exe) so that its window appears first on the unscaled (96 DPI) monitor.

The issue seems to be also affected by the font used, the default font (Arial?) is quite bad while the monospaced font used in the htmlhelp.exe or htmltest.exe seems a lot better.

The HTML renders correctly when created on the scaled monitor. However it seems the font size does not scale properly when moving the window between monitors.

I could not reproduce the issue when the samples were built as DPI-unaware or System DPI aware.

Attachments (2)

wxhtml-issue.png download (16.8 KB) - added by pb101 9 months ago.
The top window was created on the unscaled screen, the bottom one the scaled screen
wxhtml.png download (47.9 KB) - added by vadz 9 months ago.
html/test sample

Download all attachments as: .zip

Change History (12)

Changed 9 months ago by pb101

The top window was created on the unscaled screen, the bottom one the scaled screen

comment:1 Changed 9 months ago by vadz

  • Blocking 18474 added

Changed 9 months ago by vadz

html/test sample

comment:2 Changed 9 months ago by vadz

  • Status changed from new to confirmed

I see this as well with the html/test sample. It's weird because everything looks fine if the sample starts on the display using normal DPI, even if it's moved to a high DPI display later, but OTOH if the sample starts on the high DPI display it's still broken even after moving it to the standard one.

This screenshot shows 2 instances of the sample on the normal DPI display: the left one was started on it while the right one was moved to it after initially starting on the other one.

html/test sample

comment:3 Changed 9 months ago by vadz

  • Owner set to vadz
  • Status changed from confirmed to accepted

There are at least 2 problems here:

  1. We don't use the correct scaling for wxMemoryDC used in wxHtmlWindow::OnPaint().
  2. We never update the extents on receiving wxDPIChangedEvent.

Both don't seem to be that difficult to fix, so I'll try to do it soon.

comment:4 Changed 9 months ago by pb101

  • Cc pbfordev@… added

What are you describing is like the exact opposite of what I am experiencing.

Using HTML test sample (built with CMake as PMv2 aware)

Starting it on the standard DPI screen
Me: The text is squashed (no spaces between words).
You: Looks OK.

Starting it on the high DPI screen
Me: Looks OK.
You: the spaces between words are too large.

*Maybe* the difference is caused by your higher scaling (200% vs 125%)?

comment:5 Changed 5 weeks ago by vadz

Returning to this one, (2) from comment:3 does actually seem difficult to fix because there is no way to force a cell to recalculate its extents, so even calling wxHtmlWindow::CreateLayout() on DPI change doesn't help at all.

One way of fixing this is to keep the original HTML source and recreate everything (i.e. call DoSetPage()), but this means that we'll be wasting a lot of memory for all this HTML in the common case when the DPI never changes. OTOH, do we really care about it? wxHTML probably can't efficiently display really huge HTML pages anyhow...

Otherwise we need to add a new virtual method UpdateDimensions() to wxHtmlCell and override it at least in wxHtmlWordCell and call it when the DPI changes. To be honest, this doesn't look like the end of the world neither, but I'm just not sure if it's worth doing this.

comment:6 Changed 5 weeks ago by vadz

  • Milestone set to 3.2.0

The idea with UpdateDimensions() is even less simple than I thought because there is also "pixel scale" used in a few places, which is currently set during parsing/cell construction, so this would need to be refactored to allow updating it later.

OTOH the idea with recreating everything has its own problems, e.g. losing the selection.

I'm still not sure what would be best to do here, if Vaclav is reading this, I'd be grateful for any advice. For now I'm going to postpone it for 3.2.0 as I'm unlikely to be able to finish this in time for 3.1.4.

comment:7 Changed 4 weeks ago by dwhittaker

  • Cc dwhittaker@… added

Whether you get the squashed or spaced text depends on whether the high resolution (scaled) or low resolution (non-scaled) display is set as the primary (main) display. If the sample starts on the primary display the text looks ok. If it starts on a non-primary display the text will be squashed if the high res display is the primary display, and spaced out if low res display is the primary display.

It looks like the wxMemoryDC is created using the scaling of the primary display.

You can get around the initial display problem by removing the double buffering in the htmlwin.cpp OnPaint() function, however this doesn't help with the issues when the window is moved to a display with a different scaling.

Possibly wxMemoryDC might need a constructor that takes a window as a parameter in order to create a DC with the same scaling as the Window.

comment:8 Changed 4 weeks ago by MaartenB

You can also fix this by adding dc->GetImpl()->SetWindow(this); after dc is assigned to the wxMemoryDC. Then it uses the DPI of the window for the font size.
But this does not fix the squashed/spaced problem. I'm too unfamiliar with wxHTML to know whats going on here.

comment:9 Changed 4 weeks ago by dwhittaker

  • Cc dwhittaker@… removed

More info on this. If you apply the dc->GetImpl()->SetWindow(this) fix to the memory dc and then Bind a wxEVT_DPI_CHANGED handler that calls SetStandardFonts() on the wxHtmlWindow the initial test HTML window displays correctly on either display and also seems to move between them okay - at least the text components.

comment:10 Changed 4 weeks ago by vadz

FWIW you definitely need at least this:

  • src/html/htmlwin.cpp

    commit 0199ce419686fa7a8f075d1d10836be82d4a99f2
    Author: Vadim Zeitlin <vadim@wxwidgets.org>
    Date:   2019-11-14 01:49:06 +0100
    
        Fix wxHtmlWindow rendering in high DPI under MSW
        
        Scale the back buffer bitmap to ensure that coordinates used when
        measuring the text (using the window DC) and rendering it (using this
        memory DC) are consistent.
        
        See #18564.
    
    diff --git a/src/html/htmlwin.cpp b/src/html/htmlwin.cpp
    index 8fdbe56cf6..d2ef7e5a47 100644
    a b void wxHtmlWindow::OnPaint(wxPaintEvent& WXUNUSED(event)) 
    11071107    else // window is not double buffered by the system, do it ourselves
    11081108    {
    11091109        if ( !m_backBuffer.IsOk() )
    1110             m_backBuffer.Create(sz.x, sz.y);
     1110        {
     1111            m_backBuffer.CreateScaled(sz.x, sz.y, wxBITMAP_SCREEN_DEPTH,
     1112                                      GetContentScaleFactor());
     1113        }
     1114
    11111115        dcm.SelectObject(m_backBuffer);
    11121116        dc = &dcm;
    11131117    }

but it's not enough, as there are problems even after disabling double-buffering completely.

As I wrote in comment:6, this is due (again, at least, maybe there are other problems too...) to wxHtmlCell objects storing the values in pixels computed at the moment of construction, i.e. when the HTML input is parsed. This includes cell width/height in wxHtmlWordCell, table width in the table cell and also image size in the image cell. All these values would need to be recomputed on DPI change but, again, this is not really simple to do with the current code structure.

Which is why storing HTML and reparsing seems appealing as a band-aid fix, even if it's definitely not the right thing to do.

Last edited 4 weeks ago by vadz (previous) (diff)
Note: See TracTickets for help on using tickets.