Clock rendering (DirectWrite)

Renderer API (clock-windows)

ItemWhereContract
font_size_px(pt, dpi), scale_dip, padding_pxrender.rsThe only DPI conversions. Direct2D runs at 96 DPI over physical pixels; fonts carry the scale (pt × dpi / 72). None for zero inputs.
PixelSize::of(PhysicalRect)render.rsSigned physical rectangles are reduced to a size only at the render boundary. Positions (including negative monitor coordinates) never enter layout.
WorstCase::for_format(&CompiledFormat)render.rsEvery distinct line the format yields over 2026‑01‑01..2028‑12‑31 × five wall times: all month/day names, year boundaries, ISO week 53 (2026), leap day (2028), AM/PM. Content-addressed fingerprint(). Compute once per committed preferences.
TextMeasurer, required_size, plan_fit → Fit::{Fits(Layout), Overflow(Overflow)}render.rsPure and fully unit tested. Width = widest candidate + both paddings; height = per-line tallest candidate, summed. Overflow carries required/available and width_exceeded()/height_exceeded().
Rendererrender/native.rsUI-thread factories (single-threaded D2D, shared DirectWrite) and the system UI font (lfMessageFont, Segoe UI fallback). refresh_system_font() after settings changes. !Send.
ClockSurface::paint(&Renderer, &PaintRequest) -> Result<PaintOutcome, RenderError>render/native.rsPainted, Unchanged (identical text/palette/font/region on a live device) or Overflow. Overflow draws nothing: the runtime must hide that monitor’s overlay.
ClockSurface::{mark_dirty, invalidate_device}render/native.rsForce a repaint; discard device-dependent objects (what D2DERR_RECREATE_TARGET does internally, with one automatic retry).
theme::{resolve, Palette, SystemAppearance, AppearanceTracker}theme.rsHigh contrast (system COLOR_WINDOW/COLOR_WINDOWTEXT) always wins; otherwise explicit Light/Dark; System follows SystemUsesLightTheme. Opaque colors only; no pixel sampling.
theme::{system_appearance, is_appearance_message}theme.rsWindows-only queries. The tracker yields the reducer’s ThemeChanged { revision } only when the resolved appearance really changed.

How the runtime drives it

On CreateOverlay/SetBounds size the HWND to the verified clock rectangle and call paint on Effect::Paint. Build PaintRequest from the reducer’s committed preferences (font_size_pt, theme → resolve(theme, tracker.current())), the overlay’s discovered dpi, the effect’s ClockText and a WorstCase computed from preferences.format(). Hide the overlay on PaintOutcome::Overflow (and on Err) and surface the Overflow values in Settings/diagnostics. Route WM_SETTINGCHANGE/WM_THEMECHANGED/WM_SYSCOLORCHANGE (is_appearance_message) to AppearanceTracker::observe, and call Renderer::refresh_system_font. A WM_PAINT, WM_DPICHANGED or WM_DISPLAYCHANGE must call mark_dirty (the overlay prototype shows the pattern: the window procedure only bumps an epoch counter and validates the rectangle).

Behavior notes

Resource ownership

Everything is owned by the UI thread that created it and is !Send: Renderer (D2D/DWrite factories), each ClockSurface (ID2D1HwndRenderTarget, brush, text format) and the HWND passed in, which the caller creates and destroys. All objects are COM references released by Drop; there are no raw handles. Drop a surface before destroying its window (the overlay prototype declares the surface field first). Nothing native crosses a worker channel.

Tests

Portable tests cover DPI conversion at 100/125/150/200%, DIP rounding/saturation, negative-coordinate regions, worst-case coverage (months, ISO week 53, year boundary, leap day), width/height overflow, exact-fit boundaries, “future text overflows though today fits”, invalid measurements and palette/high-contrast resolution. tests/render_native.rs adds real DirectWrite/Direct2D checks (all four scales, no-repaint/theme repaint, long-format overflow, future month overflow, height overflow, resize, and a device-loss + surface-churn handle/GDI/USER count check), run by the Windows CI job.