Fix underline and half-bright being discarded where plain char is unsigned - #1
Open
kay-ws wants to merge 1 commit into
Open
Fix underline and half-bright being discarded where plain char is unsigned#1kay-ws wants to merge 1 commit into
kay-ws wants to merge 1 commit into
Conversation
…nsigned
s8 is a typedef for plain char, whose signedness is implementation-defined.
The Arm procedure call standard maps char to "unsigned byte" (AAPCS32 section
8.1.1, "LDRB is unsigned"), so the -1 sentinel stored in cur_underline_color
and cur_halfbright_color reads back as 255 there and the guards in
normal_char_attr() are true unconditionally:
if (a.underline && cur_underline_color != -1) { // 255 != -1
a.underline = false;
a.fcolor = cur_underline_color; // fg forced to 255
}
Underline (SGR 4) is therefore dropped and the cell is drawn with color index
255, a near-white gray on the default palette, so the failure is easy to
overlook: the text looks almost normal, it just loses the decoration. The
half-bright branch below it has the same defect and replaces the intended
gray with color 255.
The guards predate the decoration rendering added in ccf83cf; before that
commit fbterm never drew underlines, so the bug had no observable effect.
The typedef itself cannot be changed to signed char, because s8 is also the
string type used throughout the tree and signed char * is not assignable from
a string literal in C++. -fsigned-char would work too, but it also changes the
interpretation of every s8 buffer holding UTF-8 bytes, so widening the two
fields seemed the smaller change. They only ever hold -1 to 7.
Verified on armv7l (Debian 12).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SGR 4(underline) andSGR 2(faint) are silently discarded on targets whereplain
charis unsigned: the terminal takes the "represent the decoration as acolor instead" path unconditionally, even though no such color was ever
requested. The substituted index is 255 —
rgb(238, 238, 238)indefaultPalette, against a normal foreground of index 7,rgb(170, 170, 170)— so the text merely comes out slightly brighter and the failure is easy to
overlook.
What the guards are for
The VGA text console cannot draw an underline, so the Linux console represents
underlined cells as a color change instead.
console_codes(4), under LinuxConsole Private CSI Sequences:
VTerm::linux_specific()implements both, andnormal_char_attr()applies thesubstitution: where a color has been requested, the decoration bit is cleared
and the foreground is replaced.
-1means no substitution requested — draw areal underline, which fbterm can do because it is not limited to VGA text mode.
Why the sentinel does not survive
src/lib/vterm.hstores that-1ins8, whichsrc/lib/type.hdefines asplain
char, whose signedness is implementation-defined. GCC's documentationputs it this way:
For Arm that document is the AAPCS, whose §8.1.1 Arithmetic Types maps
charto unsigned byte, with the note "LDRB is unsigned".VTerm::reset()sets both fields to-1, which on such a target reads back as255, so the guards in
VTerm::normal_char_attr()are true unconditionally:The guards themselves are older than the decoration rendering added in
ccf83cf; before that commit fbterm never drew underlines, so the broken
comparison had no observable effect.
Confirmed on the target with a two-line program:
About the fix
Changing the typedef to
signed charis not an option:s8is also the stringtype used throughout the tree, and
signed char *is not assignable from astring literal in C++.
-fsigned-charwould work and would remove the wholeclass of latent
s8sign bugs, but it also flips the interpretation of everys8buffer holding UTF-8 bytes, so this patch widens the two fields instead —they only ever hold
-1to7. If you would rather take the compiler flag,that decision belongs to you and this commit can be dropped.
Reproducing
Before, on an unsigned-
chartarget:UNDERLINEhas no underline andFAINTis brighter than surrounding text rather than dimmer;
STRIKErenderscorrectly, since strikethrough has no such substitution. After, all three
render as expected.
That contrast doubles as a discriminator when debugging this area —
strikethrough and underline are emitted by the same
fillRect()call indrawGlyph()and differ only in the y offset, so "strikethrough renders butunderline does not" points at the attribute rather than the geometry.
Verification
underline is drawn at all; after it, ASCII and double-width cells, colored
underlines and
SGR 2all render correctly.charis signed, draw theunderline correctly with and without this commit — which is what the analysis
above predicts.
SGR 21(double underline) remains unimplemented; this patch does notchange that.
I have a shell script covering the underline paths and a larger one covering
the 256-color and direct-color output, if a
tests/entry would be useful.