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

#-*- 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()})