fix(pptx_to_svg): decode <a:hslClr> hue with the correct unit scale#102
Conversation
…) scale
`resolve_color` for the `hslClr` branch divided the `hue` attribute by
60000.0, leaving the value in plain degrees (e.g. 240 for blue). The
downstream `_hsl_to_hex` helper, however, expects a fraction in [0, 1)
and only does `h = h % 1.0`, so a 240° input collapses to `0.0` and
every `<a:hslClr>` color resolves as pure red regardless of the actual
hue.
Divide by `21_600_000` (= 60000 × 360) instead so the result is already
normalized to [0, 1) before reaching `_hsl_to_hex`, matching the unit
that the helper documents.
Repro (before the fix):
>>> from xml.etree import ElementTree as ET
>>> from pptx_to_svg.color_resolver import resolve_color
>>> ns = 'xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"'
>>> for hue, label in [(0,'red'),(7200000,'green 120°'),
... (14400000,'blue 240°'),(10800000,'cyan 180°')]:
... elem = ET.fromstring(f'<a:hslClr {ns} hue="{hue}" sat="100000" lum="50000"/>')
... print(label, resolve_color(elem, None)[0])
red #FF0000
green 120° #FF0000 ← expected #00FF00
blue 240° #FF0000 ← expected #0000FF
cyan 180° #FF0000 ← expected #00FFFF
After the fix all four cases round-trip the full 0–360° colour wheel
(verified locally for red / yellow / green / cyan / blue / magenta /
360° wrap / 720° wrap, plus the sat=0 grey and lum=0/100% black/white
edges).
Other branches (`srgbClr`, `schemeClr`, `sysClr`, `prstClr`,
`scrgbClr`) are untouched, and the `_hsl_to_hex` helper is unchanged so
no other callers are affected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep it up! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Thanks @voidborne-d for the surgical fix! 🙏 The diagnosis is spot-on — the I re-ran your repro locally — the 6 primary/secondary hues plus the 360°/720° wrap and the sat=0 / lum=0 / lum=100% edge cases all match expectations. Squash-merged into |
Problem
resolve_color()inskills/ppt-master/scripts/pptx_to_svg/color_resolver.py:217-221decodes a DrawingML<a:hslClr>element by dividing thehueattribute by60000.0. The DrawingML attribute is in 1/60000-degree units, so the result is hue in degrees (0..360).The downstream helper
_hsl_to_hex(h, s, l)(same file, lines ~340-345) however expects hue as a fraction in [0, 1) — it doesh = h % 1.0before handing off tocolorsys.hls_to_rgb. So any non-zero hue collapses to0.0and every<a:hslClr>color decodes as pure red regardless of its actual hue.Repro (on
main@ 2026-05-14, no fix applied)Before the fix:
Fix
Divide by
21_600_000(= 60000 × 360) so the hue is already normalized to a[0, 1)fraction before reaching_hsl_to_hex. Comment updated alongside.After the fix the same repro snippet returns the expected hex values for the full 0–360° wheel.
Local verification
Beyond the 6 primary/secondary hues above, I also walked the boundary cases:
hue=21600000 sat=100000 lum=50000#FF0000hue=43200000 sat=100000 lum=50000#FF0000hue=14400000 sat=0 lum=50000#808080hue=7200000 sat=100000 lum=0#000000hue=7200000 sat=100000 lum=100000#FFFFFFI also reran the other 5 colour branches (
srgbClr,schemeClr,sysClr,prstClr,scrgbClr) plus ansrgbClr+<a:alpha>modifier — none of them touch_hsl_to_hexand all still resolve to the same values as before.Notes
_hsl_to_hexhelper itself is left untouched — itsh = h % 1.0 if h != 1.0 else 1.0contract makes sense for a fraction-input helper, and changing it would silently affect any future caller that already passes a fraction.CONTRIBUTING.md(Not a fit: Adding CI, test frameworks ...), no test file is included. The verification snippet above is fully copy-pasteable for hand-running if you'd like.<a:hslClr>is rare in PowerPoint-authored decks (most usesrgbClr/schemeClr), but it does appear in some third-party DrawingML output and in certain theme variants — and when it does appear today, every such fill silently turns pure red.🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com