The Wayback Machine - https://web.archive.org/web/20210328063108/https://github.com/microsoft/terminal/pull/8580
Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add support for the "italic" graphic rendition attribute #8580

Merged
merged 4 commits into from Dec 18, 2020

Conversation

@j4james
Copy link
Contributor

@j4james j4james commented Dec 13, 2020

This PR adds support for the ANSI italic graphic rendition attribute,
which is enabled by the SGR 3 escape sequence.

For the GDI renderer, I've just created an additional italic variant of
the font, and then the UpdateDrawingBrushes method selects the
appropriate font variant into the device context based on the requested
text attributes.

It's a bit more complicated in the DX renderer, because we need both an
italic variant of the font, and a variant of the text format object. The
CustomTextLayout class also had to be updated to hold the two font and
format instances, and decide which of the variants to use based on a
useItalicFont property in the drawing context, initially set in the
UpdateDrawingBrushes method.

Validation Steps Performed

I've created some test content using a range of different character sets
(e.g. CJK, block characters, emoji, etc.), then applied the italic
attribute mixed with various other SGR attributes to see how they
interact. The output isn't always perfect, but I think it seems
reasonable given the constraints of a cell-based terminal renderer.

Closes #5461

@j4james j4james force-pushed the j4james:feature-sgr-italic branch from d5e2966 to 966f748 Dec 14, 2020
@DHowett
Copy link
Member

@DHowett DHowett commented Dec 14, 2020

I've had it on my TODO list for a long time to implement "cascading FontInfo", where a Renderer consumer could set up a group of fontinfos and have attribute-specific overrides only to meaningful properties (like, "italic" is "override nothing except this flag in the font lookup"; bold is "set face name to Xxx Bold, keep size and slant"). It's always conceptually been a blocker for me to have actual styling... but look, here it is and working today, and I'm not going to stand in the way of progress and hold out for some "ideal world" where our designs are perfect and community-ready at all times.

That's a lot of words for me to say to explain why I'm 100%, or maybe 150%, okay with this... so that folks on the team who know how I felt about FontInfo shouldn't be worried about how I'll feel. 😀

@j4james
Copy link
Contributor Author

@j4james j4james commented Dec 14, 2020

I'm was just going to say this is probably not a great approach, and I don't really know what I'm doing on the DX side of things, but I thought it was at least worth offering as a PR. If nothing else it could serve as a temporary solution until you have something better.

@miniksa
Copy link
Member

@miniksa miniksa commented Dec 14, 2020

I'm was just going to say this is probably not a great approach, and I don't really know what I'm doing on the DX side of things, but I thought it was at least worth offering as a PR. If nothing else it could serve as a temporary solution until you have something better.

The DX side of things is close with one observation that wouldn't necessarily be immediately apparent: A CustomTextLayout is a rather expensive object to create/maintain with all the vectors that it holds and maintains between calls for caching reasons. Having two of them will consume a lot more memory for a long period of time.

I would greatly prefer if both reg/italic font/formats were pushed into the CustomTextLayout on instantiation. Then at least we're not making all the vector buffers twice.

Whether or not it's italic at the moment could be stored in the DrawingContext like the fg/bg color data and you can flip that from the outside before writing a run to tell the CustomTextLayout as to which font style to use as it has a ref to that DrawingContext. Inside the CustomTextLayout, we can just key on that part of the DrawingContext to choose one or the other during layout and render steps.

@j4james
Copy link
Contributor Author

@j4james j4james commented Dec 14, 2020

I would greatly prefer if both reg/italic font/formats were pushed into the CustomTextLayout on instantiation. Then at least we're not making all the vector buffers twice.

OK. That sounds good. I'll give it a try.

_hfont((HFONT)INVALID_HANDLE_VALUE)
_hfont(nullptr),
Comment on lines -33 to +34

This comment has been minimized.

@j4james

j4james Dec 17, 2020
Author Contributor

Note that this was a deliberate change - the correct error value for a font is null rather than INVALID_HANDLE_VALUE. This was previously generating error logs when the font got deleted.

This comment has been minimized.

@miniksa

miniksa Dec 17, 2020
Member

Woopsie. Thanks.

This comment has been minimized.

@DHowett

DHowett Dec 18, 2020
Member

Great catch!

@j4james
Copy link
Contributor Author

@j4james j4james commented Dec 17, 2020

The more I look at the DX renderer, the less I feel I know what's going on, but I think I've got something working now with a single CustomTextLayout instance.

My one concern is that I'm not sure the block characters are handled correctly when italic. On the plus side, they have less render artifacts than the non-italic variant, but I think they're more likely to have gaps between blocks in some situations. That said, I wouldn't expect people to be using block characters in italics anyway, so I don't think it's a big deal.

But that brings me to another issue: quite a few fonts that I've tested actually render the block characters at an angle when italic, which doesn't really make any sense. The line characters don't connect vertically, and the blocks get sort of chopped off at the edges. Technically I think this is the font's responsibility, but it is probably something we could fix (at least in the DX renderer) if we really wanted to. In practice I think it's unlikely to be a problem though.

