ScoDoc-Lille/app/but/prepajury_xl_format.py

667 lines
19 KiB
Python

import abc
from enum import Enum
import openpyxl.styles
from openpyxl.cell import Cell
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")
GREEN = (9, "FF00FF00")
GREY = (10, "FF101010")
BUT1 = (23, "FF95B3D7")
RCUE1 = (24, "FFB8CCE4")
UE1 = (25, "FFDCE6F1")
BUT2 = (26, "FFC4D79B")
RCUE2 = (27, "FFD8E4BC")
UE2 = (28, "FFEBF1DE")
BUT3 = (29, "FFFABF8F")
RCUE3 = (30, "FFFCD5B4")
UE3 = (31, "FFFDE9D9")
class SCO_BORDERTHICKNESS(Enum):
def __new__(cls, value, width):
obj = object.__new__(cls)
obj._value_ = value
obj.width = width
return obj
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
NONE = (0, None)
FONTNAME_CALIBRI = (1, "Calibri")
FONTNAME_ARIAL = (2, "Arial")
FONTNAME_COURIER = (3, "Courier New")
FONTNAME_TIMES = (4, "Times New Roman")
class SCO_FONTSIZE(Enum):
def __new__(cls, value, fontsize):
obj = object.__new__(cls)
obj._value_ = value
obj.fontsize = fontsize
return obj
NONE = (0, None)
FONTSIZE_9 = (1, 9.0)
FONTSIZE_10 = (2, 10.0)
FONTSIZE_11 = (2, 11.0)
FONTSIZE_13 = (4, 13.0)
class SCO_NUMBER_FORMAT(Enum):
def __new__(cls, value, format):
obj = object.__new__(cls)
obj._value_ = value
obj.format = format
return obj
NONE = (0, None)
NUMBER_GENERAL = (0, FORMAT_GENERAL)
NUMBER_00 = (1, FORMAT_NUMBER_00)
NUMBER_0 = (2, "0.0")
class SCO_HALIGN(Enum):
def __new__(cls, value, position):
obj = object.__new__(cls)
obj._value_ = value
obj.position = position
return obj
NONE = (0, None)
HALIGN_LEFT = (1, "left")
HALIGN_CENTER = (2, "center")
HALIGN_RIGHT = (3, "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) -> bool:
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) -> SCO_NUMBER_FORMAT:
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) -> SCO_COLORS:
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) -> SCO_BORDERTHICKNESS:
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) -> SCO_FONTNAME:
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) -> SCO_FONTSIZE:
value = self.read(signature)
assert value < (1 << self.width)
return SCO_FONTSIZE(value) or None
class Composante_halign(Composante):
WIDTH: int = 3
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) -> SCO_HALIGN:
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 = 3
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) -> SCO_VALIGN:
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=None if self.thickness is None else 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=None if self.halign is None else self.halign.position,
vertical=None if self.valign is None else 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=None if self.name is None else self.name.fontname,
size=None if self.fontsize is None else 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 = FORMAT_GENERAL,
):
self.font = font or None
self.fill = fill or None
self.alignment = alignment or None
self.borders = borders or None
self.number_format = number_format or SCO_NUMBER_FORMAT.NUMBER_GENERAL
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()
cell.number_format = (
FORMAT_GENERAL
if self.number_format is None
or self.number_format == SCO_NUMBER_FORMAT.NONE
else 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) -> Sco_Fill:
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) -> Sco_Font:
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) -> Sco_BorderSide:
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) -> Sco_Borders:
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) -> Sco_Alignment:
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) -> Sco_Style:
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)
def apply(self, cell: Cell, signature: int):
self.composante.build(signature).apply(cell)
@classmethod
def compose(cls, composition: list[("FMT", int)], signature: int = 0) -> int:
for field, value in composition:
signature = field.write(value, field.composante.clear(signature))
return signature
@classmethod
def style(cls, signature: int = None) -> Sco_Style:
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)
fmt_atomics = {
FMT.FONT_NAME,
FMT.FONT_SIZE,
FMT.FONT_COLOR,
FMT.FONT_BOLD,
FMT.FONT_ITALIC,
FMT.FONT_OUTLINE,
FMT.BORDER_LEFT_STYLE,
FMT.BORDER_LEFT_COLOR,
FMT.BORDER_RIGHT_STYLE,
FMT.BORDER_RIGHT_COLOR,
FMT.BORDER_TOP_STYLE,
FMT.BORDER_TOP_COLOR,
FMT.BORDER_BOTTOM_STYLE,
FMT.BORDER_BOTTOM_COLOR,
FMT.FILL_BGCOLOR,
FMT.ALIGNMENT_HALIGN,
FMT.ALIGNMENT_VALIGN,
FMT.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]
)