def __init__(self, default_fg, default_bg, **kwargs): super().__init__(**kwargs) # These are ordinary instance parameters, but are used as the default # source for components for "DEFAULT_FG" and "DEFAULT_BG" colors # for all non-terminal backends. self._default_fg = Color(default_fg) self._default_bg = Color(default_bg)
def __init__(self, attributes=None, pop_attributes=None, moveto=None, rmoveto=None, color=None, foreground=None, background=None, effects=None, direction=None, transformer=None, new_line=None): self.attributes = attributes or {} if isinstance(pop_attributes, (set, Sequence)): pop_attributes = {name: None for name in pop_attributes} self.pop_attributes = pop_attributes self.moveto = moveto self.rmoveto = rmoveto for parameter in "color foreground background effects direction transformer new_line".split(): if locals()[parameter] is None: continue value = locals()[parameter] if parameter == "color": parameter = "foreground" if parameter == "transformer": parameter = "pretransformer" if parameter in ("foreground", "background") and not isinstance(value, Color): value = Color(value) if parameter == "effects" and not (isinstance(value, Effects) or value is TRANSPARENT): sep = "," if "," in value else "|" if "|" in value else " " effects = [e.strip() for e in value.split(sep) if e] value = Effects.none for effect in effects: value |= Effects.__members__[effect.lower()] if parameter == "direction" and isinstance(value, str): value = V2(value) self.attributes[parameter] = value self.affects_text_flow = bool(self.moveto or self.rmoveto or self.attributes.get("direction") or new_line)
def __init__(self): self.active_unicode_effects = Effects.none self.__class__.last_pos = V2(0, 0) self.next_pos = V2(0, 0) self.current_foreground = None self.current_background = None self.current_effects = None self.next_foreground = Color((0, 0, 0)) self.next_background = Color((255, 255, 255)) self.next_effects = Effects.none self.tag_is_open = False
def set_bg_color(self, color, file=None): """Writes ANSI sequence to set the background color color: RGB 3-sequence (0.0-1.0 or 0-255 range) or color constant """ if color == DEFAULT_BG: self.SGR(49, file=file) else: self.SGR(48, 2, *Color(color), file=file)
def test_color_name_is_reset_on_any_change(): c = Color("red") assert c.name == "red" c.green = 255 assert not c.name c = Color("red") c[1] = 1.0 assert not c.name c = Color("red") c.components = (0, 1, 0) assert not c.name
def test_can_set_color_component_by_name(): c = Color("red") c.green = 255 assert c.components == (255, 255, 0) c = Color("red") c.green = 1.0 assert c.components == (255, 255, 0)
def test_setting_hsv_component_works(): c1 = Color("red") c1.hue = 0.5 assert c1.components == (0, 255, 255) c1 = Color("red") c1.saturation = 0.5 assert c1.components == (255, 127, 127)
def __setitem__(self, pos, value): """ Values set for each pixel are 3-sequences with an RGB color value """ pos = V2(pos) self.dirty_mark_pixel(pos) color = value if isinstance(value, Pixel): v, color = value.get_values(self.context, self.PixelCls.capabilities) elif isinstance(value, (int, tuple, Color)): color = Color(value) elif isinstance(value, str): color = self.context.color if value != EMPTY else self.context.background self._raw_setitem(pos, color)
def __getitem__(self, pos): """Values for each pixel are: character, fg_color, bg_color, effects. """ v = super().__getitem__(pos) if v: return v char = self.get_raw(pos) value = bool(char != EMPTY) # TODO: Legacy: when this class doubled as "BooleanShape". # (remove comment block when BooleanShape is implemented) # if self.color_map: # foreground_arg = (self.color_map.get(char, DEFAULT_FG),) # else: # foreground_arg = () foreground_arg = self.color_map.get(char, CONTEXT_COLORS) if not isinstance(foreground_arg, Color): foreground_arg = Color(foreground_arg) return self.PixelCls(value, foreground_arg)
def test_subtracting_colors_dont_underflow(): c = Color((200, 0, 0)) c -= Color((255, 255, 0)) assert c.components == (0, 0, 0)
def test_can_sub_colors(): c = Color("red") c -= Color((255, 0, 0)) assert c.components == (0, 0, 0)
def test_color_by_name_works(): c = Color("red") assert "red" in repr(c) assert tuple(c.components) == (255, 0, 0)
def test_can_set_color_component_by_index(): c = Color("red") c[1] = 255 assert c.components == (255, 255, 0)
def set_bg_color(self, color, file=None): """ """ self.next_background = Color(color)
def test_html_rendering_of_color(): c = Color((255, 128, 0)) assert c.html == "#FF8000"
def test_normalized_color_works(): c = Color("red") assert tuple(c.components) == (255, 0, 0) assert c.normalized == (1.0, 0, 0)
def default_bg(self, value): from terminedia.values import DEFAULT_BG if value is DEFAULT_BG: raise ValueError("The source for default_bg can't be set as DEFAULT_BG") self._default_bg = Color(value)
def print(self, *texts, pos=None, context=None, color=None, foreground=None, background=None, effects=None, file=None, flush=False, sep=" ", end="\n" ): """Method to print a straightforward rich-text string to the terminal Params: *texts: List[str]: strings to print pos: Optional[Tuple[int, int]]: Terminal position to print to context: Optional[terminedia.Context instance] color: Union[terminedia.Color, str, Tuple[int, int, int], Tuple[float, float, float]] : foreground color to use foreground: aliased to color background: Union[terminedia.Color, str, Tuple[int, int, int], Tuple[float, float, float]] : background color to use effects: terminedia.Effects : effect or effect combination to apply to characters before printing file, flush, sep, end: The same as standard Python's `print` """ from terminedia.text.style import MLTokenizer from terminedia import shape import os if not context: context = active_context.get() original_attributes = (context.color, context.background, context.effects) color = foreground or color color = Color(color) if color is not None else context.color background = Color(background) if background is not None else context.background if effects is not None: # if an empty string is given, we have to use Effects.none effects = Effects(effects) if effects else Effects.none else: effects = context.effects self.set_colors(color, background, effects, file=file) if self.active_unicode_effects: texts = [self.apply_unicode_effects(text) if text[0] != ESC else text for text in texts] if pos: self.__class__.last_pos = None self.moveto(pos, file=file) first_text = True for text in texts: if not first_text: self._print(sep, file=file, end="") if not isinstance(text, str): text = str(text) tokenized = MLTokenizer(text) tokenized.parse() if not tokenized.mark_sequence: self._print(text, file=file, flush=flush, sep=sep, end=end) else: try: size = os.get_terminal_size() except OSError: size = 80, 25 sh = shape(size) sh.text[1][0,0] = text output = sh.render() self._print(output, file=file, flush=flush, sep=sep, end="") first_text = False self._print(end, file=file, flush=flush, end="") self.set_colors(*original_attributes, file=file)
def test_color_by_float_works(): c = Color((1.0, 0, 0)) assert tuple(c.components) == (255, 0, 0)
def test_colors_isclose(): a = Color("black") assert a.isclose((0, 2, 2)) assert not a.isclose((0, 2, 4)) assert a.isclose((0, 2, 4), abs_tol=10)
def test_can_add_colors_to_sequences(): c = Color("red") c += [0, 1.0, 0] assert c.components == (255, 255, 0)
def test_can_add_colors(): c = Color("red") c += Color((0, 255, 0)) assert c.components == (255, 255, 0)
def test_can_get_color_component_by_name(): c = Color("red") assert c.red == 255 and c.green == 0 and c.blue == 0
def set_fg_color(self, color, file=None): """ """ self.next_foreground = Color(color)
def char(self, char, foreground): if not isinstance(foreground, Color): foreground = Color(foreground) if (foreground.value >= self.threshold) ^ self.invert: return char return values.EMPTY
def test_color_by_hex6_works(): c = Color("#ff0000") assert tuple(c.components) == (255, 0, 0) c = Color("#FF0000") assert tuple(c.components) == (255, 0, 0)
def test_adding_colors_dont_overflow(): c = Color((200, 0, 0)) c += Color((200, 255, 0)) assert c.components == (255, 255, 0)
def _tokens_to_marks(self, raw_tokens): from terminedia.transformers import library as transformers_library from terminedia import Effects, Color, Directions, DEFAULT_BG, DEFAULT_FG, TRANSPARENT self.mark_sequence = {} # Separate stack to anottate the length of the affected string inside each Transformer transformer_stack = [] last_offset = -1 offset_repeat_counter = -1 for offset, token in raw_tokens: if offset == last_offset: offset_repeat_counter += 1 else: offset_repeat_counter = 0 last_offset = offset attributes = None pop_attributes = None rmoveto = None moveto = None token = token.lower().strip() if not "transformer" in token else token.strip() if token.startswith("/"): starting_tag= False token = token[1:] else: starting_tag = True if ":" in token: action, value = [v.strip() for v in token.split(":")] action = action.lower() else: action = token.strip() value = None if action in {"left", "right", "up", "down"}: value = action action = "direction" if action == "effect": action = "effects" if action not in ("transformer", "font", "char") and value: value = value.lower() # Allow for special color values: if action in ("color", "foreground") and value == "default": value = DEFAULT_FG if action == "background" and value == "default": value = DEFAULT_BG if value == "transparent" and action in {"effects", "color", "foreground", "background"}: value = TRANSPARENT if value and isinstance(value, str) and value.count(",") == 2 and action in {"color", "foreground", "background"}: value = ast.literal_eval(value) if action == "transformer": action = "pretransformer" if starting_tag: transformer_stack.append((value, offset, offset_repeat_counter)) else: closing_transformer, oppening_offset, oppening_repeat = transformer_stack.pop() if not " " in closing_transformer: # if there is a space, assume the spam of the transformer is given # on the opening tag and do nothing. spam = offset - oppening_offset closing_mark = self.mark_sequence[oppening_offset] if isinstance(closing_mark, list): closing_mark = closing_mark[oppening_repeat] closing_mark.attributes["pretransformer"] += f" {spam}" attribute_names = {"effects", "color", "foreground", "background", "direction", "pretransformer", "char", "font", } if action in attribute_names: if starting_tag: attributes = { action: ( Color(value) if action in ("color", "foreground", "background") else sum(Effects.__members__.get(v.strip(), 0) for v in value.split("|")) if action == "effects" else getattr(Directions, value.upper()) if action == "direction" else # getattr(transformers_library, value) if action == "pretransformer" else value ) } else: pop_attributes = {action: None} if action == "new_line": attributes = {"new_line": True} if "," in action and attributes is None and pop_attributes is None: nx, ny = [v.strip() for v in action.split(",")] nnx, nny = int(nx), int(ny) if nx[0] in ("+", "-") and ny[0] in ("+", "-"): rmoveto = nnx, nny elif nx[0] in ("+", "-") and ny[0] not in ("+", "-"): moveto = RETAIN_POS, nny rmoveto = nnx, 0 elif nx[0] not in ("+", "-") and ny[0] in ("+", "-"): moveto = nnx, RETAIN_POS rmoveto = 0, nny else: moveto = nnx, nny # Unknown token action - simply drop for now" mark = Mark(attributes, pop_attributes, moveto, rmoveto) if offset in self.mark_sequence: mark = Mark.merge(self.mark_sequence[offset], mark) self.mark_sequence[offset] = mark
def test_color_by_hsv_works(): c = Color(hsv=(0, 1, 1)) assert tuple(c.components) == (255, 0, 0)