Skip to content

Commit d6801d2

Browse files
committed
Name the repeated colour-swap idioms in pptx for readability (no behaviour change)
The dark-mode post-pass inlined two idioms several times each; give them a name so the value/expression lives in one place: - _rgb_key(rgb): the (int(rgb[0]), int(rgb[1]), int(rgb[2])) tuple used as the light->dark map key — was inlined 3x across _swap_fill / _swap_text_colors. - _rgb_hex(rgb): the "%02X%02X%02X" srgbClr `val` string — was inlined in the cell-border drawer and the border-recolour pass. - _DARK_BODY_TEXT: the #E5E7EB near-white dark-mode body colour, promoted from a per-call local in _swap_text_colors to a module constant beside _DARK_SLIDE_BG (single source of truth, matching the _BRAND_* / _LIGHT_TO_DARK_TEXT style). Pure refactor — 601 tests pass unchanged (incl. the dark-mode contract, no-invisible-runs, and no-red-text regressions that exercise these passes); ruff + bandit clean.
1 parent 2a847ca commit d6801d2

1 file changed

Lines changed: 25 additions & 10 deletions

File tree

thesisagents/exporters/pptx.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@
158158
# Dark-mode palette (post-build recolour, opt-in via
159159
# ``ExportOptions.dark_mode``).
160160
_DARK_SLIDE_BG = RGBColor(0x12, 0x15, 0x1B)
161+
# Near-white body-text colour for dark mode. The post-build recolour pass
162+
# promotes any run lacking an explicit colour (theme inheritance → renders
163+
# black on the dark slide) to this, and it's the swap target for _BRAND_DARK
164+
# body text in _LIGHT_TO_DARK_TEXT below. Named so the value lives in one place.
165+
_DARK_BODY_TEXT = RGBColor(0xE5, 0xE7, 0xEB)
161166

162167
# Light-palette RGB → dark-palette RGB mapping for TEXT colours. Keys
163168
# are 3-tuples (R, G, B) since python-pptx's RGBColor is tuple-comparable
@@ -201,6 +206,20 @@
201206
_TABLE_DIVIDER = RGBColor(0xD0, 0xD7, 0xE2) # row divider — soft grey-blue
202207
_TABLE_HEADER_RULE = RGBColor(0x1F, 0x3A, 0x66) # heavy nav rule under header
203208

209+
210+
def _rgb_key(rgb) -> tuple[int, int, int]:
211+
"""An RGBColor as a hashable ``(r, g, b)`` int tuple — the key type the
212+
light→dark palette maps above are indexed by. Centralises the
213+
``(int(rgb[0]), int(rgb[1]), int(rgb[2]))`` idiom used by every swap pass."""
214+
return (int(rgb[0]), int(rgb[1]), int(rgb[2]))
215+
216+
217+
def _rgb_hex(rgb) -> str:
218+
"""6-char uppercase hex (no ``#``) for an RGBColor or ``(r, g, b)`` tuple —
219+
the form OOXML's ``<a:srgbClr val=...>`` attribute expects."""
220+
return f"{rgb[0]:02X}{rgb[1]:02X}{rgb[2]:02X}"
221+
222+
204223
# ---------------------------------------------------------------------------
205224
# Abstract segmentation (fallback when summary is absent / lightweight only)
206225
# ---------------------------------------------------------------------------
@@ -1741,8 +1760,7 @@ def _set_cell_border(cell, edge: str, width, colour: RGBColor) -> None:
17411760
nsmap=None,
17421761
)
17431762
solid = ln.makeelement(qn("a:solidFill"), {}, nsmap=None)
1744-
rgb_hex = f"{colour[0]:02X}{colour[1]:02X}{colour[2]:02X}"
1745-
solid.append(solid.makeelement(qn("a:srgbClr"), {"val": rgb_hex}, nsmap=None))
1763+
solid.append(solid.makeelement(qn("a:srgbClr"), {"val": _rgb_hex(colour)}, nsmap=None))
17461764
ln.append(solid)
17471765
ln.append(ln.makeelement(qn("a:prstDash"), {"val": "solid"}, nsmap=None))
17481766
ln.append(ln.makeelement(qn("a:round"), {}, nsmap=None))
@@ -2091,8 +2109,7 @@ def _swap_fill(shape_or_cell) -> None:
20912109
return
20922110
if rgb is None:
20932111
return
2094-
key = (int(rgb[0]), int(rgb[1]), int(rgb[2]))
2095-
new = _LIGHT_TO_DARK_FILL.get(key)
2112+
new = _LIGHT_TO_DARK_FILL.get(_rgb_key(rgb))
20962113
if new is None:
20972114
return
20982115
fill.solid()
@@ -2112,18 +2129,16 @@ def _swap_text_colors(shape_or_cell) -> None:
21122129
text_frame = getattr(shape_or_cell, "text_frame", None)
21132130
if text_frame is None:
21142131
return
2115-
near_white = RGBColor(0xE5, 0xE7, 0xEB)
21162132
for paragraph in text_frame.paragraphs:
21172133
for run in paragraph.runs:
21182134
try:
21192135
rgb = run.font.color.rgb
21202136
except (AttributeError, ValueError, TypeError):
21212137
rgb = None
2122-
if rgb is None or (int(rgb[0]), int(rgb[1]), int(rgb[2])) == (0, 0, 0):
2123-
run.font.color.rgb = near_white
2138+
if rgb is None or _rgb_key(rgb) == (0, 0, 0):
2139+
run.font.color.rgb = _DARK_BODY_TEXT
21242140
continue
2125-
key = (int(rgb[0]), int(rgb[1]), int(rgb[2]))
2126-
new = _LIGHT_TO_DARK_TEXT.get(key)
2141+
new = _LIGHT_TO_DARK_TEXT.get(_rgb_key(rgb))
21272142
if new is not None:
21282143
run.font.color.rgb = RGBColor(*new)
21292144

@@ -2155,4 +2170,4 @@ def _swap_cell_border_colors(cell) -> None:
21552170
new = _LIGHT_TO_DARK_FILL.get(key)
21562171
if new is None:
21572172
continue
2158-
clr.set("val", f"{new[0]:02X}{new[1]:02X}{new[2]:02X}")
2173+
clr.set("val", _rgb_hex(new))

0 commit comments

Comments
 (0)