simple-resume 0.1.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simple_resume/__init__.py +132 -0
- simple_resume/core/__init__.py +47 -0
- simple_resume/core/colors.py +215 -0
- simple_resume/core/config.py +672 -0
- simple_resume/core/constants/__init__.py +207 -0
- simple_resume/core/constants/colors.py +98 -0
- simple_resume/core/constants/files.py +28 -0
- simple_resume/core/constants/layout.py +58 -0
- simple_resume/core/dependencies.py +258 -0
- simple_resume/core/effects.py +154 -0
- simple_resume/core/exceptions.py +261 -0
- simple_resume/core/file_operations.py +68 -0
- simple_resume/core/generate/__init__.py +21 -0
- simple_resume/core/generate/exceptions.py +69 -0
- simple_resume/core/generate/html.py +233 -0
- simple_resume/core/generate/pdf.py +659 -0
- simple_resume/core/generate/plan.py +131 -0
- simple_resume/core/hydration.py +55 -0
- simple_resume/core/importers/__init__.py +3 -0
- simple_resume/core/importers/json_resume.py +284 -0
- simple_resume/core/latex/__init__.py +60 -0
- simple_resume/core/latex/context.py +56 -0
- simple_resume/core/latex/conversion.py +227 -0
- simple_resume/core/latex/escaping.py +68 -0
- simple_resume/core/latex/fonts.py +93 -0
- simple_resume/core/latex/formatting.py +81 -0
- simple_resume/core/latex/sections.py +218 -0
- simple_resume/core/latex/types.py +84 -0
- simple_resume/core/markdown.py +127 -0
- simple_resume/core/models.py +102 -0
- simple_resume/core/palettes/__init__.py +38 -0
- simple_resume/core/palettes/common.py +73 -0
- simple_resume/core/palettes/data/default_palettes.json +58 -0
- simple_resume/core/palettes/exceptions.py +33 -0
- simple_resume/core/palettes/fetch_types.py +52 -0
- simple_resume/core/palettes/generators.py +137 -0
- simple_resume/core/palettes/registry.py +76 -0
- simple_resume/core/palettes/resolution.py +123 -0
- simple_resume/core/palettes/sources.py +162 -0
- simple_resume/core/paths.py +21 -0
- simple_resume/core/protocols.py +134 -0
- simple_resume/core/py.typed +0 -0
- simple_resume/core/render/__init__.py +37 -0
- simple_resume/core/render/manage.py +199 -0
- simple_resume/core/render/plan.py +405 -0
- simple_resume/core/result.py +226 -0
- simple_resume/core/resume.py +609 -0
- simple_resume/core/skills.py +60 -0
- simple_resume/core/validation.py +321 -0
- simple_resume/py.typed +0 -0
- simple_resume/shell/__init__.py +3 -0
- simple_resume/shell/assets/static/css/README.md +213 -0
- simple_resume/shell/assets/static/css/common.css +641 -0
- simple_resume/shell/assets/static/css/fonts.css +42 -0
- simple_resume/shell/assets/static/css/preview.css +82 -0
- simple_resume/shell/assets/static/css/print.css +99 -0
- simple_resume/shell/assets/static/fonts/AvenirLTStd-Book.otf +0 -0
- simple_resume/shell/assets/static/fonts/AvenirLTStd-Light.otf +0 -0
- simple_resume/shell/assets/static/fonts/AvenirLTStd-Medium.otf +0 -0
- simple_resume/shell/assets/static/fonts/AvenirLTStd-Oblique.otf +0 -0
- simple_resume/shell/assets/static/fonts/AvenirLTStd-Roman.otf +0 -0
- simple_resume/shell/assets/static/fonts/fontawesome/Font Awesome 6 Brands-Regular-400.otf +0 -0
- simple_resume/shell/assets/static/fonts/fontawesome/Font Awesome 6 Free-Solid-900.otf +0 -0
- simple_resume/shell/assets/static/images/default_profile_1.jpg +0 -0
- simple_resume/shell/assets/static/images/default_profile_2.png +0 -0
- simple_resume/shell/assets/static/schema.json +236 -0
- simple_resume/shell/assets/static/themes/README.md +208 -0
- simple_resume/shell/assets/static/themes/bold.yaml +64 -0
- simple_resume/shell/assets/static/themes/classic.yaml +64 -0
- simple_resume/shell/assets/static/themes/executive.yaml +64 -0
- simple_resume/shell/assets/static/themes/minimal.yaml +64 -0
- simple_resume/shell/assets/static/themes/modern.yaml +64 -0
- simple_resume/shell/assets/templates/html/cover.html +129 -0
- simple_resume/shell/assets/templates/html/demo.html +13 -0
- simple_resume/shell/assets/templates/html/resume_base.html +453 -0
- simple_resume/shell/assets/templates/html/resume_no_bars.html +316 -0
- simple_resume/shell/assets/templates/html/resume_with_bars.html +362 -0
- simple_resume/shell/cli/__init__.py +35 -0
- simple_resume/shell/cli/main.py +975 -0
- simple_resume/shell/cli/palette.py +75 -0
- simple_resume/shell/cli/random_palette_demo.py +407 -0
- simple_resume/shell/config.py +96 -0
- simple_resume/shell/effect_executor.py +211 -0
- simple_resume/shell/file_opener.py +308 -0
- simple_resume/shell/generate/__init__.py +37 -0
- simple_resume/shell/generate/core.py +650 -0
- simple_resume/shell/generate/lazy.py +284 -0
- simple_resume/shell/io_utils.py +199 -0
- simple_resume/shell/palettes/__init__.py +1 -0
- simple_resume/shell/palettes/fetch.py +63 -0
- simple_resume/shell/palettes/loader.py +321 -0
- simple_resume/shell/palettes/remote.py +179 -0
- simple_resume/shell/pdf_executor.py +52 -0
- simple_resume/shell/py.typed +0 -0
- simple_resume/shell/render/__init__.py +1 -0
- simple_resume/shell/render/latex.py +308 -0
- simple_resume/shell/render/operations.py +240 -0
- simple_resume/shell/resume_extensions.py +737 -0
- simple_resume/shell/runtime/__init__.py +7 -0
- simple_resume/shell/runtime/content.py +190 -0
- simple_resume/shell/runtime/generate.py +497 -0
- simple_resume/shell/runtime/lazy.py +138 -0
- simple_resume/shell/runtime/lazy_import.py +173 -0
- simple_resume/shell/service_locator.py +80 -0
- simple_resume/shell/services.py +256 -0
- simple_resume/shell/session/__init__.py +6 -0
- simple_resume/shell/session/config.py +35 -0
- simple_resume/shell/session/manage.py +386 -0
- simple_resume/shell/strategies.py +181 -0
- simple_resume/shell/themes/__init__.py +35 -0
- simple_resume/shell/themes/loader.py +230 -0
- simple_resume-0.1.9.dist-info/METADATA +201 -0
- simple_resume-0.1.9.dist-info/RECORD +116 -0
- simple_resume-0.1.9.dist-info/WHEEL +4 -0
- simple_resume-0.1.9.dist-info/entry_points.txt +5 -0
- simple_resume-0.1.9.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Define the Simple Resume public API.
|
|
3
|
+
|
|
4
|
+
Symbols listed in `:data:simple_resume.__all__` are covered by the
|
|
5
|
+
stability contract, mirroring pandas' curated ``pandas.api`` surface.
|
|
6
|
+
Other components (utility helpers, palette plumbing, rendering shell, etc.)
|
|
7
|
+
reside under `:mod:simple_resume.core` and `:mod:simple_resume.shell`
|
|
8
|
+
and may change without notice. Import from those modules only if prepared
|
|
9
|
+
to track upstream changes.
|
|
10
|
+
|
|
11
|
+
High-level categories include:
|
|
12
|
+
|
|
13
|
+
* **Core models** – `:class:Resume`, `:class:ResumeConfig`, and
|
|
14
|
+
`:class:RenderPlan` represent resumes and render plans.
|
|
15
|
+
* **Sessions & results** – `:class:ResumeSession`, `:class:SessionConfig`,
|
|
16
|
+
`:class:GenerationResult`, and `:class:BatchGenerationResult`.
|
|
17
|
+
* **Generation helpers** – ``generate_pdf/html/all/resume`` plus new
|
|
18
|
+
convenience wrappers `:func:generate` and `:func:preview` for one-liner
|
|
19
|
+
workflows, similar to ``requests`` verb helpers.
|
|
20
|
+
* **FCIS Architecture** – Functional core in `:mod:simple_resume.core`
|
|
21
|
+
(e.g., `:mod:simple_resume.core.colors`) provides pure functions,
|
|
22
|
+
while shell layer handles I/O and side effects.
|
|
23
|
+
|
|
24
|
+
Refer to ``README.md`` and ``wiki/architecture/ADR003-api-surface-design.md``
|
|
25
|
+
for the API map and stability notes.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
# Exception hierarchy
|
|
31
|
+
from simple_resume.core.exceptions import (
|
|
32
|
+
ConfigurationError,
|
|
33
|
+
FileSystemError,
|
|
34
|
+
GenerationError,
|
|
35
|
+
PaletteError,
|
|
36
|
+
SessionError,
|
|
37
|
+
SimpleResumeError,
|
|
38
|
+
TemplateError,
|
|
39
|
+
ValidationError,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Core classes (data models only - no I/O methods)
|
|
43
|
+
from simple_resume.core.models import (
|
|
44
|
+
GenerationConfig,
|
|
45
|
+
RenderPlan,
|
|
46
|
+
ResumeConfig,
|
|
47
|
+
ValidationResult,
|
|
48
|
+
)
|
|
49
|
+
from simple_resume.core.resume import Resume
|
|
50
|
+
|
|
51
|
+
# Public API namespaces - higher-level generation functions
|
|
52
|
+
from simple_resume.shell.generate import (
|
|
53
|
+
generate,
|
|
54
|
+
generate_all,
|
|
55
|
+
generate_html,
|
|
56
|
+
generate_pdf,
|
|
57
|
+
generate_resume,
|
|
58
|
+
preview,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Shell layer I/O operations - these are the primary generation functions
|
|
62
|
+
from simple_resume.shell.resume_extensions import (
|
|
63
|
+
generate as resume_generate,
|
|
64
|
+
)
|
|
65
|
+
from simple_resume.shell.resume_extensions import (
|
|
66
|
+
to_html,
|
|
67
|
+
to_pdf,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Rich result objects (lazy-loaded)
|
|
71
|
+
from simple_resume.shell.runtime.lazy_import import (
|
|
72
|
+
lazy_BatchGenerationResult as BatchGenerationResult,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Session management (lazy-loaded)
|
|
76
|
+
from simple_resume.shell.runtime.lazy_import import (
|
|
77
|
+
lazy_create_session as create_session,
|
|
78
|
+
)
|
|
79
|
+
from simple_resume.shell.runtime.lazy_import import (
|
|
80
|
+
lazy_GenerationMetadata as GenerationMetadata,
|
|
81
|
+
)
|
|
82
|
+
from simple_resume.shell.runtime.lazy_import import (
|
|
83
|
+
lazy_GenerationResult as GenerationResult,
|
|
84
|
+
)
|
|
85
|
+
from simple_resume.shell.runtime.lazy_import import (
|
|
86
|
+
lazy_ResumeSession as ResumeSession,
|
|
87
|
+
)
|
|
88
|
+
from simple_resume.shell.runtime.lazy_import import (
|
|
89
|
+
lazy_SessionConfig as SessionConfig,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Version
|
|
93
|
+
__version__ = "0.1.9"
|
|
94
|
+
|
|
95
|
+
# Public API exports - organized by functionality
|
|
96
|
+
__all__ = [
|
|
97
|
+
"__version__",
|
|
98
|
+
# Core models (data only)
|
|
99
|
+
"Resume",
|
|
100
|
+
"ResumeConfig",
|
|
101
|
+
"RenderPlan",
|
|
102
|
+
"ValidationResult",
|
|
103
|
+
# Exceptions
|
|
104
|
+
"SimpleResumeError",
|
|
105
|
+
"ValidationError",
|
|
106
|
+
"ConfigurationError",
|
|
107
|
+
"TemplateError",
|
|
108
|
+
"GenerationError",
|
|
109
|
+
"PaletteError",
|
|
110
|
+
"FileSystemError",
|
|
111
|
+
"SessionError",
|
|
112
|
+
# Results & sessions
|
|
113
|
+
"GenerationResult",
|
|
114
|
+
"GenerationMetadata",
|
|
115
|
+
"BatchGenerationResult",
|
|
116
|
+
"ResumeSession",
|
|
117
|
+
"SessionConfig",
|
|
118
|
+
"create_session",
|
|
119
|
+
# Generation primitives
|
|
120
|
+
"GenerationConfig",
|
|
121
|
+
"generate_pdf",
|
|
122
|
+
"generate_html",
|
|
123
|
+
"generate_all",
|
|
124
|
+
"generate_resume",
|
|
125
|
+
# Shell layer I/O functions
|
|
126
|
+
"to_pdf",
|
|
127
|
+
"to_html",
|
|
128
|
+
"resume_generate",
|
|
129
|
+
# Convenience helpers
|
|
130
|
+
"generate",
|
|
131
|
+
"preview",
|
|
132
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Core resume data transformations - pure functions without side effects."""
|
|
2
|
+
|
|
3
|
+
from simple_resume.core.file_operations import (
|
|
4
|
+
find_yaml_files,
|
|
5
|
+
get_resume_name_from_path,
|
|
6
|
+
iterate_yaml_files,
|
|
7
|
+
)
|
|
8
|
+
from simple_resume.core.models import (
|
|
9
|
+
RenderMode,
|
|
10
|
+
RenderPlan,
|
|
11
|
+
ResumeConfig,
|
|
12
|
+
ValidationResult,
|
|
13
|
+
)
|
|
14
|
+
from simple_resume.core.render import (
|
|
15
|
+
prepare_html_generation_request,
|
|
16
|
+
prepare_pdf_generation_request,
|
|
17
|
+
validate_render_plan,
|
|
18
|
+
)
|
|
19
|
+
from simple_resume.core.render.plan import (
|
|
20
|
+
build_render_plan,
|
|
21
|
+
normalize_with_palette_fallback,
|
|
22
|
+
prepare_render_data,
|
|
23
|
+
transform_for_mode,
|
|
24
|
+
validate_resume_config,
|
|
25
|
+
validate_resume_config_or_raise,
|
|
26
|
+
)
|
|
27
|
+
from simple_resume.core.resume import Resume
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"Resume",
|
|
31
|
+
"ResumeConfig",
|
|
32
|
+
"RenderPlan",
|
|
33
|
+
"ValidationResult",
|
|
34
|
+
"RenderMode",
|
|
35
|
+
"find_yaml_files",
|
|
36
|
+
"get_resume_name_from_path",
|
|
37
|
+
"iterate_yaml_files",
|
|
38
|
+
"build_render_plan",
|
|
39
|
+
"normalize_with_palette_fallback",
|
|
40
|
+
"prepare_render_data",
|
|
41
|
+
"transform_for_mode",
|
|
42
|
+
"validate_resume_config",
|
|
43
|
+
"validate_resume_config_or_raise",
|
|
44
|
+
"prepare_html_generation_request",
|
|
45
|
+
"prepare_pdf_generation_request",
|
|
46
|
+
"validate_render_plan",
|
|
47
|
+
]
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Color calculation helpers that belong to the core domain layer.
|
|
2
|
+
|
|
3
|
+
This module centralizes hex parsing, luminance math, and contrast helpers so
|
|
4
|
+
domain services (and utility shims) do not need to re-implement
|
|
5
|
+
workarounds to avoid circular imports.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from simple_resume.core.constants.colors import (
|
|
14
|
+
BOLD_DARKEN_FACTOR,
|
|
15
|
+
DEFAULT_COLOR_SCHEME,
|
|
16
|
+
HEX_COLOR_FULL_LENGTH,
|
|
17
|
+
HEX_COLOR_SHORT_LENGTH,
|
|
18
|
+
ICON_CONTRAST_THRESHOLD,
|
|
19
|
+
LUMINANCE_DARK,
|
|
20
|
+
LUMINANCE_VERY_DARK,
|
|
21
|
+
LUMINANCE_VERY_LIGHT,
|
|
22
|
+
WCAG_LINEARIZATION_DIVISOR,
|
|
23
|
+
WCAG_LINEARIZATION_EXPONENT,
|
|
24
|
+
WCAG_LINEARIZATION_OFFSET,
|
|
25
|
+
WCAG_LINEARIZATION_THRESHOLD,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"calculate_contrast_ratio",
|
|
30
|
+
"calculate_icon_contrast_color",
|
|
31
|
+
"calculate_luminance",
|
|
32
|
+
"ColorCalculationService",
|
|
33
|
+
"darken_color",
|
|
34
|
+
"get_contrasting_text_color",
|
|
35
|
+
"hex_to_rgb",
|
|
36
|
+
"is_valid_color",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def hex_to_rgb(hex_color: str) -> tuple[int, int, int]:
|
|
41
|
+
"""Convert hex color to an RGB tuple."""
|
|
42
|
+
processed = hex_color.lstrip("#")
|
|
43
|
+
if len(processed) == HEX_COLOR_SHORT_LENGTH:
|
|
44
|
+
processed = "".join(char * 2 for char in processed)
|
|
45
|
+
if len(processed) != HEX_COLOR_FULL_LENGTH:
|
|
46
|
+
raise ValueError(f"Invalid hex color: {hex_color}")
|
|
47
|
+
try:
|
|
48
|
+
r = int(processed[0:2], 16)
|
|
49
|
+
g = int(processed[2:4], 16)
|
|
50
|
+
b = int(processed[4:6], 16)
|
|
51
|
+
return r, g, b
|
|
52
|
+
except ValueError as exc: # pragma: no cover - defensive
|
|
53
|
+
raise ValueError(f"Invalid hex color: {hex_color}") from exc
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def darken_color(hex_color: str, factor: float) -> str:
|
|
57
|
+
"""Return a darker variant of the provided hex color."""
|
|
58
|
+
try:
|
|
59
|
+
rgb = hex_to_rgb(hex_color)
|
|
60
|
+
except ValueError:
|
|
61
|
+
return "#585858"
|
|
62
|
+
|
|
63
|
+
darkened = tuple(max(0, min(255, round(component * factor))) for component in rgb)
|
|
64
|
+
return "#{:02X}{:02X}{:02X}".format(*darkened)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _calculate_luminance_from_rgb(rgb: tuple[int, int, int]) -> float:
|
|
68
|
+
"""Calculate relative luminance from an RGB tuple using WCAG 2.1 formula.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
rgb: An RGB color tuple (e.g., (255, 255, 255)).
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
The relative luminance (0.0 to 1.0).
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
r, g, b = rgb
|
|
78
|
+
|
|
79
|
+
def _linearize(component: int) -> float:
|
|
80
|
+
value = component / 255.0
|
|
81
|
+
return (
|
|
82
|
+
value / WCAG_LINEARIZATION_DIVISOR
|
|
83
|
+
if value <= WCAG_LINEARIZATION_THRESHOLD
|
|
84
|
+
else ((value + WCAG_LINEARIZATION_OFFSET) / (1 + WCAG_LINEARIZATION_OFFSET))
|
|
85
|
+
** WCAG_LINEARIZATION_EXPONENT
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
r_linear = _linearize(r)
|
|
89
|
+
g_linear = _linearize(g)
|
|
90
|
+
b_linear = _linearize(b)
|
|
91
|
+
|
|
92
|
+
return 0.2126 * r_linear + 0.7152 * g_linear + 0.0722 * b_linear
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def calculate_luminance(hex_color: str) -> float:
|
|
96
|
+
"""Return the relative luminance for ``hex_color``."""
|
|
97
|
+
rgb = hex_to_rgb(hex_color)
|
|
98
|
+
return _calculate_luminance_from_rgb(rgb)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def calculate_contrast_ratio(color1: str, color2: str) -> float:
|
|
102
|
+
"""Return the WCAG contrast ratio between two hex colors."""
|
|
103
|
+
rgb1 = hex_to_rgb(color1)
|
|
104
|
+
rgb2 = hex_to_rgb(color2)
|
|
105
|
+
lum1 = _calculate_luminance_from_rgb(rgb1)
|
|
106
|
+
lum2 = _calculate_luminance_from_rgb(rgb2)
|
|
107
|
+
lighter = max(lum1, lum2)
|
|
108
|
+
darker = min(lum1, lum2)
|
|
109
|
+
return (lighter + 0.05) / (darker + 0.05)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_contrasting_text_color(background_color: str) -> str:
|
|
113
|
+
"""Return a readable text color for the provided background."""
|
|
114
|
+
try:
|
|
115
|
+
luminance = calculate_luminance(background_color)
|
|
116
|
+
if luminance <= LUMINANCE_VERY_DARK:
|
|
117
|
+
return "#F5F5F5"
|
|
118
|
+
if luminance <= LUMINANCE_DARK:
|
|
119
|
+
return "#FFFFFF"
|
|
120
|
+
if luminance >= LUMINANCE_VERY_LIGHT:
|
|
121
|
+
return "#333333"
|
|
122
|
+
return "#000000"
|
|
123
|
+
except (ValueError, TypeError): # pragma: no cover - defensive
|
|
124
|
+
return "#000000"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def calculate_icon_contrast_color(
|
|
128
|
+
user_color: str | None,
|
|
129
|
+
background_color: str,
|
|
130
|
+
*,
|
|
131
|
+
contrast_threshold: float = ICON_CONTRAST_THRESHOLD,
|
|
132
|
+
) -> str:
|
|
133
|
+
"""Return an icon color that respects the WCAG AA contrast threshold."""
|
|
134
|
+
if user_color and is_valid_color(user_color):
|
|
135
|
+
ratio = calculate_contrast_ratio(user_color, background_color)
|
|
136
|
+
if ratio >= contrast_threshold:
|
|
137
|
+
return user_color
|
|
138
|
+
return get_contrasting_text_color(background_color)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def is_valid_color(color: str) -> bool:
|
|
142
|
+
"""Check if a color string is a valid hex color code."""
|
|
143
|
+
if not color:
|
|
144
|
+
return False
|
|
145
|
+
return bool(re.match(r"^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$", color))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class ColorCalculationService:
|
|
149
|
+
"""Service for calculating various color values based on configuration."""
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def calculate_sidebar_text_color(config: dict[str, Any]) -> str:
|
|
153
|
+
"""Calculate sidebar text color based on sidebar background."""
|
|
154
|
+
sidebar_color = config.get("sidebar_color")
|
|
155
|
+
if sidebar_color and is_valid_color(sidebar_color):
|
|
156
|
+
return get_contrasting_text_color(sidebar_color)
|
|
157
|
+
return DEFAULT_COLOR_SCHEME.get("sidebar_text_color", "#000000")
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def calculate_sidebar_bold_color(config: dict[str, Any]) -> str:
|
|
161
|
+
"""Calculate sidebar bold color based on sidebar background."""
|
|
162
|
+
sidebar_color = config.get("sidebar_color")
|
|
163
|
+
if sidebar_color and is_valid_color(sidebar_color):
|
|
164
|
+
# Make the bold color slightly darker than the background
|
|
165
|
+
return darken_color(sidebar_color, BOLD_DARKEN_FACTOR)
|
|
166
|
+
return "#000000" # Default black
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def calculate_heading_icon_color(config: dict[str, Any]) -> str:
|
|
170
|
+
"""Calculate heading icon color based on configuration."""
|
|
171
|
+
user_icon_color = config.get("heading_icon_color")
|
|
172
|
+
frame_color = config.get("frame_color", "#000000")
|
|
173
|
+
|
|
174
|
+
if isinstance(user_icon_color, str) and is_valid_color(user_icon_color):
|
|
175
|
+
return user_icon_color
|
|
176
|
+
theme_color = config.get("theme_color")
|
|
177
|
+
if isinstance(theme_color, str) and is_valid_color(theme_color):
|
|
178
|
+
return theme_color
|
|
179
|
+
return str(frame_color)
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def calculate_sidebar_icon_color(config: dict[str, Any]) -> str:
|
|
183
|
+
"""Calculate sidebar icon color based on configuration."""
|
|
184
|
+
sidebar_color = config.get("sidebar_color")
|
|
185
|
+
if sidebar_color and is_valid_color(sidebar_color):
|
|
186
|
+
return get_contrasting_text_color(sidebar_color)
|
|
187
|
+
return "#FFFFFF" # Default white icon
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def ensure_color_contrast(
|
|
191
|
+
background: str,
|
|
192
|
+
text_color: str,
|
|
193
|
+
*,
|
|
194
|
+
contrast_threshold: float = 3.0,
|
|
195
|
+
) -> str:
|
|
196
|
+
"""Ensure text color has sufficient contrast against background.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
background: Background color in hex format
|
|
200
|
+
text_color: Text color in hex format
|
|
201
|
+
contrast_threshold: Minimum contrast ratio required
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Original text color if contrast is sufficient, otherwise a fallback color
|
|
205
|
+
|
|
206
|
+
"""
|
|
207
|
+
if not is_valid_color(background) or not is_valid_color(text_color):
|
|
208
|
+
return "#333333" # Default fallback color
|
|
209
|
+
|
|
210
|
+
contrast = calculate_contrast_ratio(text_color, background)
|
|
211
|
+
if contrast >= contrast_threshold:
|
|
212
|
+
return text_color
|
|
213
|
+
|
|
214
|
+
# Return fallback color with better contrast
|
|
215
|
+
return get_contrasting_text_color(background)
|