Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cursor width changes randomly because window.zoomLevel interacts with system DPI settings. #28542

Closed
fj128 opened this issue Jun 12, 2017 · 7 comments
Assignees
Labels
bug Issue identified by VS Code Team member as probable bug upstream Issue identified as 'upstream' component related (exists outside of VS Code) verified Verification succeeded zoom VS Code window zoom issues
Milestone

Comments

@fj128
Copy link

fj128 commented Jun 12, 2017

  • VSCode Version: 1.13.0
  • OS Version: Windows 7, 10.

Steps to Reproduce:

  1. Go to "Control Panel\Appearance and Personalization\Display", set text size to "Medium - 125%"
  2. Set "window.zoomLevel": 0, move the cursor around, observe it occasionally being twice as wide:

vscode cursor width bug

This is more or less a duplicate of #22904, except the workaround there was to not use window.zoomLevel at all, while in my case I'm not using it, it's Chromium deciding to respect the OS setting in a way that breaks vscode rendering if I understand it correctly.

Anyway, I want to add an actual work-around, two even.

  1. Set window.zoomLevel to -1.25 -1.2239 (edit: see below comments, that was close enough though) , this will negate the OS setting.

    The actual formula seems to be -(OS_zoom - 100) / 20: each negative point in zoomLevel decreases text size by 20%, in a sense of dividing text size by 1 + 0.2 * zoomLevel. So if we need to compensate for a 25% increase, we need -1.25 points, and for 50% it's -2.5 points. actually it's 1.2 raised to the power of zoomLevel, positive or negative.

  2. Or if you'd prefer a larger UI instead, you can set zoomLevel to 1, this gives you 1.25 * 1.2 = 1.5 combined zoom factor, which apparently doesn't produce the same artifacts.

@ramya-rao-a ramya-rao-a added the zoom VS Code window zoom issues label Jun 12, 2017
@alexdima
Copy link
Member

In all cases the primary cursor is "painted" as a <div> of width 2px. Chromium ultimately decides how to "zoom in" to respect the OS settings (possibly by multiplying everything with 1.25, and then proceeding to snap to real screen pixels).

AFAIK there is no possible code change we can do on our side to workaround this snapping / rounding-error quirks. I am thinking this is reproducible using Chrome at https://jsfiddle.net/b4hkeLew/ It is just a matter of playing with the left offset position until the snapping / rounding-error happens.

@alexdima alexdima added the upstream Issue identified as 'upstream' component related (exists outside of VS Code) label Jun 13, 2017
@alexdima alexdima added this to the Backlog milestone Jun 13, 2017
@fj128
Copy link
Author

fj128 commented Jun 13, 2017

@alexandrudima Interesting, that explains why there's no artifacts at 150% zoom (because it yields 3px exactly). And why there still are artifacts at 150% with "editor.cursorStyle": "line-thin".

Look at this though: https://jsfiddle.net/655jcnmf/2/ when I specify 1.6px width (2 / 1.25), there's no width artifacts!

So, if you could somehow determine OS DPI and also keep track of your own zoomLevel, you could pre-round line width based on that and use it everywhere.

Or at least it would be nice if you exposed it as some sort of an advanced option. Because vscode is otherwise Literally Unplayable™ for me without tweaking anyway, and I'd prefer a tweaking option that reliably works with any zoomLevel (especially 0).

@alexdima
Copy link
Member

alexdima commented Jun 13, 2017

I have been down this road only last week. The problem is that I could not come up with any formula that makes sense and uses the values I have at my disposal: window.devicePixelRatio, document.createElement('canvas').getContext('2d').webkitBackingStorePixelRatio which when divided should give the pixelRatio; zoomLevel or zoomFactor.

Perhaps I am missing something obvious but here is what I observed on a mac book pro's built-in monitor:

See https://github.com/Microsoft/vscode/blob/91a0e04417cabaeda04bc20891d58ae338179f66/src/vs/editor/browser/controller/textAreaHandler.ts#L423

