Source code for fastpyxl.styles.fonts

# Copyright (c) 2010-2024 fastpyxl


from fastpyxl.typed_serialisable.base import Serialisable
from fastpyxl.typed_serialisable.errors import FieldValidationError
from fastpyxl.typed_serialisable.fields import AliasField, Field
from .colors import Color

from fastpyxl.compat import safe_string
from fastpyxl.xml.functions import Element
from fastpyxl.xml.constants import SHEET_MAIN_NS


def _no_value(tagname, value, namespace=None):
    if value:
        return Element(tagname, val=safe_string(value))


def _validate_range(value, *, field_name: str, min_value: float, max_value: float):
    if value is None:
        return None
    try:
        numeric = float(value)
    except Exception as exc:  # pragma: no cover
        raise FieldValidationError(
            f"{field_name} rejected value {value!r}"
        ) from exc
    if numeric < min_value or numeric > max_value:
        raise FieldValidationError(f"{field_name} rejected value {value!r}")
    return numeric


def _color_converter(value):
    if value is None:
        return None
    if isinstance(value, Color):
        return value
    if isinstance(value, str):
        return Color(rgb=value)
    raise FieldValidationError(f"color rejected value {value!r}")


def _underline_converter(value):
    if value is None or value == "none":
        return None
    allowed = {
        "single",
        "double",
        "singleAccounting",
        "doubleAccounting",
    }
    if value not in allowed:
        raise FieldValidationError(f"u rejected value {value!r}")
    return value


def _vert_align_converter(value):
    if value is None or value == "none":
        return None
    allowed = {"superscript", "subscript", "baseline"}
    if value not in allowed:
        raise FieldValidationError(f"vertAlign rejected value {value!r}")
    return value


def _scheme_converter(value):
    if value is None or value == "none":
        return None
    allowed = {"major", "minor"}
    if value not in allowed:
        raise FieldValidationError(f"scheme rejected value {value!r}")
    return value


[docs] class Font(Serialisable): """Font options used in styles.""" UNDERLINE_DOUBLE = 'double' UNDERLINE_DOUBLE_ACCOUNTING = 'doubleAccounting' UNDERLINE_SINGLE = 'single' UNDERLINE_SINGLE_ACCOUNTING = 'singleAccounting' tagname = "font" xml_order = ( "name", "charset", "family", "b", "i", "strike", "outline", "shadow", "condense", "color", "extend", "sz", "u", "vertAlign", "scheme", ) name: str | None = Field.nested_value(expected_type=str, allow_none=True, default=None) charset: int | None = Field.nested_value(expected_type=int, allow_none=True, default=None) family: float | None = Field.nested_value( expected_type=float, allow_none=True, converter=lambda v: _validate_range(v, field_name="family", min_value=0, max_value=14), default=None, ) sz: float | None = Field.nested_value(expected_type=float, allow_none=True, default=None) size: float | None = AliasField("sz", default=None) b: bool | None = Field.nested_bool(renderer=_no_value, default=None) bold: bool | None = AliasField("b", default=None) i: bool | None = Field.nested_bool(renderer=_no_value, default=None) italic: bool | None = AliasField("i", default=None) strike: bool | None = Field.nested_bool(allow_none=True, default=None) strikethrough: bool | None = AliasField("strike", default=None) outline: bool | None = Field.nested_bool(allow_none=True, default=None) shadow: bool | None = Field.nested_bool(allow_none=True, default=None) condense: bool | None = Field.nested_bool(allow_none=True, default=None) color: Color | None = Field.element( expected_type=Color, allow_none=True, converter=_color_converter, default=None, ) extend: bool | None = Field.nested_bool(allow_none=True, default=None) u: str | None = Field.nested_value( expected_type=str, allow_none=True, converter=_underline_converter, default=None, ) underline: str | None = AliasField("u", default=None) vertAlign: str | None = Field.nested_value( expected_type=str, allow_none=True, converter=_vert_align_converter, default=None, ) scheme: str | None = Field.nested_value( expected_type=str, allow_none=True, converter=_scheme_converter, default=None, ) def __init__(self, name=None, sz=None, b=None, i=None, charset=None, u=None, strike=None, color=None, scheme=None, family=None, size=None, bold=None, italic=None, strikethrough=None, underline=None, vertAlign=None, outline=None, shadow=None, condense=None, extend=None): self.name = name self.family = family if size is not None: sz = size self.sz = sz if bold is not None: b = bold self.b = b if italic is not None: i = italic self.i = i if underline is not None: u = underline self.u = u if strikethrough is not None: strike = strikethrough self.strike = strike self.color = color self.vertAlign = vertAlign self.charset = charset self.outline = outline self.shadow = shadow self.condense = condense self.extend = extend self.scheme = scheme
[docs] @classmethod def from_tree(cls, node): """ Set default value for underline if child element is present """ underline = node.find("{%s}u" % SHEET_MAIN_NS) if underline is not None and underline.get('val') is None: underline.set("val", "single") return super().from_tree(node)
def __copy__(self): cp = super().__copy__() # `b` and `i` use a custom renderer that omits the element when falsey. # During copy via XML round-trip that means `False` becomes `None` unless # we explicitly preserve the semantic value here. if self.b is False and cp.b is None: cp.b = False if self.i is False and cp.i is None: cp.i = False return cp
DEFAULT_FONT = Font(name="Calibri", sz=11, family=2, b=False, i=False, color=Color(theme=1), scheme="minor")