First commit. Version 0.1.0
This commit is contained in:
112
src/render/helpers.rs
Normal file
112
src/render/helpers.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
//! Backend-neutral helpers for the software renderer: rounded-rect
|
||||
//! path construction + system-font lookup.
|
||||
|
||||
use tiny_skia::{ Path, PathBuilder };
|
||||
|
||||
use crate::types::Corners;
|
||||
|
||||
/// Cubic bezier control-point factor for a quarter-circle approximation
|
||||
/// (`(4/3) * (sqrt(2) - 1) ≈ 0.5523`).
|
||||
const KAPPA: f32 = 0.5523_f32;
|
||||
|
||||
/// Build a rounded rectangle path with independent per-corner radii
|
||||
/// using cubic bezier curves. Each corner is clamped against the
|
||||
/// inscribed-circle limit `min(width, height) / 2` before drawing,
|
||||
/// so callers can pass theme pill sentinels (e.g. `RADIUS = 100`) and
|
||||
/// still get a well-formed pill on a small rect.
|
||||
pub ( super ) fn build_rounded_rect( rect: tiny_skia::Rect, corners: Corners ) -> Option<Path>
|
||||
{
|
||||
let c = corners.clamp_to_size( rect.width(), rect.height() );
|
||||
let tl = c.tl;
|
||||
let tr = c.tr;
|
||||
let br = c.br;
|
||||
let bl = c.bl;
|
||||
|
||||
let x0 = rect.left();
|
||||
let y0 = rect.top();
|
||||
let x1 = rect.right();
|
||||
let y1 = rect.bottom();
|
||||
|
||||
let mut pb = PathBuilder::new();
|
||||
pb.move_to( x0 + tl, y0 );
|
||||
pb.line_to( x1 - tr, y0 );
|
||||
if tr > 0.0
|
||||
{
|
||||
let kk = tr * KAPPA;
|
||||
pb.cubic_to( x1 - tr + kk, y0, x1, y0 + tr - kk, x1, y0 + tr );
|
||||
}
|
||||
pb.line_to( x1, y1 - br );
|
||||
if br > 0.0
|
||||
{
|
||||
let kk = br * KAPPA;
|
||||
pb.cubic_to( x1, y1 - br + kk, x1 - br + kk, y1, x1 - br, y1 );
|
||||
}
|
||||
pb.line_to( x0 + bl, y1 );
|
||||
if bl > 0.0
|
||||
{
|
||||
let kk = bl * KAPPA;
|
||||
pb.cubic_to( x0 + bl - kk, y1, x0, y1 - bl + kk, x0, y1 - bl );
|
||||
}
|
||||
pb.line_to( x0, y0 + tl );
|
||||
if tl > 0.0
|
||||
{
|
||||
let kk = tl * KAPPA;
|
||||
pb.cubic_to( x0, y0 + tl - kk, x0 + tl - kk, y0, x0 + tl, y0 );
|
||||
}
|
||||
pb.close();
|
||||
pb.finish()
|
||||
}
|
||||
|
||||
/// System-font search chain, ordered by preference. Shared by
|
||||
/// [`find_font`] (which panics when none match) and
|
||||
/// [`find_font_opt`] (which returns `None` — used by tests that
|
||||
/// want to skip gracefully on images without the usual fonts
|
||||
/// installed).
|
||||
const SYSTEM_FONT_CANDIDATES: &[&str] =
|
||||
&[
|
||||
// Debian `fonts-sora` — the canonical path `ltk-theme-default`
|
||||
// depends on. Listed first so Sora wins as the default font
|
||||
// whenever the package is installed.
|
||||
"/usr/share/fonts/opentype/sora/Sora-Regular.otf",
|
||||
"/usr/share/fonts/truetype/sora/Sora-Regular.ttf",
|
||||
"/usr/share/fonts/sora/Sora-Regular.ttf",
|
||||
"/usr/share/fonts/TTF/Sora-Regular.ttf",
|
||||
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
|
||||
"/usr/share/fonts/liberation/LiberationSans-Regular.ttf",
|
||||
"/usr/share/fonts/truetype/freefont/FreeSans.ttf",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/dejavu/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/TTF/DejaVuSans.ttf",
|
||||
];
|
||||
|
||||
/// Resolve the first system font available from
|
||||
/// [`SYSTEM_FONT_CANDIDATES`], or `None` if none exist. Used by
|
||||
/// tests; runtime code uses [`find_font`].
|
||||
pub ( crate ) fn find_font_opt() -> Option<String>
|
||||
{
|
||||
SYSTEM_FONT_CANDIDATES.iter()
|
||||
.find( |p| std::path::Path::new( p ).exists() )
|
||||
.copied()
|
||||
.map( str::to_string )
|
||||
}
|
||||
|
||||
/// Load the bytes of a default system font. Tries the candidate chain
|
||||
/// via [`find_font_opt`]; falls back to the embedded
|
||||
/// [`crate::theme::fallback::FALLBACK_FONT`] (Sora Regular, ~50 KB,
|
||||
/// OFL 1.1) when nothing matches or the file cannot be read. Always
|
||||
/// returns usable bytes so canvas construction never panics on a
|
||||
/// system without the expected fonts.
|
||||
pub ( super ) fn load_default_font_bytes() -> Vec<u8>
|
||||
{
|
||||
if let Some( path ) = find_font_opt()
|
||||
{
|
||||
if let Ok( bytes ) = std::fs::read( &path )
|
||||
{
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
crate::theme::fallback::FALLBACK_FONT.to_vec()
|
||||
}
|
||||
Reference in New Issue
Block a user