"""Single-cell styling shared by widgets and animation layers."""importrefromdataclassesimportdataclassfrom.gridimport_glyph_SGR=re.compile(r"\x1b\[([0-9;:]*)m")
[docs]@dataclass(frozen=True)classTextStyle:"""Per-cell ANSI styling; palette colors are 0..255, or None for defaults. On a Layer, None colors and bold=False leave child attributes unchanged; explicit colors and bold=True override them. Widget behavior is unchanged. """foreground:int|None=Nonebackground:int|None=Nonebold:bool=Falsedef__post_init__(self)->None:forname,valuein(("foreground",self.foreground),("background",self.background),):ifvalueisnotNone:iftype(value)isnotint:raiseTypeError(f"{name} must be an integer or None")ifnot0<=value<=255:raiseValueError(f"{name} must be between 0 and 255")iftype(self.bold)isnotbool:raiseTypeError("bold must be a bool")def_prefix(self)->str:codes=["1"]ifself.boldelse[]ifself.foregroundisnotNone:codes.append(f"38;5;{self.foreground}")ifself.backgroundisnotNone:codes.append(f"48;5;{self.background}")returnf"\x1b[{';'.join(codes)}m"ifcodeselse""def_cell(self,glyph:str)->str:prefix=self._prefix()returnf"{prefix}{glyph}\x1b[0m"ifprefixelseglyph
[docs]def_background(prefix:str)->str:"""Resolve background at the glyph, including resets and RGB/palette SGR."""background="\x1b[49m"formatchin_SGR.finditer(prefix):parts=match.group(1).split(";")index=0whileindex<len(parts):part=parts[index]if":"inpart:code=int(part.split(":",1)[0]or0)ifcode==48:background=f"\x1b[{part}m"else:code=int(partor0)ifcodein(38,48,58)andindex+1<len(parts):length={"5":3,"2":5}.get(parts[index+1],1)ifcode==48andindex+length<=len(parts):background=f"\x1b[{';'.join(parts[index:index+length])}m"index+=lengthcontinueifcodein(0,49):background="\x1b[49m"elif40<=code<=47or100<=code<=107:background=f"\x1b[{code}m"index+=1returnbackground