// Observed values on a retina screen (by taking screenshots):
// |--------|-----------|------------|------------|-----------|
// | css px | zoomLevel | zoomFactor | pixelRatio | screen px |
// |--------|-----------|------------|------------|-----------|
// |   18   |    -8     |   0.2325   |   0.5000   |      8    |
// |   18   |    -7     |   0.2790   |   0.5581   |     10    |
// |   18   |    -6     |   0.3348   |   0.6697   |     12    |
// |   18   |    -5     |   0.4018   |   0.8037   |     14    |
// |   18   |    -4     |   0.4822   |   0.9645   |     18    |
// |   18   |    -3     |   0.5787   |   1.1574   |     20    |
// |   18   |    -2     |   0.6944   |   1.3888   |     26    |
// |   18   |    -1     |   0.8333   |   1.6666   |     30    |
// |   18   |     0     |   1.0000   |   2.0000   |     36    |
// |--------|-----------|------------|------------|-----------|

@fj128
Copy link
Author

fj128 commented Jun 13, 2017

I found this: https://chromium.googlesource.com/chromium/src.git/+/refs/heads/lkcr/content/common/page_zoom.cc

And the formula there matches your observed zoomFactor perfectly and pixel values pretty close (if we assume a 2x factor coming from the device DPI or something):

edit: and pixelRatio = zoomFactor * 2, except it's limited to a minimum of 0.5 (or 0.25 as per the linked code, with the 2x factor coming from somewhere else)

for zoomLevel in range(-8, 1):
    zoomFactor = 1.2 ** zoomLevel
    print(f'{zoomLevel:>2} | {zoomFactor:0.4f} |  {2 * zoomFactor:0.4f} | {36 * zoomFactor:>5.2f}')
====================
-8 | 0.2326 | 0.4651 |  8.37
-7 | 0.2791 | 0.5582 | 10.05
-6 | 0.3349 | 0.6698 | 12.06
-5 | 0.4019 | 0.8038 | 14.47
-4 | 0.4823 | 0.9645 | 17.36
-3 | 0.5787 | 1.1574 | 20.83
-2 | 0.6944 | 1.3889 | 25.00
-1 | 0.8333 | 1.6667 | 30.00
 0 | 1.0000 | 2.0000 | 36.00

(also, this means that I was wrong about the way zoom works, and it worked for me both times accidentally, because 1.25 * (1.2 ** -1.25) = 0.995 while the correct zoomLevel to counteract 125% zoom is -1.2239)

edit2: also I checked, at least on JSFiddle window.devicePixelRatio shows 1.25 to me (at normal zoom), so it already takes into account system DPI, so I guess you can just go and use it? And ignore the maybe applied but maybe not cutoff at zoomFactor: -8.

@alexdima
Copy link
Member

alexdima commented Jun 14, 2017

Thank you for looking and helping out with this. To sum up:

  • zoomFactor = Math.pow(1.2, zoomLevel)
  • window.devicePixelRatio = Math.max(0.5, Math.min(5, zoomFactor * systemRatio)) (for the screen I measured with systemRatio appears to be 2)
  • screenPx = window.devicePixelRatio * cssPx with the extra constraint that rounding depends on how the dom node "aligns" to the screen pixels. i.e. it depends on what is the left and the width of the dom node. I think that's why 17.36 is rounded to 18 in my measurements.

So we could programmatically set the cursor width with the following formula:

function dpiAwareSize(cssPx) {
  var screenPx = window.devicePixelRatio * cssPx;
  return Math.max(1, Math.round(screenPx)) / window.devicePixelRatio;
}

The idea is that we don't want to have the cursor be always 2 screen pixels. If the user zooms in at 200%, the cursor should be 4 screen pixels, or at least I think that's what we'd want. In your case it would be Math.round(1.25 * 2) / 1.25 = 2.5. So we would set it on our side to be 2.5px such that it will be of a constant width of 3 screen pixels.

@fj128
Copy link
Author

fj128 commented Jun 14, 2017

Sounds good. By the way, this function should also be used for drawing rulers (see #9634) and the newly enabled by default Indent Guides, they too are drawn with varying screen widths for me.

@alexdima
Copy link
Member

The function from above appears to work fine -- Windows at 125% zoom:

image

@alexdima alexdima modified the milestones: June 2017, Backlog Jun 14, 2017
@alexdima alexdima added the bug Issue identified by VS Code Team member as probable bug label Jun 14, 2017
@roblourens roblourens added the verified Verification succeeded label Jun 29, 2017
@vscodebot vscodebot bot locked and limited conversation to collaborators Nov 18, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Issue identified by VS Code Team member as probable bug upstream Issue identified as 'upstream' component related (exists outside of VS Code) verified Verification succeeded zoom VS Code window zoom issues
Projects
None yet
Development

No branches or pull requests

5 participants