Každá šrafa se kreslila jako plná výplň, bez ohledu na vzor. Logo md_sfdi.dxf má 3× ANSI31 (šikmé čáry) – vycházel z něj plný blok. Nový SrafaVzor rozvine definiční čáry vzoru na úsečky: rodina rovnoběžek podle vektoru Offset, ořezaná hranicemi šrafy sudo-lichou paritou (takže díry ve vnořených smyčkách vyjdou správně) a čárkováním z DashLengths. Proti zamrznutí náhledu při vadném měřítku ve zdroji je strop 20 000 úseček na šrafu. Rozhraní: Fill(loops) dostává druhý parametr Vypln. Rastrové renderery kreslí rozvinuté čáry (Skia vzory neumí), DXF export je ignoruje a zapíše zpátky HATCH s původním jménem vzoru, měřítkem a úhlem – v CADu tak zůstane editovatelný. Když se vzor rozvinout nepodaří, plocha zůstane prázdná; zaplácnout ji načerno je horší než ji nevyplnit. Ověřeno i vizuálně: z 9 dodávaných log se změnilo jen md_sfdi, ostatních 8 je po renderu do PNG pixel po pixelu shodných s předchozím stavem. 105 testů zelených. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
72 lines
3.0 KiB
C#
72 lines
3.0 KiB
C#
namespace Rozpisky.Rendering;
|
||
|
||
/// <summary>
|
||
/// Nic nekreslí – jen sčítá min/max souřadnic (ROZPISKY.md §3). Stejnou kresbou se zjistí
|
||
/// rozsah pro vystředění na A4 / „zoom to fit".
|
||
/// </summary>
|
||
public sealed class BoundsRenderer : IProfileRenderer
|
||
{
|
||
public double MinX { get; private set; } = double.MaxValue;
|
||
public double MinY { get; private set; } = double.MaxValue;
|
||
public double MaxX { get; private set; } = double.MinValue;
|
||
public double MaxY { get; private set; } = double.MinValue;
|
||
|
||
public bool IsEmpty => MinX > MaxX;
|
||
public double Width => IsEmpty ? 0 : MaxX - MinX;
|
||
public double Height => IsEmpty ? 0 : MaxY - MinY;
|
||
|
||
private void Add(double x, double y)
|
||
{
|
||
if (x < MinX) MinX = x;
|
||
if (y < MinY) MinY = y;
|
||
if (x > MaxX) MaxX = x;
|
||
if (y > MaxY) MaxY = y;
|
||
}
|
||
|
||
public void Stav(in KresliciStav s) { }
|
||
|
||
public void Line(double x1, double y1, double x2, double y2) { Add(x1, y1); Add(x2, y2); }
|
||
|
||
public void Polyline(IReadOnlyList<(double X, double Y)> pts, bool closed = false)
|
||
{
|
||
foreach (var (x, y) in pts) Add(x, y);
|
||
}
|
||
|
||
public void Circle(double cx, double cy, double r) { Add(cx - r, cy - r); Add(cx + r, cy + r); }
|
||
|
||
public void Stroke(Contour c) => AddContour(c);
|
||
|
||
public 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)
|
||
{
|
||
// Hrubý odhad rozsahu (na vystředění stačí kotva + přibližná výška/šířka), víceřádkově.
|
||
var segs = (text ?? "").Replace("\\P", "\n").Replace('\r', '\n').Split('\n');
|
||
int longest = 0;
|
||
foreach (var s in segs) if (s.Length > longest) longest = s.Length;
|
||
double wf = widthFactor > 0 ? widthFactor : 1;
|
||
double w = longest * heightMm * 0.6 * wf
|
||
+ heightMm * Math.Abs(Math.Tan(style.ObliqueDeg * Math.PI / 180)); // přesah sklonu
|
||
double blockH = segs.Length * heightMm * 1.4;
|
||
// Kotva dle zarovnání: box roste od kotvy směrem daným H/V (dřív jen doprava nahoru).
|
||
double dx = h switch { HAlign.Center => -w / 2, HAlign.Right => -w, _ => 0 };
|
||
double dy = v switch { VAlign.Top => -blockH, VAlign.Middle => -blockH / 2, _ => 0 };
|
||
Add(x + dx, y + dy);
|
||
Add(x + dx + w, y + dy + blockH);
|
||
}
|
||
|
||
public void Fill(IReadOnlyList<Contour> loops, Vypln vypln = default)
|
||
{
|
||
foreach (var loop in loops) AddContour(loop);
|
||
}
|
||
|
||
/// <summary>Započítá obrys – oblouky navzorkuje na body (na bbox stačí hrubší tolerance).</summary>
|
||
private void AddContour(Contour c)
|
||
{
|
||
var pts = new List<(double X, double Y)>();
|
||
foreach (var e in c.Edges) e.AppendPoints(pts, maxSagitta: 0.1);
|
||
foreach (var (x, y) in pts) Add(x, y);
|
||
if (c.Edges.Count > 0) { Add(c.Edges[^1].EX, c.Edges[^1].EY); } // poslední koncový bod
|
||
}
|
||
}
|