forked from ScoDoc/ScoDoc
632 lines
18 KiB
Python
632 lines
18 KiB
Python
import abc
|
|
from enum import Enum
|
|
|
|
import openpyxl.styles
|
|
from openpyxl.styles import Side, Border, Font, PatternFill, Alignment
|
|
from openpyxl.styles.numbers import FORMAT_GENERAL, FORMAT_NUMBER_00
|
|
|
|
# Formatting Enums
|
|
|
|
|
|
class SCO_COLORS(Enum):
|
|
def __new__(cls, value, argb):
|
|
obj = object.__new__(cls)
|
|
obj._value_ = value
|
|
obj.argb = argb
|
|
return obj
|
|
|
|
NONE = (0, None)
|
|
BLACK = (1, "FF000000")
|
|
WHITE = (2, "FFFFFFFF")
|
|
RED = (3, "FFFF0000")
|
|
BROWN = (4, "FF993300")
|
|
PURPLE = (5, "FF993366")
|
|
BLUE = (6, "FF0000FF")
|
|
ORANGE = (7, "FFFF3300")
|
|
LIGHT_YELLOW = (8, "FFFFFF99")
|
|
BUT1 = (9, "FF95B3D7")
|
|
RCUE1 = (10, "FFB8CCE4")
|
|
UE1 = (11, "FFDCE6F1")
|
|
BUT2 = (12, "FFC4D79B")
|
|
RCUE2 = (13, "FFD8E4BC")
|
|
UE2 = (14, "FFEBF1DE")
|
|
BUT3 = (15, "FFFABF8F")
|
|
RCUE3 = (16, "FFFCD5B4")
|
|
UE3 = (17, "FFFDE9D9")
|
|
|
|
|
|
class SCO_BORDERTHICKNESS(Enum):
|
|
def __new__(cls, value, width):
|
|
obj = object.__new__(cls)
|
|
obj._value_ = value
|
|
obj.width = width
|
|
return obj
|
|
|
|
BORDER_NONE = (0, None)
|
|
BORDER_HAIR = (1, "hair")
|
|
BORDER_THIN = (2, "thin")
|
|
BORDER_MEDIUM = (3, "medium")
|
|
BORDER_THICK = (4, "thick")
|
|
|
|
|
|
class SCO_FONTNAME(Enum):
|
|
def __new__(cls, value, fontname):
|
|
obj = object.__new__(cls)
|
|
obj._value_ = value
|
|
obj.fontname = fontname
|
|
return obj
|
|
|
|
FONTNAME_CALIBRI = (0, "Calibri")
|
|
FONTNAME_ARIAL = (1, "Arial")
|
|
FONTNAME_COURIER = (2, "Courier New")
|
|
FONTNAME_TIMES = (3, "Times New Roman")
|
|
|
|
|
|
class SCO_FONTSIZE(Enum):
|
|
def __new__(cls, value, fontsize):
|
|
obj = object.__new__(cls)
|
|
obj._value_ = value
|
|
obj.fontsize = fontsize
|
|
return obj
|
|
|
|
FONTSIZE_10 = (0, 10.0)
|
|
FONTSIZE_9 = (1, 9.0)
|
|
FONTSIZE_11 = (2, 11.0)
|
|
FONTSIZE_13 = (3, 13.0)
|
|
FONTSIZE_16 = (4, 16.0)
|
|
|
|
|
|
class SCO_NUMBER_FORMAT(Enum):
|
|
def __new__(cls, value, format):
|
|
obj = object.__new__(cls)
|
|
obj._value_ = value
|
|
obj.format = format
|
|
return obj
|
|
|
|
NUMBER_GENERAL = (0, FORMAT_GENERAL)
|
|
NUMBER_00 = (1, FORMAT_NUMBER_00)
|
|
|
|
|
|
class SCO_HALIGN(Enum):
|
|
def __new__(cls, value, position):
|
|
obj = object.__new__(cls)
|
|
obj._value_ = value
|
|
obj.position = position
|
|
return obj
|
|
|
|
HALIGN_LEFT = (0, "left")
|
|
HALIGN_CENTER = (1, "center")
|
|
HALIGN_RIGHT = (2, "right")
|
|
|
|
|
|
class SCO_VALIGN(Enum):
|
|
def __new__(cls, value, position):
|
|
obj = object.__new__(cls)
|
|
obj._value_ = value
|
|
obj.position = position
|
|
return obj
|
|
|
|
VALIGN_BOTTOM = (0, "bottom")
|
|
VALIGN_TOP = (1, "top")
|
|
VALIGN_CENTER = (2, "center")
|
|
|
|
|
|
# Composante (bitfield) atomique. Based on Enums
|
|
free = 0
|
|
|
|
|
|
class Composante(abc.ABC):
|
|
def __init__(self, base=None, width: int = 1):
|
|
global free
|
|
if base is None:
|
|
self.base = free
|
|
free += width
|
|
else:
|
|
self.base = base
|
|
self.width = width
|
|
self.end = self.base + self.width
|
|
self.mask = ((1 << width) - 1) << self.base
|
|
|
|
def read(self, signature: int) -> int:
|
|
return (signature & self.mask) >> self.base
|
|
|
|
def clear(self, signature: int) -> int:
|
|
return signature & ~self.mask
|
|
|
|
def write(self, index, signature=0) -> int:
|
|
return self.clear(signature) + (index << self.base)
|
|
|
|
def make_zero_based_constant(self, enums: list[Enum] = None):
|
|
return 0
|
|
|
|
@abc.abstractmethod
|
|
def build(self, value: int):
|
|
pass
|
|
|
|
|
|
class Composante_boolean(Composante):
|
|
def __init__(self):
|
|
super().__init__(width=1)
|
|
|
|
def set(self, data: bool, signature=0) -> int:
|
|
return self.write(1 if data else 0, signature)
|
|
|
|
def build(self, signature):
|
|
value = self.read(signature)
|
|
assert value < (1 << self.width)
|
|
return value == 1
|
|
|
|
|
|
class Composante_number_format(Composante):
|
|
WIDTH: int = 2
|
|
|
|
def __init__(self):
|
|
assert (1 << self.WIDTH) > SCO_NUMBER_FORMAT.__len__()
|
|
super().__init__(width=self.WIDTH)
|
|
|
|
def set(self, data: SCO_NUMBER_FORMAT, signature=0) -> int:
|
|
return self.write(data.value, signature)
|
|
|
|
def build(self, signature: int):
|
|
value = self.read(signature)
|
|
assert value < (1 << self.width)
|
|
return SCO_NUMBER_FORMAT(value)
|
|
|
|
|
|
class Composante_Colors(Composante):
|
|
WIDTH: int = 5
|
|
|
|
def __init__(self, default: SCO_COLORS = SCO_COLORS.BLACK):
|
|
assert (1 << self.WIDTH) > SCO_COLORS.__len__()
|
|
super().__init__(width=self.WIDTH)
|
|
self.default: SCO_COLORS = default
|
|
|
|
def set(self, data: SCO_COLORS, signature=0) -> int:
|
|
return self.write(data.value, signature)
|
|
|
|
def build(self, signature: int):
|
|
value = self.read(signature)
|
|
assert value < (1 << self.width)
|
|
if value == 0:
|
|
return None
|
|
try:
|
|
return SCO_COLORS(value)
|
|
except:
|
|
return self.default
|
|
|
|
|
|
class Composante_borderThickness(Composante):
|
|
WIDTH: int = 3
|
|
|
|
def __init__(self):
|
|
assert (1 << self.WIDTH) > SCO_BORDERTHICKNESS.__len__()
|
|
super().__init__(width=self.WIDTH)
|
|
|
|
def set(self, data: SCO_BORDERTHICKNESS, signature=0) -> int:
|
|
return self.write(data.value, signature)
|
|
|
|
def build(self, signature: int):
|
|
value = self.read(signature)
|
|
assert value < (1 << self.width)
|
|
try:
|
|
return SCO_BORDERTHICKNESS(value)
|
|
except:
|
|
return None
|
|
|
|
|
|
class Composante_fontname(Composante):
|
|
WIDTH: int = 3
|
|
|
|
def __init__(self):
|
|
assert (1 << self.WIDTH) > SCO_FONTNAME.__len__()
|
|
super().__init__(width=self.WIDTH)
|
|
|
|
def set(self, data: SCO_FONTNAME, signature=0) -> int:
|
|
return self.write(data.value, signature)
|
|
|
|
def build(self, signature: int):
|
|
value = self.read(signature)
|
|
assert value < (1 << self.width)
|
|
try:
|
|
return SCO_FONTNAME(value)
|
|
except:
|
|
return SCO_FONTNAME.FONTNAME_CALIBRI
|
|
|
|
|
|
class Composante_fontsize(Composante):
|
|
WIDTH: int = 3
|
|
|
|
def __init__(self):
|
|
assert (1 << self.WIDTH) > SCO_FONTSIZE.__len__()
|
|
super().__init__(width=self.WIDTH)
|
|
|
|
def set(self, data: SCO_FONTSIZE, signature=0) -> int:
|
|
return self.write(data.value, signature)
|
|
|
|
def build(self, signature: int):
|
|
value = self.read(signature)
|
|
assert value < (1 << self.width)
|
|
return SCO_FONTSIZE(value) or None
|
|
|
|
|
|
class Composante_halign(Composante):
|
|
WIDTH: int = 2
|
|
|
|
def __init__(self):
|
|
assert (1 << self.WIDTH) > SCO_HALIGN.__len__()
|
|
super().__init__(width=self.WIDTH)
|
|
|
|
def set(self, data: SCO_HALIGN, signature=0) -> int:
|
|
return self.write(data.value, signature)
|
|
|
|
def build(self, signature: int):
|
|
value = self.read(signature)
|
|
assert value < (1 << self.width)
|
|
try:
|
|
return SCO_HALIGN(value)
|
|
except:
|
|
return SCO_HALIGN.HALIGN_LEFT
|
|
|
|
|
|
class Composante_valign(Composante):
|
|
WIDTH: int = 2
|
|
|
|
def __init__(self):
|
|
assert (1 << self.WIDTH) > SCO_VALIGN.__len__()
|
|
super().__init__(width=self.WIDTH)
|
|
|
|
def set(self, data: SCO_VALIGN, signature=0) -> int:
|
|
return self.write(data.value, signature)
|
|
|
|
def build(self, signature: int):
|
|
value = self.read(signature)
|
|
assert value < (1 << self.width)
|
|
try:
|
|
return SCO_VALIGN(value)
|
|
except:
|
|
return SCO_VALIGN.VALIGN_CENTER
|
|
|
|
|
|
# Formatting objects
|
|
|
|
|
|
class Sco_Fill:
|
|
def __init__(self, color: SCO_COLORS):
|
|
self.color = color
|
|
|
|
def to_openpyxl(self):
|
|
return PatternFill(
|
|
fill_type="solid",
|
|
fgColor=None if self.color is None else self.color.argb,
|
|
)
|
|
|
|
|
|
class Sco_BorderSide:
|
|
def __init__(
|
|
self,
|
|
thickness: SCO_BORDERTHICKNESS = None,
|
|
color: SCO_COLORS = SCO_COLORS.WHITE,
|
|
):
|
|
self.thickness = thickness
|
|
self.color: SCO_COLORS = color
|
|
|
|
def to_openpyxl(self):
|
|
return Side(
|
|
border_style=self.thickness.width,
|
|
color=None if self.color is None else self.color.argb,
|
|
)
|
|
|
|
|
|
class Sco_Borders:
|
|
def __init__(
|
|
self,
|
|
left: Sco_BorderSide = None,
|
|
right: Sco_BorderSide = None,
|
|
top: Sco_BorderSide = None,
|
|
bottom: Sco_BorderSide = None,
|
|
):
|
|
self.left = left
|
|
self.right = right
|
|
self.top = top
|
|
self.bottom = bottom
|
|
|
|
def to_openpyxl(self):
|
|
return Border(
|
|
left=self.left.to_openpyxl(),
|
|
right=self.right.to_openpyxl(),
|
|
top=self.top.to_openpyxl(),
|
|
bottom=self.bottom.to_openpyxl(),
|
|
)
|
|
|
|
|
|
class Sco_Alignment:
|
|
def __init__(
|
|
self,
|
|
halign: SCO_HALIGN = None,
|
|
valign: SCO_VALIGN = None,
|
|
):
|
|
self.halign = halign
|
|
self.valign = valign
|
|
|
|
def to_openpyxl(self):
|
|
return Alignment(
|
|
horizontal=self.halign.position,
|
|
vertical=self.valign.position,
|
|
)
|
|
|
|
|
|
class Sco_Font:
|
|
def __init__(
|
|
self,
|
|
name: SCO_FONTNAME = SCO_FONTNAME(0),
|
|
fontsize: SCO_FONTSIZE = SCO_FONTSIZE(0),
|
|
bold: bool = None,
|
|
italic: bool = None,
|
|
outline: bool = None,
|
|
color: "SCO_COLORS" = None,
|
|
):
|
|
self.name = name
|
|
self.bold = bold
|
|
self.italic = italic
|
|
self.outline = outline
|
|
self.color = color
|
|
self.fontsize = fontsize
|
|
|
|
def to_openpyxl(self):
|
|
return Font(
|
|
name=self.name.fontname,
|
|
size=self.fontsize.fontsize,
|
|
bold=self.bold,
|
|
italic=self.italic,
|
|
outline=self.outline,
|
|
color=None if self.color is None else self.color.argb,
|
|
)
|
|
|
|
|
|
class Sco_Style:
|
|
from openpyxl.cell import Cell
|
|
|
|
def __init__(
|
|
self,
|
|
font: Sco_Font = None,
|
|
fill: Sco_Fill = None,
|
|
alignment: Sco_Alignment = None,
|
|
borders: Sco_Borders = None,
|
|
number_format: SCO_NUMBER_FORMAT = None,
|
|
):
|
|
self.font = font
|
|
self.fill = fill
|
|
self.alignment = alignment
|
|
self.borders = borders
|
|
self.number_format = number_format
|
|
|
|
def apply(self, cell: Cell):
|
|
if self.font:
|
|
cell.font = self.font.to_openpyxl()
|
|
if self.fill and self.fill.color:
|
|
cell.fill = self.fill.to_openpyxl()
|
|
if self.alignment:
|
|
cell.alignment = self.alignment.to_openpyxl()
|
|
if self.borders:
|
|
cell.border = self.borders.to_openpyxl()
|
|
if self.number_format:
|
|
cell.number_format = self.number_format.format
|
|
|
|
|
|
# Composantes groupant d'autres composantes et dotées d'un mécanisme de cache
|
|
|
|
|
|
class Composante_group(Composante):
|
|
def __init__(self, composantes: list[Composante]):
|
|
self.composantes = composantes
|
|
self.cache = {}
|
|
mini = min([comp.base for comp in composantes])
|
|
maxi = max([comp.end for comp in composantes])
|
|
width = sum([comp.width for comp in composantes])
|
|
if not width == (maxi - mini):
|
|
raise Exception("Composante group non complete ou non connexe")
|
|
super().__init__(base=mini, width=width)
|
|
|
|
def lookup_or_cache(self, signature: int):
|
|
value = self.read(signature)
|
|
assert value < (1 << self.width)
|
|
if not value in self.cache:
|
|
self.cache[value] = self.build(signature)
|
|
return self.cache[value]
|
|
|
|
def make_zero_based_constant(self, enums: list[Enum] = None) -> int:
|
|
if enums is None:
|
|
return 0
|
|
signature = 0
|
|
for enum, composante in zip(enums, self.composantes):
|
|
signature += composante.write(enum.value)
|
|
return signature >> self.base
|
|
|
|
|
|
class Composante_fill(Composante_group):
|
|
def __init__(self, color: Composante_Colors):
|
|
super().__init__([color])
|
|
self.color = color
|
|
|
|
def build(self, signature: int):
|
|
return Sco_Fill(color=self.color.build(signature))
|
|
|
|
|
|
class Composante_font(Composante_group):
|
|
def __init__(
|
|
self,
|
|
name: Composante_fontname,
|
|
fontsize: Composante_fontsize,
|
|
color: Composante_Colors,
|
|
bold: Composante_boolean,
|
|
italic: Composante_boolean,
|
|
outline: Composante_boolean,
|
|
):
|
|
super().__init__([name, fontsize, color, bold, italic, outline])
|
|
self.name = name
|
|
self.fontsize = fontsize
|
|
self.color = color
|
|
self.bold = bold
|
|
self.italic = italic
|
|
self.outline = outline
|
|
|
|
def build(self, signature: int):
|
|
return Sco_Font(
|
|
name=self.name.build(signature),
|
|
fontsize=self.fontsize.build(signature),
|
|
color=self.color.build(signature),
|
|
bold=self.bold.build(signature),
|
|
italic=self.italic.build(signature),
|
|
outline=self.outline.build(signature),
|
|
)
|
|
|
|
|
|
class Composante_border(Composante_group):
|
|
def __init__(self, thick: Composante_borderThickness, color: Composante_Colors):
|
|
super().__init__([thick, color])
|
|
self.thick = thick
|
|
self.color = color
|
|
|
|
def build(self, signature: int):
|
|
return Sco_BorderSide(
|
|
thickness=self.thick.build(signature),
|
|
color=self.color.build(signature),
|
|
)
|
|
|
|
|
|
class Composante_borders(Composante_group):
|
|
def __init__(
|
|
self,
|
|
left: Composante_border,
|
|
right: Composante_border,
|
|
top: Composante_border,
|
|
bottom: Composante_border,
|
|
):
|
|
super().__init__([left, right, top, bottom])
|
|
self.left = left
|
|
self.right = right
|
|
self.top = top
|
|
self.bottom = bottom
|
|
|
|
def build(self, signature: int):
|
|
return Sco_Borders(
|
|
left=self.left.lookup_or_cache(signature),
|
|
right=self.right.lookup_or_cache(signature),
|
|
top=self.top.lookup_or_cache(signature),
|
|
bottom=self.bottom.lookup_or_cache(signature),
|
|
)
|
|
|
|
|
|
class Composante_alignment(Composante_group):
|
|
def __init__(self, halign: Composante_halign, valign: Composante_valign):
|
|
super().__init__([halign, valign])
|
|
self.halign = halign
|
|
self.valign = valign
|
|
|
|
def build(self, signature: int):
|
|
return Sco_Alignment(
|
|
halign=self.halign.build(signature),
|
|
valign=self.valign.build(signature),
|
|
)
|
|
|
|
|
|
class Composante_all(Composante_group):
|
|
def __init__(
|
|
self,
|
|
font: Composante_font,
|
|
fill: Composante_fill,
|
|
borders: Composante_borders,
|
|
alignment: Composante_alignment,
|
|
number_format: Composante_number_format,
|
|
):
|
|
super().__init__([font, fill, borders, alignment, number_format])
|
|
assert self.width < 64
|
|
self.font = font
|
|
self.fill = fill
|
|
self.borders = borders
|
|
self.alignment = alignment
|
|
self.number_format = number_format
|
|
|
|
def build(self, signature: int):
|
|
return Sco_Style(
|
|
fill=self.fill.lookup_or_cache(signature),
|
|
font=self.font.lookup_or_cache(signature),
|
|
borders=self.borders.lookup_or_cache(signature),
|
|
alignment=self.alignment.lookup_or_cache(signature),
|
|
number_format=self.number_format.build(signature),
|
|
)
|
|
|
|
def get_style(self, signature: int):
|
|
return self.lookup_or_cache(signature)
|
|
|
|
|
|
class FMT(Enum):
|
|
def __init__(self, composante: Composante):
|
|
self.composante = composante
|
|
|
|
def write(self, value, signature=0) -> int:
|
|
return self.composante.write(value, signature)
|
|
|
|
def set(self, data, signature: int = 0) -> int:
|
|
return self.composante.set(data, signature)
|
|
|
|
def get_style(self, signature: int):
|
|
return self.composante.lookup_or_cache(signature)
|
|
|
|
def make_zero_based_constant(self, enums: list[Enum]) -> int:
|
|
return self.composante.make_zero_based_constant(enums=enums)
|
|
|
|
@classmethod
|
|
def compose(cls, composition: list, signature: int = 0) -> int:
|
|
for field, value in composition:
|
|
signature = field.write(value, signature)
|
|
return signature
|
|
|
|
@classmethod
|
|
def style(cls, signature: int):
|
|
return FMT.ALL.get_style(signature)
|
|
|
|
FONT_NAME = Composante_fontname()
|
|
FONT_SIZE = Composante_fontsize()
|
|
FONT_COLOR = Composante_Colors()
|
|
FONT_BOLD = Composante_boolean()
|
|
FONT_ITALIC = Composante_boolean()
|
|
FONT_OUTLINE = Composante_boolean()
|
|
BORDER_LEFT_STYLE = Composante_borderThickness()
|
|
BORDER_LEFT_COLOR = Composante_Colors()
|
|
BORDER_RIGHT_STYLE = Composante_borderThickness()
|
|
BORDER_RIGHT_COLOR = Composante_Colors()
|
|
BORDER_TOP_STYLE = Composante_borderThickness()
|
|
BORDER_TOP_COLOR = Composante_Colors()
|
|
BORDER_BOTTOM_STYLE = Composante_borderThickness()
|
|
BORDER_BOTTOM_COLOR = Composante_Colors()
|
|
FILL_BGCOLOR = Composante_Colors(None)
|
|
ALIGNMENT_HALIGN = Composante_halign()
|
|
ALIGNMENT_VALIGN = Composante_valign()
|
|
NUMBER_FORMAT = Composante_number_format()
|
|
FONT = Composante_font(
|
|
FONT_NAME, FONT_SIZE, FONT_COLOR, FONT_BOLD, FONT_ITALIC, FONT_OUTLINE
|
|
)
|
|
FILL = Composante_fill(FILL_BGCOLOR)
|
|
BORDER_LEFT = Composante_border(BORDER_LEFT_STYLE, BORDER_LEFT_COLOR)
|
|
BORDER_RIGHT = Composante_border(BORDER_RIGHT_STYLE, BORDER_RIGHT_COLOR)
|
|
BORDER_TOP = Composante_border(BORDER_TOP_STYLE, BORDER_TOP_COLOR)
|
|
BORDER_BOTTOM = Composante_border(BORDER_BOTTOM_STYLE, BORDER_BOTTOM_COLOR)
|
|
BORDERS = Composante_borders(BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM)
|
|
ALIGNMENT = Composante_alignment(ALIGNMENT_HALIGN, ALIGNMENT_VALIGN)
|
|
ALL = Composante_all(FONT, FILL, BORDERS, ALIGNMENT, NUMBER_FORMAT)
|
|
|
|
|
|
HAIR_BLACK: int = FMT.BORDER_LEFT.make_zero_based_constant(
|
|
enums=[SCO_BORDERTHICKNESS.BORDER_HAIR, SCO_COLORS.BLACK]
|
|
)
|
|
THIN_BLACK: int = FMT.BORDER_LEFT.make_zero_based_constant(
|
|
enums=[SCO_BORDERTHICKNESS.BORDER_THIN, SCO_COLORS.BLACK]
|
|
)
|
|
MEDIUM_BLACK: int = FMT.BORDER_LEFT.make_zero_based_constant(
|
|
enums=[SCO_BORDERTHICKNESS.BORDER_MEDIUM, SCO_COLORS.BLACK]
|
|
)
|
|
THICK_BLACK: int = FMT.BORDER_LEFT.make_zero_based_constant(
|
|
enums=[SCO_BORDERTHICKNESS.BORDER_THICK, SCO_COLORS.BLACK]
|
|
)
|