2626from collections import OrderedDict
2727from typing import Iterator , List , Optional , Set , Tuple , Union
2828
29- from ansi2html .style import SCHEME , get_styles
29+ from ansi2html .style import (
30+ SCHEME ,
31+ add_truecolor_style_rule ,
32+ get_styles ,
33+ pop_truecolor_styles ,
34+ )
3035
3136if sys .version_info >= (3 , 8 ):
3237 from importlib .metadata import version
5661ANSI_VISIBILITY_OFF = 8
5762ANSI_FOREGROUND_CUSTOM_MIN = 30
5863ANSI_FOREGROUND_CUSTOM_MAX = 37
59- ANSI_FOREGROUND_256 = 38
64+ ANSI_FOREGROUND = 38
6065ANSI_FOREGROUND_DEFAULT = 39
6166ANSI_BACKGROUND_CUSTOM_MIN = 40
6267ANSI_BACKGROUND_CUSTOM_MAX = 47
63- ANSI_BACKGROUND_256 = 48
68+ ANSI_BACKGROUND = 48
6469ANSI_BACKGROUND_DEFAULT = 49
6570ANSI_NEGATIVE_ON = 7
6671ANSI_NEGATIVE_OFF = 27
6772ANSI_FOREGROUND_HIGH_INTENSITY_MIN = 90
6873ANSI_FOREGROUND_HIGH_INTENSITY_MAX = 97
6974ANSI_BACKGROUND_HIGH_INTENSITY_MIN = 100
7075ANSI_BACKGROUND_HIGH_INTENSITY_MAX = 107
76+ ANSI_256_COLOR_ID = 5
77+ ANSI_TRUECOLOR_ID = 2
7178
7279VT100_BOX_CODES = {
7380 "0x71" : "─" ,
@@ -131,11 +138,11 @@ def reset(self) -> None:
131138 self .underline : int = ANSI_UNDERLINE_OFF
132139 self .crossedout : int = ANSI_CROSSED_OUT_OFF
133140 self .visibility : int = ANSI_VISIBILITY_ON
134- self .foreground : Tuple [int , Optional [int ]] = (ANSI_FOREGROUND_DEFAULT , None )
135- self .background : Tuple [int , Optional [int ]] = (ANSI_BACKGROUND_DEFAULT , None )
141+ self .foreground : Tuple [int , Optional [str ]] = (ANSI_FOREGROUND_DEFAULT , None )
142+ self .background : Tuple [int , Optional [str ]] = (ANSI_BACKGROUND_DEFAULT , None )
136143 self .negative : int = ANSI_NEGATIVE_OFF
137144
138- def adjust (self , ansi_code : int , parameter : Optional [int ] = None ) -> None :
145+ def adjust (self , ansi_code : int , parameter : Optional [str ] = None ) -> None :
139146 if ansi_code in (
140147 ANSI_INTENSITY_INCREASED ,
141148 ANSI_INTENSITY_REDUCED ,
@@ -160,7 +167,7 @@ def adjust(self, ansi_code: int, parameter: Optional[int] = None) -> None:
160167 <= ANSI_FOREGROUND_HIGH_INTENSITY_MAX
161168 ):
162169 self .foreground = (ansi_code , None )
163- elif ansi_code == ANSI_FOREGROUND_256 :
170+ elif ansi_code == ANSI_FOREGROUND :
164171 self .foreground = (ansi_code , parameter )
165172 elif ansi_code == ANSI_FOREGROUND_DEFAULT :
166173 self .foreground = (ansi_code , None )
@@ -172,13 +179,25 @@ def adjust(self, ansi_code: int, parameter: Optional[int] = None) -> None:
172179 <= ANSI_BACKGROUND_HIGH_INTENSITY_MAX
173180 ):
174181 self .background = (ansi_code , None )
175- elif ansi_code == ANSI_BACKGROUND_256 :
182+ elif ansi_code == ANSI_BACKGROUND :
176183 self .background = (ansi_code , parameter )
177184 elif ansi_code == ANSI_BACKGROUND_DEFAULT :
178185 self .background = (ansi_code , None )
179186 elif ansi_code in (ANSI_NEGATIVE_ON , ANSI_NEGATIVE_OFF ):
180187 self .negative = ansi_code
181188
189+ def adjust_truecolor (self , ansi_code : int , r : int , g : int , b : int ) -> None :
190+ parameter = "{:03d}{:03d}{:03d}" .format (
191+ r , g , b
192+ ) # r=1, g=64, b=255 -> 001064255
193+
194+ is_foreground = ansi_code == ANSI_FOREGROUND
195+ add_truecolor_style_rule (is_foreground , ansi_code , r , g , b , parameter )
196+ if is_foreground :
197+ self .foreground = (ansi_code , parameter )
198+ else :
199+ self .background = (ansi_code , parameter )
200+
182201 def to_css_classes (self ) -> List [str ]:
183202 css_classes : List [str ] = []
184203
@@ -189,7 +208,7 @@ def append_unless_default(output: List[str], value: int, default: int) -> None:
189208
190209 def append_color_unless_default (
191210 output : List [str ],
192- color : Tuple [int , Optional [int ]],
211+ color : Tuple [int , Optional [str ]],
193212 default : int ,
194213 negative : bool ,
195214 neg_css_class : str ,
@@ -198,7 +217,7 @@ def append_color_unless_default(
198217 if value != default :
199218 prefix = "inv" if negative else "ansi"
200219 css_class_index = (
201- str (value ) if (parameter is None ) else "%d-%d " % (value , parameter )
220+ str (value ) if (parameter is None ) else "%d-%s " % (value , parameter )
202221 )
203222 output .append (prefix + css_class_index )
204223 elif negative :
@@ -297,7 +316,6 @@ def __init__(
297316 self .title = title
298317 self ._attrs : Attributes
299318 self .hyperref = False
300-
301319 if inline :
302320 self .styles = dict (
303321 [
@@ -449,8 +467,14 @@ def _handle_ansi_code(
449467
450468 if v == ANSI_FULL_RESET :
451469 last_null_index = i
452- elif v in (ANSI_FOREGROUND_256 , ANSI_BACKGROUND_256 ):
453- skip_after_index = i + 2
470+ elif v in (ANSI_FOREGROUND , ANSI_BACKGROUND ):
471+ try :
472+ x_bit_color_id = params [i + 1 ]
473+ except IndexError :
474+ x_bit_color_id = - 1
475+ is_256_color = x_bit_color_id == ANSI_256_COLOR_ID
476+ shift = 2 if is_256_color else 4
477+ skip_after_index = i + shift
454478
455479 # Process reset marker, drop everything before
456480 if last_null_index is not None :
@@ -472,12 +496,28 @@ def _handle_ansi_code(
472496 if i <= skip_after_index :
473497 continue
474498
475- if v in (ANSI_FOREGROUND_256 , ANSI_BACKGROUND_256 ):
499+ is_x_bit_color = v in (ANSI_FOREGROUND , ANSI_BACKGROUND )
500+ try :
501+ x_bit_color_id = params [i + 1 ]
502+ except IndexError :
503+ x_bit_color_id = - 1
504+ is_256_color = x_bit_color_id == ANSI_256_COLOR_ID
505+ is_truecolor = x_bit_color_id == ANSI_TRUECOLOR_ID
506+ if is_x_bit_color and is_256_color :
476507 try :
477- parameter : Optional [int ] = params [i + 2 ]
508+ parameter : Optional [str ] = str ( params [i + 2 ])
478509 except IndexError :
479510 continue
480511 skip_after_index = i + 2
512+ elif is_x_bit_color and is_truecolor :
513+ try :
514+ state .adjust_truecolor (
515+ v , params [i + 2 ], params [i + 3 ], params [i + 4 ]
516+ )
517+ except IndexError :
518+ continue
519+ skip_after_index = i + 4
520+ continue
481521 else :
482522 parameter = None
483523 state .adjust (v , parameter = parameter )
@@ -495,6 +535,7 @@ def _handle_ansi_code(
495535 styles_used .update (css_classes )
496536
497537 if self .inline :
538+ self .styles .update (pop_truecolor_styles ())
498539 if self .latex :
499540 style = [
500541 self .styles [klass ].kwl [0 ][1 ]
0 commit comments