From beb7896234c10873424c00cce43ae51651dd88ce Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 2 Oct 2015 15:47:27 +0200 Subject: [PATCH] Enable subpixel font scaling For certain font sizes, enabling `textRendering: optimizeLegibility` caused a bunch of measurement-related issues. You can reproduce it by setting the following in your stylesheet: ``` atom-text-editor { font-size: 14px; text-rendering: optimizeLegibility; } ``` Although I wanted to defer subpixel font scaling to a later moment, it seems like Chrome needs to have it enabled in order to properly support the "legibility" path for text rendering. (I guess this is part of the reason why the Chromium team enabled it by default at some point in the past.) --- spec/lines-yardstick-spec.coffee | 36 +++---- spec/text-editor-component-spec.coffee | 126 +++++++++++++------------ src/browser/atom-window.coffee | 2 +- src/text-editor-presenter.coffee | 16 +++- 4 files changed, 99 insertions(+), 81 deletions(-) diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee index 2eea19da7..35a13c1fd 100644 --- a/spec/lines-yardstick-spec.coffee +++ b/spec/lines-yardstick-spec.coffee @@ -70,10 +70,10 @@ describe "LinesYardstick", -> expect(linesYardstick.pixelPositionForScreenPosition([0, 0])).toEqual({left: 0, top: 0}) expect(linesYardstick.pixelPositionForScreenPosition([0, 1])).toEqual({left: 7, top: 0}) - expect(linesYardstick.pixelPositionForScreenPosition([0, 5])).toEqual({left: 38, top: 0}) - expect(linesYardstick.pixelPositionForScreenPosition([1, 6])).toEqual({left: 42, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([1, 9])).toEqual({left: 72, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([2, Infinity])).toEqual({left: 280, top: 28}) + expect(linesYardstick.pixelPositionForScreenPosition([0, 5])).toEqual({left: 37.8046875, top: 0}) + expect(linesYardstick.pixelPositionForScreenPosition([1, 6])).toEqual({left: 43.20703125, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition([1, 9])).toEqual({left: 72.20703125, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition([2, Infinity])).toEqual({left: 288.046875, top: 28}) it "reuses already computed pixel positions unless it is invalidated", -> atom.styles.addStyleSheet """ @@ -83,9 +83,9 @@ describe "LinesYardstick", -> } """ - expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 20, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 60, top: 28}) - expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 100, top: 70}) + expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 19.203125, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 57.609375, top: 28}) + expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 95.609375, top: 70}) atom.styles.addStyleSheet """ * { @@ -93,15 +93,15 @@ describe "LinesYardstick", -> } """ - expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 20, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 60, top: 28}) - expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 100, top: 70}) + expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 19.203125, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 57.609375, top: 28}) + expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 95.609375, top: 70}) linesYardstick.invalidateCache() - expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 24, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 72, top: 28}) - expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 120, top: 70}) + expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 24.00390625, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 72.01171875, top: 28}) + expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 120.01171875, top: 70}) it "correctly handles RTL characters", -> atom.styles.addStyleSheet """ @@ -114,8 +114,8 @@ describe "LinesYardstick", -> editor.setText("السلام عليكم") expect(linesYardstick.pixelPositionForScreenPosition([0, 0]).left).toBe 0 expect(linesYardstick.pixelPositionForScreenPosition([0, 1]).left).toBe 8 - expect(linesYardstick.pixelPositionForScreenPosition([0, 2]).left).toBe 17 - expect(linesYardstick.pixelPositionForScreenPosition([0, 5]).left).toBe 34 + expect(linesYardstick.pixelPositionForScreenPosition([0, 2]).left).toBe 16 + expect(linesYardstick.pixelPositionForScreenPosition([0, 5]).left).toBe 33 expect(linesYardstick.pixelPositionForScreenPosition([0, 7]).left).toBe 50 expect(linesYardstick.pixelPositionForScreenPosition([0, 9]).left).toBe 67 expect(linesYardstick.pixelPositionForScreenPosition([0, 11]).left).toBe 84 @@ -133,13 +133,13 @@ describe "LinesYardstick", -> """ expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 12.5})).toEqual([0, 2]) - expect(linesYardstick.screenPositionForPixelPosition({top: 14, left: 17.8})).toEqual([1, 3]) + expect(linesYardstick.screenPositionForPixelPosition({top: 14, left: 18.8})).toEqual([1, 3]) expect(linesYardstick.screenPositionForPixelPosition({top: 28, left: 100})).toEqual([2, 14]) expect(linesYardstick.screenPositionForPixelPosition({top: 32, left: 24.3})).toEqual([2, 3]) expect(linesYardstick.screenPositionForPixelPosition({top: 46, left: 66.5})).toEqual([3, 9]) expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 99.9})).toEqual([5, 14]) - expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 221.5})).toEqual([5, 29]) - expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 222})).toEqual([5, 30]) + expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 224.4365234375})).toEqual([5, 29]) + expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 225})).toEqual([5, 30]) it "clips pixel positions above buffer start", -> expect(linesYardstick.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0] diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 6f53ee4e2..f6936e4c4 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -958,8 +958,8 @@ describe "TextEditorComponent", -> cursorNodes = componentNode.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 1 expect(cursorNodes[0].offsetHeight).toBe lineHeightInPixels - expect(cursorNodes[0].offsetWidth).toBe charWidth - expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{5 * charWidth}px, #{0 * lineHeightInPixels}px)" + expect(cursorNodes[0].offsetWidth).toBeCloseTo charWidth, 0 + expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{Math.round(5 * charWidth)}px, #{0 * lineHeightInPixels}px)" cursor2 = editor.addCursorAtScreenPosition([8, 11], autoscroll: false) cursor3 = editor.addCursorAtScreenPosition([4, 10], autoscroll: false) @@ -968,8 +968,8 @@ describe "TextEditorComponent", -> cursorNodes = componentNode.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 2 expect(cursorNodes[0].offsetTop).toBe 0 - expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{5 * charWidth}px, #{0 * lineHeightInPixels}px)" - expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{10 * charWidth}px, #{4 * lineHeightInPixels}px)" + expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{Math.round(5 * charWidth)}px, #{0 * lineHeightInPixels}px)" + expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{Math.round(10 * charWidth)}px, #{4 * lineHeightInPixels}px)" verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels verticalScrollbarNode.dispatchEvent(new UIEvent('scroll')) @@ -980,13 +980,13 @@ describe "TextEditorComponent", -> cursorNodes = componentNode.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 2 - expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{10 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{4 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)" - expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{11 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{8 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)" + expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{Math.round(10 * charWidth - horizontalScrollbarNode.scrollLeft)}px, #{4 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)" + expect(cursorNodes[1].style['-webkit-transform']).toBe "translate(#{Math.round(11 * charWidth - horizontalScrollbarNode.scrollLeft)}px, #{8 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)" editor.onDidChangeCursorPosition cursorMovedListener = jasmine.createSpy('cursorMovedListener') cursor3.setScreenPosition([4, 11], autoscroll: false) nextAnimationFrame() - expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{4 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)" + expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{Math.round(11 * charWidth - horizontalScrollbarNode.scrollLeft)}px, #{4 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)" expect(cursorMovedListener).toHaveBeenCalled() cursor3.destroy() @@ -994,7 +994,7 @@ describe "TextEditorComponent", -> cursorNodes = componentNode.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 1 - expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{11 * charWidth - horizontalScrollbarNode.scrollLeft}px, #{8 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)" + expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{Math.round(11 * charWidth - horizontalScrollbarNode.scrollLeft)}px, #{8 * lineHeightInPixels - verticalScrollbarNode.scrollTop}px)" it "accounts for character widths when positioning cursors", -> atom.config.set('editor.fontFamily', 'sans-serif') @@ -1010,8 +1010,8 @@ describe "TextEditorComponent", -> range.setEnd(cursorLocationTextNode, 1) rangeRect = range.getBoundingClientRect() - expect(cursorRect.left).toBe rangeRect.left - expect(cursorRect.width).toBe rangeRect.width + expect(cursorRect.left).toBeCloseTo rangeRect.left, 0 + expect(cursorRect.width).toBeCloseTo rangeRect.width, 0 it "accounts for the width of paired characters when positioning cursors", -> atom.config.set('editor.fontFamily', 'sans-serif') @@ -1029,8 +1029,8 @@ describe "TextEditorComponent", -> range.setEnd(cursorLocationTextNode, 1) rangeRect = range.getBoundingClientRect() - expect(cursorRect.left).toBe rangeRect.left - expect(cursorRect.width).toBe rangeRect.width + expect(cursorRect.left).toBeCloseTo rangeRect.left, 0 + expect(cursorRect.width).toBeCloseTo rangeRect.width, 0 it "positions cursors correctly after character widths are changed via a stylesheet change", -> atom.config.set('editor.fontFamily', 'sans-serif') @@ -1053,8 +1053,8 @@ describe "TextEditorComponent", -> range.setEnd(cursorLocationTextNode, 1) rangeRect = range.getBoundingClientRect() - expect(cursorRect.left).toBe rangeRect.left - expect(cursorRect.width).toBe rangeRect.width + expect(cursorRect.left).toBeCloseTo rangeRect.left, 0 + expect(cursorRect.width).toBeCloseTo rangeRect.width, 0 atom.themes.removeStylesheet('test') @@ -1062,13 +1062,13 @@ describe "TextEditorComponent", -> editor.setCursorScreenPosition([0, Infinity]) nextAnimationFrame() cursorNode = componentNode.querySelector('.cursor') - expect(cursorNode.offsetWidth).toBe charWidth + expect(cursorNode.offsetWidth).toBeCloseTo charWidth, 0 it "gives the cursor a non-zero width even if it's inside atomic tokens", -> editor.setCursorScreenPosition([1, 0]) nextAnimationFrame() cursorNode = componentNode.querySelector('.cursor') - expect(cursorNode.offsetWidth).toBe charWidth + expect(cursorNode.offsetWidth).toBeCloseTo charWidth, 0 it "blinks cursors when they aren't moving", -> cursorsNode = componentNode.querySelector('.cursors') @@ -1102,21 +1102,21 @@ describe "TextEditorComponent", -> cursorNodes = componentNode.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 1 - expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{8 * charWidth}px, #{6 * lineHeightInPixels}px)" + expect(cursorNodes[0].style['-webkit-transform']).toBe "translate(#{Math.round(8 * charWidth)}px, #{6 * lineHeightInPixels}px)" it "updates cursor positions when the line height changes", -> editor.setCursorBufferPosition([1, 10]) component.setLineHeight(2) nextAnimationFrame() cursorNode = componentNode.querySelector('.cursor') - expect(cursorNode.style['-webkit-transform']).toBe "translate(#{10 * editor.getDefaultCharWidth()}px, #{editor.getLineHeightInPixels()}px)" + expect(cursorNode.style['-webkit-transform']).toBe "translate(#{Math.round(10 * editor.getDefaultCharWidth())}px, #{editor.getLineHeightInPixels()}px)" it "updates cursor positions when the font size changes", -> editor.setCursorBufferPosition([1, 10]) component.setFontSize(10) nextAnimationFrame() cursorNode = componentNode.querySelector('.cursor') - expect(cursorNode.style['-webkit-transform']).toBe "translate(#{10 * editor.getDefaultCharWidth()}px, #{editor.getLineHeightInPixels()}px)" + expect(cursorNode.style['-webkit-transform']).toBe "translate(#{Math.round(10 * editor.getDefaultCharWidth())}px, #{editor.getLineHeightInPixels()}px)" it "updates cursor positions when the font family changes", -> editor.setCursorBufferPosition([1, 10]) @@ -1125,7 +1125,7 @@ describe "TextEditorComponent", -> cursorNode = componentNode.querySelector('.cursor') {left} = wrapperNode.pixelPositionForScreenPosition([1, 10]) - expect(cursorNode.style['-webkit-transform']).toBe "translate(#{left}px, #{editor.getLineHeightInPixels()}px)" + expect(cursorNode.style['-webkit-transform']).toBe "translate(#{Math.round(left)}px, #{editor.getLineHeightInPixels()}px)" describe "selection rendering", -> [scrollViewNode, scrollViewClientLeft] = [] @@ -1144,8 +1144,8 @@ describe "TextEditorComponent", -> regionRect = regions[0].getBoundingClientRect() expect(regionRect.top).toBe 1 * lineHeightInPixels expect(regionRect.height).toBe 1 * lineHeightInPixels - expect(regionRect.left).toBe scrollViewClientLeft + 6 * charWidth - expect(regionRect.width).toBe 4 * charWidth + expect(regionRect.left).toBeCloseTo scrollViewClientLeft + 6 * charWidth, 0 + expect(regionRect.width).toBeCloseTo 4 * charWidth, 0 it "renders 2 regions for 2-line selections", -> editor.setSelectedScreenRange([[1, 6], [2, 10]]) @@ -1157,14 +1157,14 @@ describe "TextEditorComponent", -> region1Rect = regions[0].getBoundingClientRect() expect(region1Rect.top).toBe 1 * lineHeightInPixels expect(region1Rect.height).toBe 1 * lineHeightInPixels - expect(region1Rect.left).toBe scrollViewClientLeft + 6 * charWidth - expect(region1Rect.right).toBe tileNode.getBoundingClientRect().right + expect(region1Rect.left).toBeCloseTo scrollViewClientLeft + 6 * charWidth, 0 + expect(region1Rect.right).toBeCloseTo tileNode.getBoundingClientRect().right, 0 region2Rect = regions[1].getBoundingClientRect() expect(region2Rect.top).toBe 2 * lineHeightInPixels expect(region2Rect.height).toBe 1 * lineHeightInPixels - expect(region2Rect.left).toBe scrollViewClientLeft + 0 - expect(region2Rect.width).toBe 10 * charWidth + expect(region2Rect.left).toBeCloseTo scrollViewClientLeft + 0, 0 + expect(region2Rect.width).toBeCloseTo 10 * charWidth, 0 it "renders 3 regions per tile for selections with more than 2 lines", -> editor.setSelectedScreenRange([[0, 6], [5, 10]]) @@ -1178,20 +1178,20 @@ describe "TextEditorComponent", -> region1Rect = regions[0].getBoundingClientRect() expect(region1Rect.top).toBe 0 expect(region1Rect.height).toBe 1 * lineHeightInPixels - expect(region1Rect.left).toBe scrollViewClientLeft + 6 * charWidth - expect(region1Rect.right).toBe tileNode.getBoundingClientRect().right + expect(region1Rect.left).toBeCloseTo scrollViewClientLeft + 6 * charWidth, 0 + expect(region1Rect.right).toBeCloseTo tileNode.getBoundingClientRect().right, 0 region2Rect = regions[1].getBoundingClientRect() expect(region2Rect.top).toBe 1 * lineHeightInPixels expect(region2Rect.height).toBe 1 * lineHeightInPixels - expect(region2Rect.left).toBe scrollViewClientLeft + 0 - expect(region2Rect.right).toBe tileNode.getBoundingClientRect().right + expect(region2Rect.left).toBeCloseTo scrollViewClientLeft + 0, 0 + expect(region2Rect.right).toBeCloseTo tileNode.getBoundingClientRect().right, 0 region3Rect = regions[2].getBoundingClientRect() expect(region3Rect.top).toBe 2 * lineHeightInPixels expect(region3Rect.height).toBe 1 * lineHeightInPixels - expect(region3Rect.left).toBe scrollViewClientLeft + 0 - expect(region3Rect.right).toBe tileNode.getBoundingClientRect().right + expect(region3Rect.left).toBeCloseTo scrollViewClientLeft + 0, 0 + expect(region3Rect.right).toBeCloseTo tileNode.getBoundingClientRect().right, 0 # Tile 3 tileNode = component.tileNodesForLines()[1] @@ -1201,20 +1201,20 @@ describe "TextEditorComponent", -> region1Rect = regions[0].getBoundingClientRect() expect(region1Rect.top).toBe 3 * lineHeightInPixels expect(region1Rect.height).toBe 1 * lineHeightInPixels - expect(region1Rect.left).toBe scrollViewClientLeft + 0 - expect(region1Rect.right).toBe tileNode.getBoundingClientRect().right + expect(region1Rect.left).toBeCloseTo scrollViewClientLeft + 0, 0 + expect(region1Rect.right).toBeCloseTo tileNode.getBoundingClientRect().right, 0 region2Rect = regions[1].getBoundingClientRect() expect(region2Rect.top).toBe 4 * lineHeightInPixels expect(region2Rect.height).toBe 1 * lineHeightInPixels - expect(region2Rect.left).toBe scrollViewClientLeft + 0 - expect(region2Rect.right).toBe tileNode.getBoundingClientRect().right + expect(region2Rect.left).toBeCloseTo scrollViewClientLeft + 0, 0 + expect(region2Rect.right).toBeCloseTo tileNode.getBoundingClientRect().right, 0 region3Rect = regions[2].getBoundingClientRect() expect(region3Rect.top).toBe 5 * lineHeightInPixels expect(region3Rect.height).toBe 1 * lineHeightInPixels - expect(region3Rect.left).toBe scrollViewClientLeft + 0 - expect(region3Rect.width).toBe 10 * charWidth + expect(region3Rect.left).toBeCloseTo scrollViewClientLeft + 0, 0 + expect(region3Rect.width).toBeCloseTo 10 * charWidth, 0 it "does not render empty selections", -> editor.addSelectionForBufferRange([[2, 2], [2, 2]]) @@ -1237,7 +1237,7 @@ describe "TextEditorComponent", -> nextAnimationFrame() selectionNode = componentNode.querySelector('.region') expect(selectionNode.offsetTop).toBe editor.getLineHeightInPixels() - expect(selectionNode.offsetLeft).toBe 6 * editor.getDefaultCharWidth() + expect(selectionNode.offsetLeft).toBeCloseTo 6 * editor.getDefaultCharWidth(), 0 it "updates selections when the font family changes", -> editor.setSelectedBufferRange([[1, 6], [1, 10]]) @@ -1245,7 +1245,7 @@ describe "TextEditorComponent", -> nextAnimationFrame() selectionNode = componentNode.querySelector('.region') expect(selectionNode.offsetTop).toBe editor.getLineHeightInPixels() - expect(selectionNode.offsetLeft).toBe wrapperNode.pixelPositionForScreenPosition([1, 6]).left + expect(selectionNode.offsetLeft).toBeCloseTo wrapperNode.pixelPositionForScreenPosition([1, 6]).left, 0 it "will flash the selection when flash:true is passed to editor::setSelectedBufferRange", -> editor.setSelectedBufferRange([[1, 6], [1, 10]], flash: true) @@ -1439,8 +1439,8 @@ describe "TextEditorComponent", -> regionRect = regions[0].style expect(regionRect.top).toBe (0 + 'px') expect(regionRect.height).toBe 1 * lineHeightInPixels + 'px' - expect(regionRect.left).toBe 2 * charWidth + 'px' - expect(regionRect.width).toBe 2 * charWidth + 'px' + expect(regionRect.left).toBe Math.round(2 * charWidth) + 'px' + expect(regionRect.width).toBe Math.round(2 * charWidth) + 'px' it "renders highlights decoration's marker is added", -> regions = componentNode.querySelectorAll('.test-highlight .region') @@ -1606,7 +1606,7 @@ describe "TextEditorComponent", -> position = wrapperNode.pixelPositionForBufferPosition([2, 10]) overlay = component.getTopmostDOMNode().querySelector('atom-overlay') - expect(overlay.style.left).toBe position.left + gutterWidth + 'px' + expect(overlay.style.left).toBe Math.round(position.left + gutterWidth) + 'px' expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px' describe "positioning the overlay when near the edge of the editor", -> @@ -1614,10 +1614,10 @@ describe "TextEditorComponent", -> beforeEach -> atom.storeWindowDimensions() - itemWidth = 4 * editor.getDefaultCharWidth() + itemWidth = Math.round(4 * editor.getDefaultCharWidth()) itemHeight = 4 * editor.getLineHeightInPixels() - windowWidth = gutterWidth + 30 * editor.getDefaultCharWidth() + windowWidth = Math.round(gutterWidth + 30 * editor.getDefaultCharWidth()) windowHeight = 10 * editor.getLineHeightInPixels() item.style.width = itemWidth + 'px' @@ -1645,7 +1645,7 @@ describe "TextEditorComponent", -> position = wrapperNode.pixelPositionForBufferPosition([0, 26]) overlay = component.getTopmostDOMNode().querySelector('atom-overlay') - expect(overlay.style.left).toBe position.left + gutterWidth + 'px' + expect(overlay.style.left).toBe Math.round(position.left + gutterWidth) + 'px' expect(overlay.style.top).toBe position.top + editor.getLineHeightInPixels() + 'px' editor.insertText('a') @@ -1689,7 +1689,7 @@ describe "TextEditorComponent", -> wrapperNode.focus() # updates via state change nextAnimationFrame() expect(inputNode.offsetTop).toBe (5 * lineHeightInPixels) - wrapperNode.getScrollTop() - expect(inputNode.offsetLeft).toBe (4 * charWidth) - wrapperNode.getScrollLeft() + expect(inputNode.offsetLeft).toBeCloseTo (4 * charWidth) - wrapperNode.getScrollLeft(), 0 # In bounds, not focused inputNode.blur() # updates via state change @@ -2482,7 +2482,7 @@ describe "TextEditorComponent", -> rightOfLongestLine = component.lineNodeForScreenRow(6).querySelector('.line > span:last-child').getBoundingClientRect().right leftOfVerticalScrollbar = verticalScrollbarNode.getBoundingClientRect().left - expect(Math.round(rightOfLongestLine)).toBe leftOfVerticalScrollbar - 1 # Leave 1 px so the cursor is visible on the end of the line + expect(Math.round(rightOfLongestLine)).toBeCloseTo leftOfVerticalScrollbar - 1, 0 # Leave 1 px so the cursor is visible on the end of the line it "only displays dummy scrollbars when scrollable in that direction", -> expect(verticalScrollbarNode.style.display).toBe 'none' @@ -2963,7 +2963,7 @@ describe "TextEditorComponent", -> cursorLeft = componentNode.querySelector('.cursor').getBoundingClientRect().left line0Right = componentNode.querySelector('.line > span:last-child').getBoundingClientRect().right - expect(cursorLeft).toBe line0Right + expect(cursorLeft).toBeCloseTo line0Right, 0 describe "when the fontFamily changes while the editor is hidden", -> it "does not attempt to measure the defaultCharWidth until the editor becomes visible again", -> @@ -2995,7 +2995,7 @@ describe "TextEditorComponent", -> cursorLeft = componentNode.querySelector('.cursor').getBoundingClientRect().left line0Right = componentNode.querySelector('.line > span:last-child').getBoundingClientRect().right - expect(cursorLeft).toBe line0Right + expect(cursorLeft).toBeCloseTo line0Right, 0 describe "when stylesheets change while the editor is hidden", -> afterEach -> @@ -3021,7 +3021,7 @@ describe "TextEditorComponent", -> cursorLeft = componentNode.querySelector('.cursor').getBoundingClientRect().left line0Right = componentNode.querySelector('.line > span:last-child').getBoundingClientRect().right - expect(cursorLeft).toBe line0Right + expect(cursorLeft).toBeCloseTo line0Right, 0 describe "when lines are changed while the editor is hidden", -> xit "does not measure new characters until the editor is shown again", -> @@ -3350,8 +3350,9 @@ describe "TextEditorComponent", -> editor.setSelectedBufferRange([[5, 6], [6, 8]]) nextAnimationFrame() + right = wrapperNode.pixelPositionForBufferPosition([6, 8 + editor.getHorizontalScrollMargin()]).left expect(wrapperNode.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10 - expect(wrapperNode.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10 + expect(wrapperNode.getScrollRight()).toBeCloseTo right, 0 editor.setSelectedBufferRange([[0, 0], [0, 0]]) nextAnimationFrame() @@ -3361,14 +3362,16 @@ describe "TextEditorComponent", -> editor.setSelectedBufferRange([[6, 6], [6, 8]]) nextAnimationFrame() expect(wrapperNode.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10 - expect(wrapperNode.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10 + expect(wrapperNode.getScrollRight()).toBeCloseTo right, 0 describe "when adding selections for buffer ranges", -> it "autoscrolls to the added selection if needed", -> editor.addSelectionForBufferRange([[8, 10], [8, 15]]) nextAnimationFrame() + + right = wrapperNode.pixelPositionForBufferPosition([8, 15]).left expect(wrapperNode.getScrollBottom()).toBe (9 * 10) + (2 * 10) - expect(wrapperNode.getScrollRight()).toBe (15 * 10) + (2 * 10) + expect(wrapperNode.getScrollRight()).toBeCloseTo(right + 2 * 10, 0) describe "when selecting lines containing cursors", -> it "autoscrolls to the selection", -> @@ -3406,9 +3409,10 @@ describe "TextEditorComponent", -> editor.scrollToCursorPosition() nextAnimationFrame() + right = wrapperNode.pixelPositionForScreenPosition([8, 9 + editor.getHorizontalScrollMargin()]).left expect(wrapperNode.getScrollTop()).toBe (8.8 * 10) - 30 expect(wrapperNode.getScrollBottom()).toBe (8.3 * 10) + 30 - expect(wrapperNode.getScrollRight()).toBe (9 + editor.getHorizontalScrollMargin()) * 10 + expect(wrapperNode.getScrollRight()).toBeCloseTo right, 0 wrapperNode.setScrollTop(0) editor.scrollToCursorPosition(center: false) @@ -3460,11 +3464,13 @@ describe "TextEditorComponent", -> editor.moveRight() nextAnimationFrame() - expect(wrapperNode.getScrollRight()).toBe 6 * 10 + right = wrapperNode.pixelPositionForScreenPosition([0, 6]).left + expect(wrapperNode.getScrollRight()).toBeCloseTo right, 0 editor.moveRight() nextAnimationFrame() - expect(wrapperNode.getScrollRight()).toBe 7 * 10 + right = wrapperNode.pixelPositionForScreenPosition([0, 7]).left + expect(wrapperNode.getScrollRight()).toBeCloseTo right, 0 it "scrolls left when the last cursor gets closer than ::horizontalScrollMargin to the left of the editor", -> wrapperNode.setScrollRight(wrapperNode.getScrollWidth()) @@ -3475,11 +3481,13 @@ describe "TextEditorComponent", -> editor.moveLeft() nextAnimationFrame() - expect(wrapperNode.getScrollLeft()).toBe 59 * 10 + left = wrapperNode.pixelPositionForScreenPosition([6, 59]).left + expect(wrapperNode.getScrollLeft()).toBeCloseTo left, 0 editor.moveLeft() nextAnimationFrame() - expect(wrapperNode.getScrollLeft()).toBe 58 * 10 + left = wrapperNode.pixelPositionForScreenPosition([6, 58]).left + expect(wrapperNode.getScrollLeft()).toBeCloseTo left, 0 it "scrolls down when inserting lines makes the document longer than the editor's height", -> editor.setCursorScreenPosition([13, Infinity]) diff --git a/src/browser/atom-window.coffee b/src/browser/atom-window.coffee index 99b28444f..1372d46ec 100644 --- a/src/browser/atom-window.coffee +++ b/src/browser/atom-window.coffee @@ -28,7 +28,7 @@ class AtomWindow title: 'Atom' 'web-preferences': 'direct-write': true - 'subpixel-font-scaling': false + 'subpixel-font-scaling': true # Don't set icon on Windows so the exe's ico will be used as window and # taskbar's icon. See https://github.com/atom/atom/issues/4811 for more. if process.platform is 'linux' diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index b0591b10f..e647aa40c 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -501,7 +501,7 @@ class TextEditorPresenter return unless cursor.isVisible() and @startRow <= screenRange.start.row < @endRow pixelRect = @pixelRectForScreenRange(screenRange) - pixelRect.width = @baseCharacterWidth if pixelRect.width is 0 + pixelRect.width = Math.round(@baseCharacterWidth) if pixelRect.width is 0 @state.content.cursors[cursor.id] = pixelRect updateOverlaysState: -> @@ -1134,6 +1134,10 @@ class TextEditorPresenter @linesYardstick.pixelPositionForScreenPosition(screenPosition, clip) position.top -= @getScrollTop() position.left -= @getScrollLeft() + + position.top = Math.round(position.top) + position.left = Math.round(position.left) + position hasPixelRectRequirements: -> @@ -1146,6 +1150,12 @@ class TextEditorPresenter rect = @linesYardstick.pixelRectForScreenRange(screenRange) rect.top -= @getScrollTop() rect.left -= @getScrollLeft() + + rect.top = Math.round(rect.top) + rect.left = Math.round(rect.left) + rect.width = Math.round(rect.width) + rect.height = Math.round(rect.height) + rect observeDecoration: (decoration) -> @@ -1507,10 +1517,10 @@ class TextEditorPresenter @emitDidUpdateState() getVerticalScrollMarginInPixels: -> - @model.getVerticalScrollMargin() * @lineHeight + Math.round(@model.getVerticalScrollMargin() * @lineHeight) getHorizontalScrollMarginInPixels: -> - @model.getHorizontalScrollMargin() * @baseCharacterWidth + Math.round(@model.getHorizontalScrollMargin() * @baseCharacterWidth) getVerticalScrollbarWidth: -> @verticalScrollbarWidth