terminal/src/renderer/vt/Xterm256Engine.cpp
James Holderness 90ff261c35
Add support for downloadable soft fonts (#10011)
This PR adds conhost support for downloadable soft fonts - also known as
dynamically redefinable character sets (DRCS) - using the `DECDLD`
escape sequence.

These fonts are typically designed to work on a specific terminal model,
and each model tends to have a different character cell size. So in
order to support as many models as possible, the code attempts to detect
the original target size of the font, and then scale the glyphs to fit
our current cell size.

Once a font has been downloaded to the terminal, it can be designated in
the same way you would a standard character set, using an `SCS` escape
sequence. The identification string for the set is defined by the
`DECDLD` sequence. Internally we map the characters in this set to code
points `U+EF20` to `U+EF7F` in the Unicode private use are (PUA).

Then in the renderer, any characters in that range are split off into
separate runs, which get painted with a special font. The font itself is
dynamically generated as an in-memory resource, constructed from the
downloaded character bitmaps which have been scaled to the appropriate
size.

If no soft fonts are in use, then no mapping of the PUA code points will
take place, so this shouldn't interfere with anyone using those code
points for something else, as along as they aren't also trying to use
soft fonts. I also tried to pick a PUA range that hadn't already been
snatched up by Nerd Fonts, but if we do receive reports of a conflict,
it's easy enough to change.

## Validation Steps Performed

I added an adapter test that runs through a bunch of parameter
variations for the `DECDLD` sequence, to make sure we're correctly
detecting the font sizes for most of the known DEC terminal models.

I've also tested manually on a wide range of existing fonts, of varying
dimensions, and from multiple sources, and made sure they all worked
reasonably well.

Closes #9164
2021-08-06 20:41:02 +00:00

177 lines
7.3 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "Xterm256Engine.hpp"
#pragma hdrstop
using namespace Microsoft::Console;
using namespace Microsoft::Console::Render;
using namespace Microsoft::Console::Types;
Xterm256Engine::Xterm256Engine(_In_ wil::unique_hfile hPipe,
const Viewport initialViewport) :
XtermEngine(std::move(hPipe), initialViewport, false)
{
}
// Routine Description:
// - Write a VT sequence to change the current colors of text. Writes true RGB
// color sequences.
// Arguments:
// - textAttributes - Text attributes to use for the colors and character rendition
// - pData - The interface to console data structures required for rendering
// - usingSoftFont - Whether we're rendering characters from a soft font
// - isSettingDefaultBrushes: indicates if we should change the background color of
// the window. Unused for VT
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT Xterm256Engine::UpdateDrawingBrushes(const TextAttribute& textAttributes,
const gsl::not_null<IRenderData*> pData,
const bool /*usingSoftFont*/,
const bool /*isSettingDefaultBrushes*/) noexcept
{
RETURN_IF_FAILED(VtEngine::_RgbUpdateDrawingBrushes(textAttributes));
RETURN_IF_FAILED(_UpdateHyperlinkAttr(textAttributes, pData));
// Only do extended attributes in xterm-256color, as to not break telnet.exe.
return _UpdateExtendedAttrs(textAttributes);
}
// Routine Description:
// - Write a VT sequence to update the character rendition attributes.
// Arguments:
// - textAttributes - text attributes (bold, italic, underline, etc.) to use.
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT Xterm256Engine::_UpdateExtendedAttrs(const TextAttribute& textAttributes) noexcept
{
// Turning off Bold and Faint must be handled at the same time,
// since there is only one sequence that resets both of them.
const auto boldTurnedOff = !textAttributes.IsBold() && _lastTextAttributes.IsBold();
const auto faintTurnedOff = !textAttributes.IsFaint() && _lastTextAttributes.IsFaint();
if (boldTurnedOff || faintTurnedOff)
{
RETURN_IF_FAILED(_SetBold(false));
_lastTextAttributes.SetBold(false);
_lastTextAttributes.SetFaint(false);
}
// Once we've handled the cases where they need to be turned off,
// we can then check if either should be turned back on again.
if (textAttributes.IsBold() && !_lastTextAttributes.IsBold())
{
RETURN_IF_FAILED(_SetBold(true));
_lastTextAttributes.SetBold(true);
}
if (textAttributes.IsFaint() && !_lastTextAttributes.IsFaint())
{
RETURN_IF_FAILED(_SetFaint(true));
_lastTextAttributes.SetFaint(true);
}
// Turning off the underline styles must be handled at the same time,
// since there is only one sequence that resets both of them.
const auto singleTurnedOff = !textAttributes.IsUnderlined() && _lastTextAttributes.IsUnderlined();
const auto doubleTurnedOff = !textAttributes.IsDoublyUnderlined() && _lastTextAttributes.IsDoublyUnderlined();
if (singleTurnedOff || doubleTurnedOff)
{
RETURN_IF_FAILED(_SetUnderlined(false));
_lastTextAttributes.SetUnderlined(false);
_lastTextAttributes.SetDoublyUnderlined(false);
}
// Once we've handled the cases where they need to be turned off,
// we can then check if either should be turned back on again.
if (textAttributes.IsUnderlined() && !_lastTextAttributes.IsUnderlined())
{
RETURN_IF_FAILED(_SetUnderlined(true));
_lastTextAttributes.SetUnderlined(true);
}
if (textAttributes.IsDoublyUnderlined() && !_lastTextAttributes.IsDoublyUnderlined())
{
RETURN_IF_FAILED(_SetDoublyUnderlined(true));
_lastTextAttributes.SetDoublyUnderlined(true);
}
if (textAttributes.IsOverlined() != _lastTextAttributes.IsOverlined())
{
RETURN_IF_FAILED(_SetOverlined(textAttributes.IsOverlined()));
_lastTextAttributes.SetOverlined(textAttributes.IsOverlined());
}
if (textAttributes.IsItalic() != _lastTextAttributes.IsItalic())
{
RETURN_IF_FAILED(_SetItalic(textAttributes.IsItalic()));
_lastTextAttributes.SetItalic(textAttributes.IsItalic());
}
if (textAttributes.IsBlinking() != _lastTextAttributes.IsBlinking())
{
RETURN_IF_FAILED(_SetBlinking(textAttributes.IsBlinking()));
_lastTextAttributes.SetBlinking(textAttributes.IsBlinking());
}
if (textAttributes.IsInvisible() != _lastTextAttributes.IsInvisible())
{
RETURN_IF_FAILED(_SetInvisible(textAttributes.IsInvisible()));
_lastTextAttributes.SetInvisible(textAttributes.IsInvisible());
}
if (textAttributes.IsCrossedOut() != _lastTextAttributes.IsCrossedOut())
{
RETURN_IF_FAILED(_SetCrossedOut(textAttributes.IsCrossedOut()));
_lastTextAttributes.SetCrossedOut(textAttributes.IsCrossedOut());
}
if (textAttributes.IsReverseVideo() != _lastTextAttributes.IsReverseVideo())
{
RETURN_IF_FAILED(_SetReverseVideo(textAttributes.IsReverseVideo()));
_lastTextAttributes.SetReverseVideo(textAttributes.IsReverseVideo());
}
return S_OK;
}
// Routine Description:
// - Write a VT sequence to start/stop a hyperlink
// Arguments:
// - textAttributes - Text attributes to use for the hyperlink ID
// - pData - The interface to console data structures required for rendering
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
HRESULT Microsoft::Console::Render::Xterm256Engine::_UpdateHyperlinkAttr(const TextAttribute& textAttributes,
const gsl::not_null<IRenderData*> pData) noexcept
{
if (textAttributes.GetHyperlinkId() != _lastTextAttributes.GetHyperlinkId())
{
if (textAttributes.IsHyperlink())
{
const auto id = textAttributes.GetHyperlinkId();
const auto customId = pData->GetHyperlinkCustomId(id);
RETURN_IF_FAILED(_SetHyperlink(pData->GetHyperlinkUri(id), customId, id));
}
else
{
RETURN_IF_FAILED(_EndHyperlink());
}
_lastTextAttributes.SetHyperlinkId(textAttributes.GetHyperlinkId());
}
return S_OK;
}
// Method Description:
// - Manually emit a "Erase Scrollback" sequence to the connected terminal. We
// need to do this in certain cases that we've identified where we believe the
// client wanted the entire terminal buffer cleared, not just the viewport.
// For more information, see GH#3126.
// Arguments:
// - <none>
// Return Value:
// - S_OK if we wrote the sequences successfully, otherwise an appropriate HRESULT
[[nodiscard]] HRESULT Xterm256Engine::ManuallyClearScrollback() noexcept
{
return _ClearScrollback();
}