You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-3-lib/pym/calculate/lib/utils/colortext/palette.py

441 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#-*- coding: utf-8 -*-
# Copyright 2014 Calculate Ltd. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
from itertools import chain
from math import pow
from calculate.lib.utils.text import MultiReplace
class ConsoleCodesInfo:
"""
Коды цветов
"""
COLORCOUNT = 8
BLACK, RED, GREEN, BROWN, BLUE, PURPLE, CYAN, WHITE = range(0, COLORCOUNT)
DEFAULT = 9
FOREGROUND = 30
FOREGROUND_DEFAULT = 39
FOREGROUND_END = 37
BACKGROUND = 40
BACKGROUND_DEFAULT = 49
BACKGROUND_END = 47
BOLD = 1
HALFBRIGHT = 2
UNDERLINE = 4
NOUNDERLINE = 24
NORMAL = 22
RESET = 0
FOREGROUND256 = 38
COLOR256 = 5
BACKGROUND256 = 48
INVERT = 7
NOINVERT = 27
class ConsoleColor256:
"""Объект перевода RGB цветов в консоль 256color"""
colorList = (0, 95, 135, 175, 215, 255)
colorMatch = [colorList[x] + (colorList[x + 1] - colorList[x]) / 2
for x in range(0, 5)] + [255]
colorHex = ["%02x" % val for val in colorList]
webMatch = re.compile("^#[0-9A-Fa-f]{6}$")
grayMatch = [6 + x * 11 for x in xrange(0, 23)] + [255]
@staticmethod
def rgbToConsole(color):
"""Перевести RGB в 256 консоль
>>> ConsoleColor256.rgbToConsole("#5f00ff")
57
>>> ConsoleColor256.rgbToConsole("not #rrggbb") is None
True
"""
color = ConsoleColor256.convertRgbToIntGroup(color)
if color:
# grayscale match
if abs(color[0] - color[1]) + abs(color[0] - color[2]) < 5:
for j, matchGray in enumerate(ConsoleColor256.grayMatch):
if color[0] <= matchGray:
return 232 + j
# colortext match
colorNumber = 16
for i, _color in enumerate(color):
for j, halfColor in enumerate(ConsoleColor256.colorMatch):
if _color <= halfColor:
colorNumber += j * 6 ** i
break
return colorNumber
return None
@staticmethod
def convertRgbToIntGroup(color):
"""Преобразовать #RRGGBB в кортеж целых (dec(RR),dec(GG),dec(BB))
В случае ошибки возвращает None
"""
if color and ConsoleColor256.webMatch.match(color):
return [int(x, base=16) for x in
(color[5:7], color[3:5], color[1:3])]
return None
@staticmethod
def consoleToRgb(color):
"""
Перевести 256 консоль в #RGB
>>> ConsoleColor256.consoleToRgb(216)
'#ffaf87'
>>> ConsoleColor256.consoleToRgb(15) is None
True
"""
if color >= 255:
return "#ffffff"
if color >= 232:
return "#{0:02x}{0:02x}{0:02x}".format((color - 232) * 11)
elif color >= 16:
color -= 16
return "#%s" % "".join(ConsoleColor256.colorHex[color / x % 6]
for x in (36, 6, 1))
else:
return None
class TextState(object):
"""
Параметры текста
>>> ts = TextState()
>>> ts.attr = TextState.Attributes.BOLD | TextState.Attributes.UNDERLINE
>>> ts.bold
True
>>> ts.underline
True
>>> ts.halfbright
False
"""
class Attributes:
NONE = 0
BOLD = 1
HALFBRIGHT = 2
UNDERLINE = 4
INVERT = 8
class Colors:
# обычные цвета
BLACK = "black"
RED = "red"
GREEN = "green"
BROWN = "brown"
BLUE = "blue"
PURPLE = "purple"
CYAN = "cyan"
GRAY = "gray"
# яркие цвета
DARK = "dark"
LIGHT_RED = "lightred"
LIGHT_GREEN = "lightgreen"
YELLOW = "yellow"
LIGHT_BLUE = "lightblue"
LIGHT_PURPLE = "lightpurple"
LIGHT_CYAN = "lightcyan"
WHITE = "white"
# темные цвета (консоль пониженной яркости)
DARK_BLACK = "darkblack"
DARK_RED = "darkred"
DARK_GREEN = "darkgreen"
DARK_BROWN = "darkbrown"
DARK_BLUE = "darkblue"
DARK_PURPLE = "darkpurple"
DARK_CYAN = "darkcyan"
DARK_GRAY = "darkgray"
DEFAULT = None
normalColors = [Colors.BLACK, Colors.RED, Colors.GREEN, Colors.BROWN,
Colors.BLUE, Colors.PURPLE, Colors.CYAN, Colors.GRAY]
lightColors = [Colors.DARK, Colors.LIGHT_RED, Colors.LIGHT_GREEN,
Colors.YELLOW, Colors.LIGHT_BLUE, Colors.LIGHT_PURPLE,
Colors.LIGHT_CYAN, Colors.WHITE]
darkColors = [Colors.DARK_BLACK, Colors.DARK_RED, Colors.DARK_GREEN,
Colors.DARK_BROWN, Colors.DARK_BLUE, Colors.DARK_PURPLE,
Colors.DARK_CYAN, Colors.DARK_GRAY]
def bitProperty(bit):
def set(self, value):
self.attr &= ~bit
self.attr |= bit if value else 0
def get(self):
return bool(self.attr & bit)
return property(get, set)
# текст с подчеркиванием
underline = bitProperty(Attributes.UNDERLINE)
# текст жирный
bold = bitProperty(Attributes.BOLD)
# пониженная яркость
halfbright = bitProperty(Attributes.HALFBRIGHT)
# инверсия
invert = bitProperty(Attributes.INVERT)
def __init__(self, foreground=Colors.DEFAULT,
background=Colors.DEFAULT,
attr=Attributes.NONE):
self.foreground = foreground
self.background = background
self.attr = attr
def clone(self):
return self.__class__(self.foreground, self.background,
self.attr)
def __cmp__(self, other):
for i in ["foreground", "background", "attr"]:
cmp_res = cmp(getattr(self, i), getattr(other, i))
if cmp_res:
return cmp_res
return 0
@classmethod
def colors(cls):
return chain(xrange(0, 8), [None])
class BaseColorMapping:
# соответствие внутренних цветов консольным
mapConsole_TS = {ConsoleCodesInfo.BLACK: TextState.Colors.BLACK,
ConsoleCodesInfo.RED: TextState.Colors.RED,
ConsoleCodesInfo.GREEN: TextState.Colors.GREEN,
ConsoleCodesInfo.BROWN: TextState.Colors.BROWN,
ConsoleCodesInfo.BLUE: TextState.Colors.BLUE,
ConsoleCodesInfo.PURPLE: TextState.Colors.PURPLE,
ConsoleCodesInfo.CYAN: TextState.Colors.CYAN,
ConsoleCodesInfo.WHITE: TextState.Colors.GRAY}
mapTS_Console = {v: k for k, v in mapConsole_TS.items()}
def __init__(self, base):
self.mapConsole_TS = self.mapConsole_TS.copy()
self.mapConsole_TS.update(base.mapConsole_TS)
self.mapTS_Console = {v: k for k, v in self.mapConsole_TS.items()}
class LightColorMapping(BaseColorMapping):
# соответствие внутренних цветов консольным
offset = ConsoleCodesInfo.COLORCOUNT
mapConsole_TS = {ConsoleCodesInfo.BLACK + offset: TextState.Colors.DARK,
ConsoleCodesInfo.RED + offset: TextState.Colors.LIGHT_RED,
ConsoleCodesInfo.GREEN + offset: TextState.Colors.LIGHT_GREEN,
ConsoleCodesInfo.BROWN + offset: TextState.Colors.YELLOW,
ConsoleCodesInfo.BLUE + offset: TextState.Colors.LIGHT_BLUE,
ConsoleCodesInfo.PURPLE + offset: TextState.Colors.LIGHT_PURPLE,
ConsoleCodesInfo.CYAN + offset: TextState.Colors.LIGHT_CYAN,
ConsoleCodesInfo.WHITE + offset: TextState.Colors.WHITE}
mapTS_Console = {v: k for k, v in mapConsole_TS.items()}
class ConsoleCodeMapping(BaseColorMapping):
"""
Декоратор для преобразования кодов цвета в код цвета тона или фона
Добавляет смещение к коду (3 -> 3 + codeoffset)
"""
def __init__(self, codeoffset, base):
self.mapConsole_TS = {codeoffset + k: v for k, v in
base.mapConsole_TS.items()}
self.mapTS_Console = {v: k for k, v in self.mapConsole_TS.items()}
class SpanPalette:
"""
Палитра для SpanCssOutput (черное на белом)
"""
LOW_BRIGHT, NORMAL_BRIGHT, HIGH_BRIGHT = 0, 1, 2
defaultColor = ["Black", "Black", "DarkGrey"]
defaultBackground = "White"
normalBright = dict(zip(TextState.normalColors,
["Black", "DarkRed", "DarkGreen",
"Sienna", "DarkBlue", "DarkViolet",
"LightSeaGreen", "Grey"]))
highBright = dict(zip(TextState.lightColors,
["DarkGrey", "Red", "Green",
"Yellow", "RoyalBlue", "Magenta",
"Cyan", "White"]))
lowBright = dict(zip(TextState.darkColors,
["Black", "Maroon", "DarkGreen",
"SaddleBrown", "DarkBlue", "DarkViolet",
"LightSeaGreen", "Grey"]))
mapHighNormal = dict(zip(TextState.normalColors,
TextState.lightColors))
mapLowNormal = dict(zip(TextState.normalColors,
TextState.darkColors))
def __init__(self):
self.colorMap = dict(self.normalBright.items() +
self.highBright.items() +
self.lowBright.items())
self.brightMap = {self.NORMAL_BRIGHT: {},
self.HIGH_BRIGHT: self.mapHighNormal,
self.LOW_BRIGHT: self.mapLowNormal}
def brightTransform(self, color, bright):
"""
Преобразовать основной цвет в зависимости от установленной яркости
"""
mapper = self.brightMap.get(bright, {})
return mapper.get(color, color)
def getBackgroundColor(self, color=TextState.Colors.DEFAULT):
if not color:
return self.defaultBackground
return self.colorMap.get(color, color)
def getTextColor(self, color=TextState.Colors.DEFAULT,
bright=NORMAL_BRIGHT):
"""
Получить соответствие цвета строкой
"""
if not color:
return self.defaultColor[bright]
color = self.brightTransform(color, bright)
return self.colorMap.get(color, color)
def getBaseColorByRGB(self, rgb_color):
"""Получить ближайший базовый цвет, согласно палитре
Например: #709080 -> TextState.Colors.DARK
Args:
rgb_color: цвет #rrggbb (или словесный)
Returns: TextState.Colors если подходящий цвет найден
"""
def calculate_color_diff(Ri,Ro,Gi,Go,Bi,Bo):
# вычислить отличие цветов
kR, kG, kB = 30, 59, 25
return kR*pow(Ri-Ro, 2)+kG*pow(Gi-Go, 2)+kB*pow(Bi-Bo, 2)
color = ConsoleColor256.convertRgbToIntGroup(rgb_color)
diffList = []
if color:
for key, val in self.colorMap.items():
intVal = ConsoleColor256.convertRgbToIntGroup(val)
if intVal:
diffList.append((
calculate_color_diff(*chain(*zip(color, intVal))), key))
if diffList:
#print diffList,sorted(diffList)[0]
for diff, color in sorted(diffList):
return color
# если вместо RGB в палитре цвет указан словом
elif rgb_color in self.colorMap.values():
for key, val in self.colorMap.items():
if val == rgb_color:
return key
return None
class DarkPastelsPalette(SpanPalette):
"""
Палитра идентичная Calculate в консоли (Dark Pastels)
"""
defaultColor = ["#DCDCDC", "#DCDCDC", "#709080"]
defaultBackground = "#2C2C2C"
normalBright = dict(zip(TextState.normalColors,
["#2C2C2C", "#705050", "#60B48A", "#DFAF8F",
"#9AB8D7", "#DC8CC3", "#8CD0D3", "#DCDCDC"]))
highBright = dict(zip(TextState.lightColors,
["#709080", "#DCA3A3", "#72D5A3", "#F0DFAF",
"#94BFF3", "#EC93D3", "#93E0E3", "#FFFFFF"]))
class WhitePalette(SpanPalette):
"""
Палитра черное на белом
"""
defaultColor = ["#141414", "#141414", "#272727"]
defaultBackground = "#ffffff"
normalBright = dict(zip(TextState.normalColors,
["#141414", "#742523", "#237425", "#737423",
"#252374", "#742373", "#237374", "#c1c1c1"]))
highBright = dict(zip(TextState.lightColors,
["#272727", "#cc5755", "#55cc57", "#cacc55",
"#5755cc", "#cc55ca", "#55cacc", "#ffffff"]))
class GreyPaletteOld(SpanPalette):
"""
Палитра черное на светло-сером
"""
defaultColor = ["#101010", "#101010", "#505050"]
defaultBackground = "#e4e1e0"
normalBright = dict(zip(TextState.normalColors,
["#101010", "#902c2b", "#2b902c", "#8f902b",
"#2c2b90", "#902b8f", "#2b8f90", "#b9b9b9"]))
highBright = dict(zip(TextState.lightColors,
["#303030", "#c6403d", "#3dc640", "#c3c63d",
"#403dc6", "#c63dc3", "#3dc3c6", "#e4e1e0"]))
class GreyPalette(SpanPalette):
"""
Палитра черное на светло-сером
"""
defaultColor = ["#101010", "#101010", "#505050"]
defaultBackground = "#ffffff"
normalBright = dict(zip(TextState.normalColors,
["#101010", "#902c2b", "#2b902c", "#8f902b",
"#2c2b90", "#902b8f", "#2b8f90", "#b9b9b9"]))
highBright = dict(zip(TextState.lightColors,
["#303030", "#c6403d", "#3dc640", "#c3c63d",
"#403dc6", "#c63dc3", "#3dc3c6", "#ffffff"]))
class XmlFormat:
"""
Константы для внутреннего формата XML
"""
class Tags:
BOLD = "b"
UNDERLINE = "u"
FONT = "font"
HALFBRIGHT = "dark"
INVERT = "invert"
NEWLINE = "br"
TAB = "tab"
class FontAttributes:
BACKGROUND = "bgColor"
FOREGROUND = "color"
escape_map = {'<': '&lt;', '>': '&gt;'}
escaper = MultiReplace(escape_map)
unescaper = MultiReplace({v: k for k, v in escape_map.items()})