terminal/src/renderer/dx/CustomTextRenderer.cpp

695 lines
34 KiB
C++
Raw Normal View History

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "CustomTextRenderer.h"
#include <wrl.h>
#include <wrl/client.h>
#include <VersionHelpers.h>
using namespace Microsoft::Console::Render;
#pragma region IDWritePixelSnapping methods
// Routine Description:
// - Implementation of IDWritePixelSnapping::IsPixelSnappingDisabled
// - Determines if we're allowed to snap text to pixels for this particular drawing context
// Arguments:
// - clientDrawingContext - Pointer to structure of information required to draw
// - isDisabled - TRUE if we do not snap to nearest pixels. FALSE otherwise.
// Return Value:
// - S_OK
[[nodiscard]] HRESULT CustomTextRenderer::IsPixelSnappingDisabled(void* /*clientDrawingContext*/,
_Out_ BOOL* isDisabled) noexcept
{
RETURN_HR_IF_NULL(E_INVALIDARG, isDisabled);
*isDisabled = false;
return S_OK;
}
// Routine Description:
// - Implementation of IDWritePixelSnapping::GetPixelsPerDip
// - Retrieves the number of real monitor pixels to use per device-independent-pixel (DIP)
// - DIPs are used by DirectX all the way until the final drawing surface so things are only
// scaled at the very end and the complexity can be abstracted.
// Arguments:
// - clientDrawingContext - Pointer to structure of information required to draw
// - pixelsPerDip - The number of pixels per DIP. 96 is standard DPI.
// Return Value:
// - S_OK
[[nodiscard]] HRESULT CustomTextRenderer::GetPixelsPerDip(void* clientDrawingContext,
_Out_ FLOAT* pixelsPerDip) noexcept
{
RETURN_HR_IF_NULL(E_INVALIDARG, pixelsPerDip);
const DrawingContext* drawingContext = static_cast<DrawingContext*>(clientDrawingContext);
RETURN_HR_IF_NULL(E_INVALIDARG, drawingContext);
float dpiX, dpiY;
drawingContext->renderTarget->GetDpi(&dpiX, &dpiY);
*pixelsPerDip = dpiX / USER_DEFAULT_SCREEN_DPI;
return S_OK;
}
// Routine Description:
// - Implementation of IDWritePixelSnapping::GetCurrentTransform
// - Retrieves the the matrix transform to be used while laying pixels onto the
// drawing context
// Arguments:
// - clientDrawingContext - Pointer to structure of information required to draw
// - transform - The matrix transform to use to adapt DIP representations into real monitor coordinates.
// Return Value:
// - S_OK
[[nodiscard]] HRESULT CustomTextRenderer::GetCurrentTransform(void* clientDrawingContext,
DWRITE_MATRIX* transform) noexcept
{
RETURN_HR_IF_NULL(E_INVALIDARG, transform);
const DrawingContext* drawingContext = static_cast<DrawingContext*>(clientDrawingContext);
RETURN_HR_IF_NULL(E_INVALIDARG, drawingContext);
// Retrieve as D2D1 matrix then copy into DWRITE matrix.
D2D1_MATRIX_3X2_F d2d1Matrix{ 0 };
drawingContext->renderTarget->GetTransform(&d2d1Matrix);
transform->dx = d2d1Matrix.dx;
transform->dy = d2d1Matrix.dy;
transform->m11 = d2d1Matrix.m11;
transform->m12 = d2d1Matrix.m12;
transform->m21 = d2d1Matrix.m21;
transform->m22 = d2d1Matrix.m22;
return S_OK;
}
#pragma endregion
#pragma region IDWriteTextRenderer methods
// Routine Description:
// - Implementation of IDWriteTextRenderer::DrawUnderline
// - Directs us to draw an underline on the given context at the given position.
// Arguments:
// - clientDrawingContext - Pointer to structure of information required to draw
// - baselineOriginX - The text baseline position's X coordinate
// - baselineOriginY - The text baseline position's Y coordinate
// - The baseline is generally not the top nor the bottom of the "cell" that
// text is drawn into. It's usually somewhere "in the middle" and depends on the
// font and the glyphs. It can be calculated during layout and analysis in respect
// to the given font and glyphs.
// - underline - The properties of the underline that we should use for drawing
// - clientDrawingEffect - any special effect to pass along for rendering
// Return Value:
// - S_OK
[[nodiscard]] HRESULT CustomTextRenderer::DrawUnderline(void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
_In_ const DWRITE_UNDERLINE* underline,
IUnknown* clientDrawingEffect) noexcept
{
return _FillRectangle(clientDrawingContext,
clientDrawingEffect,
baselineOriginX,
baselineOriginY + underline->offset,
underline->width,
underline->thickness,
underline->readingDirection,
underline->flowDirection);
}
// Routine Description:
// - Implementation of IDWriteTextRenderer::DrawStrikethrough
// - Directs us to draw a strikethrough on the given context at the given position.
// Arguments:
// - clientDrawingContext - Pointer to structure of information required to draw
// - baselineOriginX - The text baseline position's X coordinate
// - baselineOriginY - The text baseline position's Y coordinate
// - The baseline is generally not the top nor the bottom of the "cell" that
// text is drawn into. It's usually somewhere "in the middle" and depends on the
// font and the glyphs. It can be calculated during layout and analysis in respect
// to the given font and glyphs.
// - strikethrough - The properties of the strikethrough that we should use for drawing
// - clientDrawingEffect - any special effect to pass along for rendering
// Return Value:
// - S_OK
[[nodiscard]] HRESULT CustomTextRenderer::DrawStrikethrough(void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
_In_ const DWRITE_STRIKETHROUGH* strikethrough,
IUnknown* clientDrawingEffect) noexcept
{
return _FillRectangle(clientDrawingContext,
clientDrawingEffect,
baselineOriginX,
baselineOriginY + strikethrough->offset,
strikethrough->width,
strikethrough->thickness,
strikethrough->readingDirection,
strikethrough->flowDirection);
}
// Routine Description:
// - Helper method to draw a line through our text.
// Arguments:
// - clientDrawingContext - Pointer to structure of information required to draw
// - clientDrawingEffect - any special effect passed along for rendering
// - x - The left coordinate of the rectangle
// - y - The top coordinate of the rectangle
// - width - The width of the rectangle (from X to the right)
// - height - The height of the rectangle (from Y down)
// - readingDirection - textual reading information that could affect the rectangle
// - flowDirection - textual flow information that could affect the rectangle
// Return Value:
// - S_OK
[[nodiscard]] HRESULT CustomTextRenderer::_FillRectangle(void* clientDrawingContext,
IUnknown* clientDrawingEffect,
float x,
float y,
float width,
float thickness,
DWRITE_READING_DIRECTION /*readingDirection*/,
DWRITE_FLOW_DIRECTION /*flowDirection*/) noexcept
{
DrawingContext* drawingContext = static_cast<DrawingContext*>(clientDrawingContext);
RETURN_HR_IF_NULL(E_INVALIDARG, drawingContext);
// Get brush
ID2D1Brush* brush = drawingContext->foregroundBrush;
if (clientDrawingEffect != nullptr)
{
brush = static_cast<ID2D1Brush*>(clientDrawingEffect);
}
const D2D1_RECT_F rect = D2D1::RectF(x, y, x + width, y + thickness);
drawingContext->renderTarget->FillRectangle(&rect, brush);
return S_OK;
}
// Routine Description:
// - Implementation of IDWriteTextRenderer::DrawInlineObject
// - Passes drawing control from the outer layout down into the context of an embedded object
// which can have its own drawing layout and renderer properties at a given position
// Arguments:
// - clientDrawingContext - Pointer to structure of information required to draw
// - originX - The left coordinate of the draw position
// - originY - The top coordinate of the draw position
// - inlineObject - The object to draw at the position
// - isSideways - Should be drawn vertically instead of horizontally
// - isRightToLeft - Should be drawn RTL (or bottom to top) instead of the default way
// - clientDrawingEffect - any special effect passed along for rendering
// Return Value:
// - S_OK or appropriate error from the delegated inline object's draw call
[[nodiscard]] HRESULT CustomTextRenderer::DrawInlineObject(void* clientDrawingContext,
FLOAT originX,
FLOAT originY,
IDWriteInlineObject* inlineObject,
BOOL isSideways,
BOOL isRightToLeft,
IUnknown* clientDrawingEffect) noexcept
{
RETURN_HR_IF_NULL(E_INVALIDARG, inlineObject);
return inlineObject->Draw(clientDrawingContext,
this,
originX,
originY,
isSideways,
isRightToLeft,
clientDrawingEffect);
}
// Routine Description:
// - Implementation of IDWriteTextRenderer::DrawInlineObject
// - Passes drawing control from the outer layout down into the context of an embedded object
// which can have its own drawing layout and renderer properties at a given position
// Arguments:
// - clientDrawingContext - Pointer to structure of information required to draw
// - baselineOriginX - The text baseline position's X coordinate
// - baselineOriginY - The text baseline position's Y coordinate
// - The baseline is generally not the top nor the bottom of the "cell" that
// text is drawn into. It's usually somewhere "in the middle" and depends on the
// font and the glyphs. It can be calculated during layout and analysis in respect
// to the given font and glyphs.
// - measuringMode - The mode to measure glyphs in the DirectWrite context
// - glyphRun - Information on the glyphs
// - glyphRunDescription - Further metadata about the glyphs used while drawing
// - clientDrawingEffect - any special effect passed along for rendering
// Return Value:
// - S_OK, GSL/WIL/STL error, or appropriate DirectX/Direct2D/DirectWrite based error while drawing.
[[nodiscard]] HRESULT CustomTextRenderer::DrawGlyphRun(
void* clientDrawingContext,
FLOAT baselineOriginX,
FLOAT baselineOriginY,
DWRITE_MEASURING_MODE measuringMode,
const DWRITE_GLYPH_RUN* glyphRun,
const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription,
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
IUnknown* clientDrawingEffect)
{
// Color glyph rendering sourced from https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/DWriteColorGlyph
DrawingContext* drawingContext = static_cast<DrawingContext*>(clientDrawingContext);
// Since we've delegated the drawing of the background of the text into this function, the origin passed in isn't actually the baseline.
// It's the top left corner. Save that off first.
const D2D1_POINT_2F origin = D2D1::Point2F(baselineOriginX, baselineOriginY);
// Then make a copy for the baseline origin (which is part way down the left side of the text, not the top or bottom).
// We'll use this baseline Origin for drawing the actual text.
2019-09-06 02:16:31 +02:00
const D2D1_POINT_2F baselineOrigin{ origin.x, origin.y + drawingContext->spacing.baseline };
::Microsoft::WRL::ComPtr<ID2D1DeviceContext> d2dContext;
RETURN_IF_FAILED(drawingContext->renderTarget->QueryInterface(d2dContext.GetAddressOf()));
// Draw the background
Fix RTL regression (fixes #4779) (#5734) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request This fixes the RTL regression caused in #4747. We create the rectangle taking the direction (through the BiDi Level) into account, and then the rendering works again. The GlyphRun shaping could still probably use some work to be a polished thingy, and there are still issues with RTL getting chopped up a lot when there's font fallback going on, but this fixes the regression, and it's now functional again. <!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> ## References #4779 #4747 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [x] Closes #4779 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed * [ ] Requires documentation to be updated * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx <!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments The baseline is actually direction dependent. So when it was being initialized, the unconditional baseline as left broke it, setting the box off to right of the text. We just check if the `GlyphRun->bidiLevel` is set, and if so, we adjust it so that the baseline lines up with the right, not with the left. <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed ![image](https://user-images.githubusercontent.com/16987694/80968891-681cbc00-8e21-11ea-9e5c-9b7cf6d78d53.png)
2020-05-04 18:14:44 +02:00
// The rectangle needs to be deduced based on the origin and the BidiDirection
const auto advancesSpan = gsl::make_span(glyphRun->glyphAdvances, glyphRun->glyphCount);
const auto totalSpan = std::accumulate(advancesSpan.cbegin(), advancesSpan.cend(), 0.0f);
D2D1_RECT_F rect;
rect.top = origin.y;
rect.bottom = rect.top + drawingContext->cellSize.height;
rect.left = origin.x;
Fix RTL regression (fixes #4779) (#5734) <!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request This fixes the RTL regression caused in #4747. We create the rectangle taking the direction (through the BiDi Level) into account, and then the rendering works again. The GlyphRun shaping could still probably use some work to be a polished thingy, and there are still issues with RTL getting chopped up a lot when there's font fallback going on, but this fixes the regression, and it's now functional again. <!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> ## References #4779 #4747 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [x] Closes #4779 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed * [ ] Requires documentation to be updated * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx <!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments The baseline is actually direction dependent. So when it was being initialized, the unconditional baseline as left broke it, setting the box off to right of the text. We just check if the `GlyphRun->bidiLevel` is set, and if so, we adjust it so that the baseline lines up with the right, not with the left. <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed ![image](https://user-images.githubusercontent.com/16987694/80968891-681cbc00-8e21-11ea-9e5c-9b7cf6d78d53.png)
2020-05-04 18:14:44 +02:00
// Check for RTL, if it is, move rect.left to the left from the baseline
if (WI_IsFlagSet(glyphRun->bidiLevel, 1))
{
rect.left -= totalSpan;
}
rect.right = rect.left + totalSpan;
// Clip all drawing in this glyph run to where we expect.
// We need the AntialiasMode here to be Aliased to ensure
// that background boxes line up with each other and don't leave behind
// stray colors.
// See GH#3626 for more details.
d2dContext->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED);
// Ensure we pop it on the way out
auto popclip = wil::scope_exit([&d2dContext]() noexcept {
d2dContext->PopAxisAlignedClip();
});
d2dContext->FillRectangle(rect, drawingContext->backgroundBrush);
Use grayscale AA always on non-opaque backgrounds (#5277) ## Summary of the Pull Request When we're on acrylic, we can't have cleartype text unfortunately. This PR changes the DX renderer to force cleartype runs of text that are on a non-opaque background to use grayscale AA instead. ## References Here are some of the URLS I was referencing as writing this: * https://stackoverflow.com/q/23587787 * https://docs.microsoft.com/en-us/windows/win32/direct2d/supported-pixel-formats-and-alpha-modes#cleartype-and-alpha-modes * https://devblogs.microsoft.com/oldnewthing/20150129-00/?p=44803 * https://docs.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_layer_options * https://docs.microsoft.com/en-us/windows/win32/direct2d/direct2d-layers-overview#d2d1_layer_parameters1-and-d2d1_layer_options1 * https://docs.microsoft.com/en-us/windows/win32/api/dcommon/ne-dcommon-d2d1_alpha_mode?redirectedfrom=MSDN#cleartype-and-alpha-modes * https://stackoverflow.com/a/26523006 Additionally: * This was introduced in #4711 ## PR Checklist * [x] Closes #5098 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments Basically, if you use cleartype on a light background, what you'll get today is the text foreground color _added_ to the background. This will make the text look basically invisible. So, what I did was use some trickery with `PushLayer` to basically create a layer where the text would be forced to render in grayscale AA. I only enable this layer pushing business when both: * The user has enabled cleartype text * The background opacity < 1.0 This plumbs some information through from the TermControl to the DX Renderer to make this smooth. ## Validation Steps Performed Opened both cleartype and grayscale panes SxS, and messed with the opacity liberally.
2020-04-24 19:16:34 +02:00
// GH#5098: If we're rendering with cleartype text, we need to always render
// onto an opaque background. If our background _isn't_ opaque, then we need
// to use grayscale AA for this run of text.
//
// We can force grayscale AA for just this run of text by pushing a new
// layer onto the d2d context. We'll only need to do this for cleartype
// text, when our eventual background isn't actually opaque. See
// DxEngine::PaintBufferLine and DxEngine::UpdateDrawingBrushes for more
// details.
//
// DANGER: Layers slow us down. Only do this in the specific case where
// someone has chosen the slower ClearType antialiasing (versus the faster
// grayscale antialiasing).
// First, create the scope_exit to pop the layer. If we don't need the
// layer, we'll just gracefully release it.
auto popLayer = wil::scope_exit([&d2dContext]() noexcept {
d2dContext->PopLayer();
});
if (drawingContext->forceGrayscaleAA)
{
// Mysteriously, D2D1_LAYER_OPTIONS_INITIALIZE_FOR_CLEARTYPE actually
// gets us the behavior we want, which is grayscale.
d2dContext->PushLayer(D2D1::LayerParameters(rect,
nullptr,
D2D1_ANTIALIAS_MODE_ALIASED,
D2D1::IdentityMatrix(),
1.0,
nullptr,
D2D1_LAYER_OPTIONS_INITIALIZE_FOR_CLEARTYPE),
nullptr);
}
else
{
popLayer.release();
}
// Now go onto drawing the text.
// First check if we want a color font and try to extract color emoji first.
// Color emoji are only available on Windows 10+
static const bool s_isWindows10OrGreater = IsWindows10OrGreater();
if (WI_IsFlagSet(drawingContext->options, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT) && s_isWindows10OrGreater)
{
::Microsoft::WRL::ComPtr<ID2D1DeviceContext4> d2dContext4;
RETURN_IF_FAILED(d2dContext.As(&d2dContext4));
::Microsoft::WRL::ComPtr<IDWriteFactory4> dwriteFactory4;
RETURN_IF_FAILED(drawingContext->dwriteFactory->QueryInterface(dwriteFactory4.GetAddressOf()));
// The list of glyph image formats this renderer is prepared to support.
const DWRITE_GLYPH_IMAGE_FORMATS supportedFormats =
DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE |
DWRITE_GLYPH_IMAGE_FORMATS_CFF |
DWRITE_GLYPH_IMAGE_FORMATS_COLR |
DWRITE_GLYPH_IMAGE_FORMATS_SVG |
DWRITE_GLYPH_IMAGE_FORMATS_PNG |
DWRITE_GLYPH_IMAGE_FORMATS_JPEG |
DWRITE_GLYPH_IMAGE_FORMATS_TIFF |
DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8;
// Determine whether there are any color glyph runs within glyphRun. If
// there are, glyphRunEnumerator can be used to iterate through them.
::Microsoft::WRL::ComPtr<IDWriteColorGlyphRunEnumerator1> glyphRunEnumerator;
const HRESULT hr = dwriteFactory4->TranslateColorGlyphRun(baselineOrigin,
glyphRun,
glyphRunDescription,
supportedFormats,
measuringMode,
nullptr,
0,
&glyphRunEnumerator);
// If the analysis found no color glyphs in the run, just draw normally.
if (hr == DWRITE_E_NOCOLOR)
{
RETURN_IF_FAILED(_DrawBasicGlyphRun(drawingContext,
baselineOrigin,
measuringMode,
glyphRun,
glyphRunDescription,
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
drawingContext->foregroundBrush,
clientDrawingEffect));
}
else
{
RETURN_IF_FAILED(hr);
::Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> tempBrush;
// Complex case: the run has one or more color runs within it. Iterate
// over the sub-runs and draw them, depending on their format.
for (;;)
{
BOOL haveRun;
RETURN_IF_FAILED(glyphRunEnumerator->MoveNext(&haveRun));
if (!haveRun)
break;
DWRITE_COLOR_GLYPH_RUN1 const* colorRun;
RETURN_IF_FAILED(glyphRunEnumerator->GetCurrentRun(&colorRun));
const D2D1_POINT_2F currentBaselineOrigin = D2D1::Point2F(colorRun->baselineOriginX, colorRun->baselineOriginY);
switch (colorRun->glyphImageFormat)
{
case DWRITE_GLYPH_IMAGE_FORMATS_PNG:
case DWRITE_GLYPH_IMAGE_FORMATS_JPEG:
case DWRITE_GLYPH_IMAGE_FORMATS_TIFF:
case DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8:
{
// This run is bitmap glyphs. Use Direct2D to draw them.
d2dContext4->DrawColorBitmapGlyphRun(colorRun->glyphImageFormat,
currentBaselineOrigin,
&colorRun->glyphRun,
measuringMode);
}
break;
case DWRITE_GLYPH_IMAGE_FORMATS_SVG:
{
// This run is SVG glyphs. Use Direct2D to draw them.
d2dContext4->DrawSvgGlyphRun(currentBaselineOrigin,
&colorRun->glyphRun,
drawingContext->foregroundBrush,
nullptr, // svgGlyphStyle
0, // colorPaletteIndex
measuringMode);
}
break;
case DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE:
case DWRITE_GLYPH_IMAGE_FORMATS_CFF:
case DWRITE_GLYPH_IMAGE_FORMATS_COLR:
default:
{
// This run is solid-color outlines, either from non-color
// glyphs or from COLR glyph layers. Use Direct2D to draw them.
2019-09-06 02:16:31 +02:00
ID2D1Brush* layerBrush{ nullptr };
// The rule is "if 0xffff, use current brush." See:
// https://docs.microsoft.com/en-us/windows/desktop/api/dwrite_2/ns-dwrite_2-dwrite_color_glyph_run
if (colorRun->paletteIndex == 0xFFFF)
{
// This run uses the current text color.
layerBrush = drawingContext->foregroundBrush;
}
else
{
if (!tempBrush)
{
RETURN_IF_FAILED(d2dContext4->CreateSolidColorBrush(colorRun->runColor, &tempBrush));
}
else
{
// This run specifies its own color.
tempBrush->SetColor(colorRun->runColor);
}
layerBrush = tempBrush.Get();
}
// Draw the run with the selected color.
RETURN_IF_FAILED(_DrawBasicGlyphRun(drawingContext,
currentBaselineOrigin,
measuringMode,
&colorRun->glyphRun,
colorRun->glyphRunDescription,
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
layerBrush,
clientDrawingEffect));
}
break;
}
}
}
}
else
{
// Simple case: the run has no color glyphs. Draw the main glyph run
// using the current text color.
RETURN_IF_FAILED(_DrawBasicGlyphRun(drawingContext,
baselineOrigin,
measuringMode,
glyphRun,
glyphRunDescription,
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
drawingContext->foregroundBrush,
clientDrawingEffect));
}
return S_OK;
}
#pragma endregion
[[nodiscard]] HRESULT CustomTextRenderer::_DrawBasicGlyphRun(DrawingContext* clientDrawingContext,
D2D1_POINT_2F baselineOrigin,
DWRITE_MEASURING_MODE measuringMode,
_In_ const DWRITE_GLYPH_RUN* glyphRun,
_In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription,
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
ID2D1Brush* brush,
_In_opt_ IUnknown* clientDrawingEffect)
{
RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingContext);
RETURN_HR_IF_NULL(E_INVALIDARG, glyphRun);
RETURN_HR_IF_NULL(E_INVALIDARG, brush);
::Microsoft::WRL::ComPtr<ID2D1DeviceContext> d2dContext;
RETURN_IF_FAILED(clientDrawingContext->renderTarget->QueryInterface(d2dContext.GetAddressOf()));
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
// If a special drawing effect was specified, see if we know how to deal with it.
if (clientDrawingEffect)
{
::Microsoft::WRL::ComPtr<IBoxDrawingEffect> boxEffect;
if (SUCCEEDED(clientDrawingEffect->QueryInterface<IBoxDrawingEffect>(&boxEffect)))
{
return _DrawBoxRunManually(clientDrawingContext, baselineOrigin, measuringMode, glyphRun, glyphRunDescription, boxEffect.Get());
}
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
//_DrawBasicGlyphRunManually(clientDrawingContext, baselineOrigin, measuringMode, glyphRun, glyphRunDescription);
//_DrawGlowGlyphRun(clientDrawingContext, baselineOrigin, measuringMode, glyphRun, glyphRunDescription);
}
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
// If we get down here, there either was no special effect or we don't know what to do with it. Use the standard GlyphRun drawing.
// Using the context is the easiest/default way of drawing.
d2dContext->DrawGlyphRun(baselineOrigin, glyphRun, glyphRunDescription, brush, measuringMode);
return S_OK;
}
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
[[nodiscard]] HRESULT CustomTextRenderer::_DrawBoxRunManually(DrawingContext* clientDrawingContext,
D2D1_POINT_2F baselineOrigin,
DWRITE_MEASURING_MODE /*measuringMode*/,
_In_ const DWRITE_GLYPH_RUN* glyphRun,
_In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* /*glyphRunDescription*/,
_In_ IBoxDrawingEffect* clientDrawingEffect) noexcept
try
{
RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingContext);
RETURN_HR_IF_NULL(E_INVALIDARG, glyphRun);
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingEffect);
::Microsoft::WRL::ComPtr<ID2D1Factory> d2dFactory;
clientDrawingContext->renderTarget->GetFactory(d2dFactory.GetAddressOf());
::Microsoft::WRL::ComPtr<ID2D1PathGeometry> pathGeometry;
d2dFactory->CreatePathGeometry(pathGeometry.GetAddressOf());
::Microsoft::WRL::ComPtr<ID2D1GeometrySink> geometrySink;
pathGeometry->Open(geometrySink.GetAddressOf());
glyphRun->fontFace->GetGlyphRunOutline(
glyphRun->fontEmSize,
glyphRun->glyphIndices,
glyphRun->glyphAdvances,
glyphRun->glyphOffsets,
glyphRun->glyphCount,
glyphRun->isSideways,
glyphRun->bidiLevel % 2,
geometrySink.Get());
geometrySink->Close();
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
// Can be used to see the dimensions of what is written.
/*D2D1_RECT_F bounds;
pathGeometry->GetBounds(D2D1::IdentityMatrix(), &bounds);*/
// The bounds here are going to be centered around the baseline of the font.
// That is, the DWRITE_GLYPH_METRICS property for this glyph's baseline is going
// to be at the 0 point in the Y direction when we receive the geometry.
// The ascent will go up negative from Y=0 and the descent will go down positive from Y=0.
// As for the horizontal direction, I didn't study this in depth, but it appears to always be
// positive X with both the left and right edges being positive and away from X=0.
// For one particular instance, we might ask for the geometry for a U+2588 box and see the bounds as:
//
// Top=
// -20.315
// -----------
// | |
// | |
// Left= | | Right=
// 13.859 | | 26.135
// | |
// Origin --> X | |
// (0,0) | |
// -----------
// Bottom=
// 5.955
// Dig out the box drawing effect parameters.
BoxScale scale;
RETURN_IF_FAILED(clientDrawingEffect->GetScale(&scale));
// The scale transform will inflate the entire geometry first.
// We want to do this before it moves out of its original location as generally our
// algorithms for fitting cells will blow up the glyph to the size it needs to be first and then
// nudge it into place with the translations.
const auto scaleTransform = D2D1::Matrix3x2F::Scale(scale.HorizontalScale, scale.VerticalScale);
// Now shift it all the way to where the baseline says it should be.
const auto baselineTransform = D2D1::Matrix3x2F::Translation(baselineOrigin.x, baselineOrigin.y);
// Finally apply the little "nudge" that we may have been directed to align it better with the cell.
const auto offsetTransform = D2D1::Matrix3x2F::Translation(scale.HorizontalTranslation, scale.VerticalTranslation);
// The order is important here. Scale it first, then slide it into place.
const auto matrixTransformation = scaleTransform * baselineTransform * offsetTransform;
::Microsoft::WRL::ComPtr<ID2D1TransformedGeometry> transformedGeometry;
d2dFactory->CreateTransformedGeometry(pathGeometry.Get(),
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
&matrixTransformation,
transformedGeometry.GetAddressOf());
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
// Can be used to see the dimensions after translation.
/*D2D1_RECT_F boundsAfter;
transformedGeometry->GetBounds(D2D1::IdentityMatrix(), &boundsAfter);*/
// Compare this to the original bounds above to see what the matrix did.
// To make it useful, first visualize for yourself the pixel dimensions of the cell
// based on the baselineOrigin and the exact integer cell width and heights that we're storing.
// You'll also probably need the full-pixel ascent and descent because the point we're given
// is the baseline, not the top left corner of the cell as we're used to.
// Most of these metrics can be found in the initial font creation routines or in
// the line spacing applied to the text format (member variables on the renderer).
// baselineOrigin = (0, 567)
// fullPixelAscent = 39
// fullPixelDescent = 9
// cell dimensions = 26 x 48 (notice 48 height is 39 + 9 or ascent + descent)
// This means that our cell should be the rectangle
//
// T=528
// |-------|
// L=0 | |
// | |
// Baseline->x |
// Origin | | R=26
// |-------|
// B=576
//
// And we'll want to check that the bounds after transform will fit the glyph nicely inside
// this box.
// If not? We didn't do the scaling or translation correctly. Oops.
// Fill in the geometry. Don't outline, it can leave stuff outside the area we expect.
clientDrawingContext->renderTarget->FillGeometry(transformedGeometry.Get(), clientDrawingContext->foregroundBrush);
return S_OK;
}
Scale box drawing glyphs to fit cells for visual bliss (#5743) ## Summary of the Pull Request Identifies and scales glyphs in the box and line drawing ranges U+2500-U+259F to fit their cells. ## PR Checklist * [x] Closes #455 * [x] I work here. * [x] Manual tests. This is all graphical. * [x] Metric ton of comments * [x] Math spreadsheet included in PR. * [x] Double check RTL glyphs. * [x] Why is there the extra pixel? * [x] Scrolling the mouse wheel check is done. * [x] Not drawing outline? * [x] Am core contributor. Roar. * [x] Try suppressing negative scale factors and see if that gets rid of weird shading. ## Detailed Description of the Pull Request / Additional comments ### Background - We want the Terminal to be fast at drawing. To be fast at drawing, we perform differential drawing, or only drawing what is different from the previous frame. We use DXGI's `Present1` method to help us with this as it helps us compose only the deltas onto the previous frame at drawing time and assists us in scrolling regions from the previous frame without intervention. However, it only works on strictly integer pixel row heights. - Most of the hit testing and size-calculation logic in both the `conhost` and the Terminal products are based on the size of an individual cell. Historically, a cell was always dictated in a `COORD` structure, or two `SHORT` values... which are integers. As such, when we specify the space for any individual glyph to be displayed inside our terminal drawing region, we want it to fall perfectly inside of an integer box to ensure all these other algorithms work correctly and continue to do so. - Finally, we want the Terminal to have font fallback and locate glyphs that aren't in the primary selected font from any other font it can find on the system that contains the glyph, per DirectWrite's font fallback mechanisms. These glyphs won't necessarily have the same font or glyph metrics as the base font, but we need them to fit inside the same cell dimensions as if they did because the hit testing and other algorithms aren't aware of which particular font is sourcing each glyph, just the dimensions of the bounding box per cell. ### How does Terminal deal with this? - When we select a font, we perform some calculations using the design metrics of the font and glyphs to determine how we could fit them inside a cell with integer dimensions. Our process here is that we take the requested font size (which is generally a proxy for height), find the matching glyph width for that height then round it to an integer. We back convert from that now integer width to a height value which is almost certainly now a floating point number. But because we need an integer box value, we add line padding above and below the glyphs to ensure that the height is an integer as well as the width. Finally, we don't add the padding strictly equally. We attempt to align the English baseline of the glyph box directly onto an integer pixel multiple so most characters sit crisply on a line when displayed. - Note that fonts and their glyphs have a prescribed baseline, line gap, and advance values. We use those as guidelines to get us started, but then to meet our requirements, we pad out from those. This results in fonts that should be properly authored showing gaps. It also results in fonts that are improperly authored looking even worse than they normally would. ### Now how does block and line drawing come in? - Block and Line drawing glyphs are generally authored so they will look fine when the font and glyph metrics are followed exactly as prescribed by the font. (For some fonts, this still isn't true and we want them to look fine anyway.) - When we add additional padding or rounding to make glyphs fit inside of a cell, we can be adding more space than was prescribed around these glyphs. This can cause a gap to be visible. - Additionally, when we move things like baselines to land on a perfect integer pixel, we may be drawing a glyph lower in the bounding box than was prescribed originally. ### And how do we solve it? - We identify all glyphs in the line and block drawing ranges. - We find the bounding boxes of both the cell and the glyph. - We compare the height of the glyph to the height of the cell to see if we need to scale. We prescribe a scale transform if the glyph wouldn't be tall enough to fit the box. (We leave it alone otherwise as some glyphs intentionally overscan the box and scaling them can cause banding effects.) - We inspect the overhang/underhang above and below the boxes and translate transform them (slide them) so they cover the entire cell area. - We repeat the previous two steps but in the horizontal direction. ## Validation Steps Performed - See these commments: - https://github.com/microsoft/terminal/issues/455#issuecomment-620248375 - https://github.com/microsoft/terminal/issues/455#issuecomment-621533916 - https://github.com/microsoft/terminal/issues/455#issuecomment-622585453 Also see the below one with more screenshots: - https://github.com/microsoft/terminal/pull/5743#issuecomment-624940567
2020-05-08 23:09:32 +02:00
CATCH_RETURN();
[[nodiscard]] HRESULT CustomTextRenderer::_DrawGlowGlyphRun(DrawingContext* clientDrawingContext,
D2D1_POINT_2F baselineOrigin,
DWRITE_MEASURING_MODE /*measuringMode*/,
_In_ const DWRITE_GLYPH_RUN* glyphRun,
_In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* /*glyphRunDescription*/) noexcept
{
RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingContext);
RETURN_HR_IF_NULL(E_INVALIDARG, glyphRun);
// This is glow text manually
::Microsoft::WRL::ComPtr<ID2D1Factory> d2dFactory;
clientDrawingContext->renderTarget->GetFactory(d2dFactory.GetAddressOf());
::Microsoft::WRL::ComPtr<ID2D1PathGeometry> pathGeometry;
d2dFactory->CreatePathGeometry(pathGeometry.GetAddressOf());
::Microsoft::WRL::ComPtr<ID2D1GeometrySink> geometrySink;
pathGeometry->Open(geometrySink.GetAddressOf());
glyphRun->fontFace->GetGlyphRunOutline(
glyphRun->fontEmSize,
glyphRun->glyphIndices,
glyphRun->glyphAdvances,
glyphRun->glyphOffsets,
glyphRun->glyphCount,
glyphRun->isSideways,
glyphRun->bidiLevel % 2,
geometrySink.Get());
geometrySink->Close();
D2D1::Matrix3x2F const matrixAlign = D2D1::Matrix3x2F::Translation(baselineOrigin.x, baselineOrigin.y);
::Microsoft::WRL::ComPtr<ID2D1TransformedGeometry> transformedGeometry;
d2dFactory->CreateTransformedGeometry(pathGeometry.Get(),
&matrixAlign,
transformedGeometry.GetAddressOf());
::Microsoft::WRL::ComPtr<ID2D1TransformedGeometry> alignedGeometry;
d2dFactory->CreateTransformedGeometry(pathGeometry.Get(),
&matrixAlign,
alignedGeometry.GetAddressOf());
::Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> brush;
::Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> outlineBrush;
clientDrawingContext->renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White, 1.0f), brush.GetAddressOf());
clientDrawingContext->renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Red, 1.0f), outlineBrush.GetAddressOf());
clientDrawingContext->renderTarget->DrawGeometry(transformedGeometry.Get(), outlineBrush.Get(), 2.0f);
clientDrawingContext->renderTarget->FillGeometry(alignedGeometry.Get(), brush.Get());
return S_OK;
}