Files
Rozpisky/Rozpisky/Rendering/IProfileRenderer.cs
T
marekandClaude Opus 5 a6377ab583 Baseline: současný stav projektu Rozpisky
První commit — WPF aplikace pro generování výkresových rohových razítek
(.NET 10, SkiaSharp, ACadSharp, ClosedXML) včetně xUnit testů, DXF šablony,
log a dokumentace. Build výstupy (bin/, obj/, .vs/) jsou ignorovány.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 09:45:49 +02:00

109 lines
6.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace Rozpisky.Rendering;
/// <summary>Vodorovné zarovnání textu.</summary>
public enum HAlign { Left, Center, Right }
/// <summary>Svislé zarovnání textu.</summary>
public enum VAlign { Baseline, Bottom, Middle, Top }
/// <summary>
/// Vzhled písma přenesený z DXF textového stylu (ROZPISKY.md §3): rodina fontu + řez,
/// volitelně přímo soubor fontu (.ttf) pro věrný řez. <c>default</c> = fallback font rendereru.
/// <paramref name="ObliqueDeg"/> = sklon znaků ve stupních (DXF oblique angle, group 51 entity /
/// group 50 stylu); kladný = naklonění doprava, 0 = vzpřímeně.
/// </summary>
public readonly record struct TextStyle(string? FontFamily = null, bool Bold = false, bool Italic = false,
string? FontFile = null, double ObliqueDeg = 0);
/// <summary>Druh hrany obrysu: úsečka nebo kruhový oblouk. Elipsy a spliny se do obrysu vkládají
/// jako jemná řada úseček (viz <see cref="Edge"/>), takže renderery vystačí se dvěma případy.</summary>
public enum EdgeKind : byte { Line, Arc }
/// <summary>
/// Jedna hrana obrysu ve výkresových mm (WCS, osa Y nahoru). Nese <b>analytický</b> tvar
/// úsečku nebo kruhový oblouk aby renderery kreslily hladké křivky (Skia <c>ArcTo</c>, DXF
/// <c>ARC</c>/hatch oblouk) místo lomené čáry. Počátek i konec jsou uložené explicitně kvůli
/// řetězení a otáčení hran ve smyčce (hatch). Oblouk: střed <c>(Cx,Cy)</c>, poloměr <c>R</c>,
/// počáteční úhel <c>A0</c> a <b>znaménkový</b> rozsah <c>Sweep</c> v radiánech (kladný = proti
/// směru hodin); bod(θ) = střed + R·(cos θ, sin θ), θ ∈ [A0, A0+Sweep].
/// </summary>
public readonly record struct Edge(
EdgeKind Kind, double SX, double SY, double EX, double EY,
double Cx = 0, double Cy = 0, double R = 0, double A0 = 0, double Sweep = 0)
{
public static Edge Line(double sx, double sy, double ex, double ey)
=> new(EdgeKind.Line, sx, sy, ex, ey);
public static Edge Arc(double sx, double sy, double ex, double ey,
double cx, double cy, double r, double a0, double sweep)
=> new(EdgeKind.Arc, sx, sy, ex, ey, cx, cy, r, a0, sweep);
/// <summary>Otočí hranu (prohodí start/konec; u oblouku obrátí směr rozsahu).</summary>
public Edge Reversed() => Kind == EdgeKind.Line
? this with { SX = EX, SY = EY, EX = SX, EY = SY }
: this with { SX = EX, SY = EY, EX = SX, EY = SY, A0 = A0 + Sweep, Sweep = -Sweep };
/// <summary>Bod oblouku pro úhel θ (radiány).</summary>
public (double X, double Y) PointAt(double theta) => (Cx + R * Math.Cos(theta), Cy + R * Math.Sin(theta));
}
/// <summary>Obrys = zřetězené hrany (konec jedné = počátek další). <paramref name="Closed"/> = true →
/// vyplnitelná/uzavřená smyčka (poslední hrana se vrací k počátku prvního).</summary>
public readonly record struct Contour(IReadOnlyList<Edge> Edges, bool Closed);
/// <summary>Pomůcky pro převod obrysů na body jen pro <b>odhad rozsahu</b> (bbox) a nouzové
/// zálohy; skutečná kresba (Skia/DXF) zůstává analytická (hladké oblouky).</summary>
public static class ContourGeometry
{
/// <summary>Navzorkuje hranu na body včetně počátečního (koncový přidá navazující hrana / uzávěr).
/// Oblouk se dělí tak, aby výška úsečky (sagitta) nepřekročila <paramref name="maxSagitta"/> mm.</summary>
public static void AppendPoints(this Edge e, List<(double X, double Y)> pts, double maxSagitta = 0.05)
{
pts.Add((e.SX, e.SY));
if (e.Kind == EdgeKind.Line || e.R <= 0) return;
int n = ArcSegments(Math.Abs(e.Sweep), e.R, maxSagitta);
for (int i = 1; i < n; i++) pts.Add(e.PointAt(e.A0 + e.Sweep * i / n));
}
/// <summary>Počet dílků oblouku (poloměr <paramref name="r"/>, rozsah <paramref name="sweep"/> rad),
/// aby sagitta ≤ <paramref name="maxSagitta"/>: δ = 2·acos(1tol/r), n = ⌈|sweep|/δ⌉.</summary>
public static int ArcSegments(double sweep, double r, double maxSagitta)
{
if (r <= 0 || sweep <= 0) return 1;
double ratio = 1 - maxSagitta / r;
double delta = ratio <= -1 ? Math.PI : 2 * Math.Acos(Math.Max(ratio, -1));
if (delta < 1e-6) return 1;
return Math.Max(1, (int)Math.Ceiling(sweep / delta));
}
}
/// <summary>
/// Kreslicí rozhraní (ROZPISKY.md §3). Vše ve výkresových milimetrech, osa Y nahoru
/// (jako v CAD/DXF). Tatáž kresba teče na obrazovku, do PNG i do PDF liší se jen
/// implementace (Skia / bounds).
/// </summary>
public interface IProfileRenderer
{
/// <summary>Nastaví aktuální hladinu/styl kresby. <paramref name="rgb"/> = barva 0xRRGGBB
/// (rozbalená z DXF, ByLayer i index/true-color), tloušťka v mm. Bílá se kreslí černě
/// (bílá na bílém papíře by zmizela).</summary>
void Layer(string name, int rgb, double lineWeightMm = 0.25);
void Line(double x1, double y1, double x2, double y2);
void Polyline(IReadOnlyList<(double X, double Y)> pts, bool closed = false);
void Circle(double cx, double cy, double r);
/// <summary>Vykreslí (obrysově) obrys se zachovanými kruhovými oblouky LwPolyline/Polyline2D
/// s bulge, samostatný ARC apod. Hladké v každém přiblížení i v PDF.</summary>
void Stroke(Contour c);
/// <param name="widthFactor">Horizontální měřítko písma (DXF width factor); 1 = beze změny.</param>
/// <param name="maxWidthMm">Šířka pro zalomení víceřádkového textu v mm; 0 = bez zalomení (jen explicitní zlomy).</param>
void Text(double x, double y, string text, double heightMm,
HAlign h = HAlign.Left, VAlign v = VAlign.Baseline, double rotationDeg = 0,
TextStyle style = default, double widthFactor = 1, double maxWidthMm = 0);
/// <summary>Vyplní oblast danou jednou či více uzavřenými obrysy (even-odd → vnořené smyčky
/// tvoří díry). Obrysy nesou analytické oblouky (viz <see cref="Contour"/>), takže výplně z
/// DXF HATCH např. loga mají hladké okraje v každém přiblížení i v PDF.</summary>
void Fill(IReadOnlyList<Contour> loops);
}