@j4james j4james marked this pull request as ready for review Dec 17, 2020
@j4james
Copy link
Contributor Author

@j4james j4james commented Dec 17, 2020

Btw here's a screenshot of my test pattern using the Cascadia Code font. You can see some of the issues I mentioned in the block character section.

image

@miniksa
Copy link
Member

@miniksa miniksa commented Dec 17, 2020

The more I look at the DX renderer, the less I feel I know what's going on, but I think I've got something working now with a single CustomTextLayout instance.

I'll take a look.

My one concern is that I'm not sure the block characters are handled correctly when italic. On the plus side, they have less render artifacts than the non-italic variant, but I think they're more likely to have gaps between blocks in some situations. That said, I wouldn't expect people to be using block characters in italics anyway, so I don't think it's a big deal.

But that brings me to another issue: quite a few fonts that I've tested actually render the block characters at an angle when italic, which doesn't really make any sense. The line characters don't connect vertically, and the blocks get sort of chopped off at the edges. Technically I think this is the font's responsibility, but it is probably something we could fix (at least in the DX renderer) if we really wanted to. In practice I think it's unlikely to be a problem though.

I agree.

Btw here's a screenshot of my test pattern using the Cascadia Code font. You can see some of the issues I mentioned in the block character section.

Thanks. It looks close enough for now. We can revisit block drawing in a different bug, not as a part of implementing italics if it becomes a big enough problem.

Copy link
Member

@miniksa miniksa left a comment

Yeah that's more or less what I meant by the comment a few days ago. Looks pretty fine to me. Just the one outstanding discussion point on the bare vs. Com pointer. I think @DHowett will have an opinion or an insight that will help clear that one up and then we can approve and get this in.

