Files
Rozpisky/Rozpisky/Dxf/Exporters.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

143 lines
6.4 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.
using System.IO;
using SkiaSharp;
using Rozpisky.Rendering;
namespace Rozpisky.Dxf;
/// <summary>
/// Vykreslí libovolnou kresbu (<see cref="Action{IProfileRenderer}"/>) na A4 do PNG/PDF
/// nebo do PNG bytů pro náhled (ROZPISKY.md §5). Kresba se vystředí a kreslí 1:1; zmenší se
/// jen, když se do tisknutelné plochy nevejde.
/// </summary>
public static class Exporters
{
private const double A4Wmm = 210, A4Hmm = 297;
private const double MmPerInch = 25.4;
private const double PtPerMm = 72.0 / 25.4;
/// <summary>A4 raster do PNG (default 200 dpi). marginMm = 0: rozpiska má v DXF vlastní
/// A4 rámeček (0,0)(210,297), který se mapuje 1:1 na papír (žádný přidaný okraj → vystředěno).</summary>
public static void PngA4(Action<IProfileRenderer> draw, string path, double dpi = 200,
bool landscape = false, double marginMm = 0,
(double X, double Y, double W, double H)? frame = null)
{
double pageWmm = landscape ? A4Hmm : A4Wmm, pageHmm = landscape ? A4Wmm : A4Hmm;
double pxPerMm = dpi / MmPerInch;
int wpx = (int)Math.Round(pageWmm * pxPerMm), hpx = (int)Math.Round(pageHmm * pxPerMm);
using var bmp = new SKBitmap(wpx, hpx);
using var canvas = new SKCanvas(bmp);
canvas.Clear(SKColors.White);
RenderPage(canvas, wpx, hpx, pageWmm, pageHmm, pxPerMm, marginMm, draw, minStrokePx: 1, frame);
using var img = SKImage.FromBitmap(bmp);
using var data = img.Encode(SKEncodedImageFormat.Png, 100);
using var fs = File.Create(path);
data.SaveTo(fs);
}
/// <summary>A4 vektor do PDF (1:1). marginMm = 0: DXF už obsahuje vlastní A4 rámeček, mapuje se
/// 1:1 na stránku, takže rozpiska vyjde vystředěná na papír přesně dle návrhu (žádné zmenšení).</summary>
public static void PdfA4(Action<IProfileRenderer> draw, string path,
bool landscape = false, double marginMm = 0,
(double X, double Y, double W, double H)? frame = null)
{
double pageWmm = landscape ? A4Hmm : A4Wmm, pageHmm = landscape ? A4Wmm : A4Hmm;
double wpt = pageWmm * PtPerMm, hpt = pageHmm * PtPerMm;
using var fs = File.Create(path);
using var doc = SKDocument.CreatePdf(fs);
var canvas = doc.BeginPage((float)wpt, (float)hpt);
RenderPage(canvas, wpt, hpt, pageWmm, pageHmm, PtPerMm, marginMm, draw, minStrokePx: 0, frame);
doc.EndPage();
doc.Close();
}
/// <summary>Živý náhled na plátno (SKElement): vystředí a vyplní referenční obdélník (rámeček
/// šablony, jinak rozsah kresby) do pixelového boxu jako „výchozí fit" (zoom=1), na to navrch
/// aplikuje uživatelský zoom a posun v px. Kresba je vektorová → ostrá v každém přiblížení.</summary>
public static void RenderView(SKCanvas canvas, int wpx, int hpx, Action<IProfileRenderer> draw,
(double X, double Y, double W, double H)? frame,
double zoom, double panXpx, double panYpx, double marginPx = 14)
{
canvas.Clear(SKColors.White);
double bMinX, bMinY, bW, bH;
if (frame is { } f)
{
(bMinX, bMinY, bW, bH) = (f.X, f.Y, f.W, f.H);
}
else
{
var b = Bounds(draw);
if (b.IsEmpty) return;
(bMinX, bMinY, bW, bH) = (b.MinX, b.MinY, b.Width, b.Height);
}
if (bW <= 0 || bH <= 0) return;
double basePxPerMm = Math.Min((wpx - 2 * marginPx) / bW, (hpx - 2 * marginPx) / bH);
// Uživatelský zoom/posun: canvas se transformuje před přehráním kresby, takže se škáluje
// vše včetně tloušťky čar a velikosti glyfů → Skia je rasterizuje ostře v cílovém měřítku.
canvas.Save();
canvas.Translate((float)panXpx, (float)panYpx);
canvas.Scale((float)zoom, (float)zoom);
RenderInto(canvas, wpx, hpx, bMinX, bMinY, bW, bH, basePxPerMm, draw, minStrokePx: 1);
canvas.Restore();
}
// --- jádro ---
private static BoundsRenderer Bounds(Action<IProfileRenderer> draw)
{
var b = new BoundsRenderer();
draw(b);
return b;
}
/// <summary>A4 stránka: referenční obdélník (A4 rámeček ze šablony, jinak rozsah kresby) se mapuje
/// 1:1 a vystředí na papír; zmenší se jen když se nevejde do plochy bez okrajů.</summary>
private static void RenderPage(SKCanvas canvas, double wpx, double hpx,
double pageWmm, double pageHmm, double pxPerMm, double marginMm,
Action<IProfileRenderer> draw, double minStrokePx,
(double X, double Y, double W, double H)? frame)
{
double bMinX, bMinY, bW, bH;
if (frame is { } f)
{
(bMinX, bMinY, bW, bH) = (f.X, f.Y, f.W, f.H);
}
else
{
var b = Bounds(draw);
if (b.IsEmpty) return;
(bMinX, bMinY, bW, bH) = (b.MinX, b.MinY, b.Width, b.Height);
}
double availWmm = pageWmm - 2 * marginMm, availHmm = pageHmm - 2 * marginMm;
double scale = 1.0;
if (bW > availWmm || bH > availHmm)
scale = Math.Min(availWmm / bW, availHmm / bH);
RenderInto(canvas, (int)Math.Round(wpx), (int)Math.Round(hpx), bMinX, bMinY, bW, bH, pxPerMm * scale, draw, minStrokePx);
}
/// <summary>Vystředí referenční obdélník do plátna a přehraje kresbu (mm → px, osa Y nahoru).
/// Co přesahuje obdélník (např. přetečený text), se ořízne na hraně plátna.</summary>
private static void RenderInto(SKCanvas canvas, int wpx, int hpx,
double bMinX, double bMinY, double bWidth, double bHeight,
double pxPerDrawingMm, Action<IProfileRenderer> draw, double minStrokePx)
{
double drawWpx = bWidth * pxPerDrawingMm, drawHpx = bHeight * pxPerDrawingMm;
double offX = (wpx - drawWpx) / 2.0;
double offYfromBottom = (hpx - drawHpx) / 2.0;
SKPoint Map(double x, double y) => new(
(float)(offX + (x - bMinX) * pxPerDrawingMm),
(float)(hpx - (offYfromBottom + (y - bMinY) * pxPerDrawingMm)));
using var r = new SkiaProfileRenderer(canvas, Map, pxPerDrawingMm, minStrokePx);
draw(r);
}
}