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>
109 lines
6.1 KiB
C#
109 lines
6.1 KiB
C#
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(1−tol/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);
|
||
}
|