@@ -1226,7 +1242,7 @@ CATCH_RETURN();
{
// Get the font fallback first
::Microsoft::WRL::ComPtr<IDWriteTextFormat1> format1;
if (FAILED(_format.As(&format1)))
if (FAILED(::Microsoft::WRL::ComPtr<IDWriteTextFormat>(_formatInUse).As(&format1)))

This comment has been minimized.

@miniksa

miniksa Dec 17, 2020
Member

I thought this strange, then I realized you're storing the InUse ones as a bare pointer, instead of as a ComPtr. Given the ComPtr is just a refcount anyway... should we maybe just store it that way so there are 2 refs outstanding for holding it twice in our class? I don't think that should be THAT harmful to performance overall.

This comment has been minimized.

@j4james

j4james Dec 17, 2020
Author Contributor

Yeah I went back and forth with this a few times, but settled on the bare pointer in the end because it felt a bit wasteful having that extra ref counting for something that's entirely internal to the class. I wouldn't object to going back to a ComPtr if that's what you prefer though. Just let me know.

This comment has been minimized.

@DHowett

DHowett Dec 18, 2020
Member

The lifetime is held by other member variables. I am comfortable with a bare pointer here.

Anyway, it will be instantly promoted to an owning reference (of TextFormat1) here as needed.

This comment has been minimized.

@DHowett

DHowett Dec 18, 2020
Member

If you want to make this a little "clearer" (clearer to a COM person; instead of using a constructor call with _formatInUse), you can do this:

if (FAILED(_formatInUse->QueryInterface(IID_PPV_ARGS(&format1))))

IID_PPV_ARGS is a macro that takes a pointer to a COM thing and transforms it into an interface ID for that pointed-to type + the type as an argument pack.

the & operator on ComPtr acts roughly as expected of COM things.

QueryInterface returns a positive reference counted object into the pointed-to location so it's consistent with what WRL needs.

This comment has been minimized.

@miniksa

miniksa Dec 18, 2020
Member

OK I'm good with what Dustin says then. Bare pointer is fine with me and you can optionally make it "clearer to a COM person" per his suggestion.

_hfont((HFONT)INVALID_HANDLE_VALUE)
_hfont(nullptr),

This comment has been minimized.

@miniksa

miniksa Dec 17, 2020
Member

Woopsie. Thanks.

Copy link
Member

@DHowett DHowett left a comment

Totally comfortable with this. Minor readability nit (though whether you consider IID_PPV_ARGS more or less readable is rather up to you!)

@@ -1226,7 +1242,7 @@ CATCH_RETURN();
{
// Get the font fallback first
::Microsoft::WRL::ComPtr<IDWriteTextFormat1> format1;
if (FAILED(_format.As(&format1)))
if (FAILED(::Microsoft::WRL::ComPtr<IDWriteTextFormat>(_formatInUse).As(&format1)))

This comment has been minimized.

@DHowett

DHowett Dec 18, 2020
Member

If you want to make this a little "clearer" (clearer to a COM person; instead of using a constructor call with _formatInUse), you can do this:

if (FAILED(_formatInUse->QueryInterface(IID_PPV_ARGS(&format1))))

IID_PPV_ARGS is a macro that takes a pointer to a COM thing and transforms it into an interface ID for that pointed-to type + the type as an argument pack.

the & operator on ComPtr acts roughly as expected of COM things.

QueryInterface returns a positive reference counted object into the pointed-to location so it's consistent with what WRL needs.

_hfont((HFONT)INVALID_HANDLE_VALUE)
_hfont(nullptr),

This comment has been minimized.

@DHowett

DHowett Dec 18, 2020
Member

Great catch!

@@ -300,6 +322,16 @@ GdiEngine::~GdiEngine()
// Save the font.
_hfont = hFont.release();

This comment has been minimized.

@DHowett

DHowett Dec 18, 2020
Member

it is hilarious that we used the smart pointer types for all of the 8 seconds required to ... decapsulate them and store them as raw handles in our class. LOL.

This comment has been minimized.

@miniksa

miniksa Dec 18, 2020
Member

Yeah, I mean. I'm sure I had good intentions like "it'll be released properly if one of the interim steps fails and the RETURN_IF* macro fires. But it is pretty silly. The class should probably have some smart pointers too... future fix.

Copy link
Member

@miniksa miniksa left a comment

I'm good. James, if you want to fix the readability nit that Dustin called out, go for it. I'll wait a bit to merge this in case you want that chance.

@j4james
Copy link
Contributor Author

@j4james j4james commented Dec 18, 2020

I was planning to make the change, but I'm still at work so won't be able to do anything until a little later tonight. I don't mind if you want to merge though.

@mdtauk
Copy link

@mdtauk mdtauk commented Dec 18, 2020

Will there be a Faux Italic as well as a Faux Bold option when the chosen font does not include those varients?

@DHowett
Copy link
Member

@DHowett DHowett commented Dec 18, 2020

Will there be a Faux Italic as well as a Faux Bold option when the chosen font does not include those varients?

@mdtauk This is not intended to be configurable right now. If DirectWrite or GDI chooses to make a faux-italic variant of a font, that is its right.

Cascadia doesn't have an italic variant, so the one pictured above is faux-italic.

@DHowett
Copy link
Member

@DHowett DHowett commented Dec 18, 2020

@j4james if that's the only change, I'll make it on your branch & merge. 😄 Thanks

@mdtauk
Copy link

@mdtauk mdtauk commented Dec 18, 2020

Will there be a Faux Italic as well as a Faux Bold option when the chosen font does not include those varients?

@mdtauk This is not intended to be configurable right now. If DirectWrite or GDI chooses to make a faux-italic variant of a font, that is its right.

Cascadia doesn't have an italic variant, so the one pictured above is faux-italic.

So this is something handled by DirectWrite then, fair enough.

@DHowett DHowett added the AutoMerge label Dec 18, 2020
@msftbot
Copy link
Contributor

@msftbot msftbot bot commented Dec 18, 2020

Hello @DHowett!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.
@DHowett
Copy link
Member

@DHowett DHowett commented Dec 18, 2020

Hmm. Our agents are on the floor.

@DHowett DHowett merged commit fc7b052 into microsoft:main Dec 18, 2020
3 of 7 checks passed
3 of 7 checks passed
Terminal CI Build #0.0.2012.1813 was canceled
Details
Terminal CI (Audit Mode Static Analysis Build x64) Audit Mode Static Analysis Build x64 was canceled
Details
Terminal CI (Build x64 Build x64 Release) Build x64 Build x64 Release was canceled
Details
Terminal CI (Build x86 Build x86 Release) Build x86 Build x86 Release was canceled
Details
Terminal CI (Code Health Scripts Proper Code Formatting Check) Code Health Scripts Proper Code Formatting Check succeeded
Details
auto-merge.config.enforce No dynamic merge policies are applicable.
license/cla All CLA requirements met.
Details
@j4james j4james deleted the j4james:feature-sgr-italic branch Dec 19, 2020
mpela81 added a commit to mpela81/terminal that referenced this pull request Jan 28, 2021
)

This PR adds support for the ANSI _italic_ graphic rendition attribute,
which is enabled by the `SGR 3` escape sequence.

For the GDI renderer, I've just created an additional italic variant of
the font, and then the `UpdateDrawingBrushes` method selects the
appropriate font variant into the device context based on the requested
text attributes.

It's a bit more complicated in the DX renderer, because we need both an
italic variant of the font, and a variant of the text format object. The
`CustomTextLayout` class also had to be updated to hold the two font and
format instances, and decide which of the variants to use based on a
`useItalicFont` property in the drawing context, initially set in the
`UpdateDrawingBrushes` method.

## Validation Steps Performed
I've created some test content using a range of different character sets
(e.g. CJK, block characters, emoji, etc.), then applied the italic
attribute mixed with various other SGR attributes to see how they
interact. The output isn't always perfect, but I think it seems
reasonable given the constraints of a cell-based terminal renderer.

Closes microsoft#5461
msftbot bot pushed a commit that referenced this pull request Feb 18, 2021
This is my attempt to isolate all the dwrite font related thing by
introducing a new layer - `DxFontRenderData`. This will free
`DxRenderer` & `CustomTextLayout` from the burden of handling fonts &
box effects. The logic is more simplified & streamlined.

In short I just moved everything fonts-related into `DxFontRenderData`
and started from there. There's no modification to code logic. Just pure
structural stuff.

SGR support tracking issue: #6879
Initial Italic support PR: #8580
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment