|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
# Copyright 2014-2016 Mir Calculate. 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(object):
|
|
|
"""
|
|
|
Коды цветов
|
|
|
"""
|
|
|
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(object):
|
|
|
"""Объект перевода 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
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
class TextState(object):
|
|
|
"""
|
|
|
Параметры текста
|
|
|
|
|
|
>>> ts = TextState()
|
|
|
>>> ts.attr = TextState.Attributes.BOLD | TextState.Attributes.UNDERLINE
|
|
|
>>> ts.bold
|
|
|
True
|
|
|
>>> ts.underline
|
|
|
True
|
|
|
>>> ts.halfbright
|
|
|
False
|
|
|
"""
|
|
|
|
|
|
class Attributes(object):
|
|
|
NONE = 0
|
|
|
BOLD = 1
|
|
|
HALFBRIGHT = 2
|
|
|
UNDERLINE = 4
|
|
|
INVERT = 8
|
|
|
|
|
|
class Colors(object):
|
|
|
# обычные цвета
|
|
|
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]
|
|
|
|
|
|
# текст с подчеркиванием
|
|
|
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()}
|
|
|
|
|
|
|
|
|
def ConsoleCodeMapping(codeoffset, base):
|
|
|
"""
|
|
|
Декоратор для преобразования кодов цвета в код цвета тона или фона
|
|
|
|
|
|
Добавляет смещение к коду (3 -> 3 + codeoffset)
|
|
|
"""
|
|
|
mapConsole_TS = {codeoffset + k: v for k, v in
|
|
|
base.mapConsole_TS.items()}
|
|
|
return {v: k for k, v in 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(object):
|
|
|
"""
|
|
|
Константы для внутреннего формата XML
|
|
|
"""
|
|
|
|
|
|
class Tags(object):
|
|
|
BOLD = "b"
|
|
|
UNDERLINE = "u"
|
|
|
FONT = "font"
|
|
|
HALFBRIGHT = "dark"
|
|
|
INVERT = "invert"
|
|
|
NEWLINE = "br"
|
|
|
TAB = "tab"
|
|
|
|
|
|
class FontAttributes(object):
|
|
|
BACKGROUND = "bgColor"
|
|
|
FOREGROUND = "color"
|
|
|
|
|
|
escape_map = {'<': '<', '>': '>'}
|
|
|
escaper = MultiReplace(escape_map)
|
|
|
unescaper = MultiReplace({v: k for k, v in escape_map.items()})
|