diff --git a/Rozpisky.Tests/AciPaletaTesty.cs b/Rozpisky.Tests/AciPaletaTesty.cs
new file mode 100644
index 0000000..080f848
--- /dev/null
+++ b/Rozpisky.Tests/AciPaletaTesty.cs
@@ -0,0 +1,87 @@
+using Rozpisky.Rendering;
+using Xunit;
+using AcColor = ACadSharp.Color;
+
+namespace Rozpisky.Tests;
+
+///
+/// Paleta indexovaných barev AutoCADu. Rozhoduje o tom, jak vypadá náhled proti CADu –
+/// v exportu se index zapisuje číslem, takže tahle tabulka do souboru nikdy neproteče.
+///
+public class AciPaletaTesty
+{
+ [Theory]
+ [InlineData(1, 0xFF0000)] [InlineData(2, 0xFFFF00)] [InlineData(3, 0x00FF00)]
+ [InlineData(4, 0x00FFFF)] [InlineData(5, 0x0000FF)] [InlineData(6, 0xFF00FF)]
+ [InlineData(7, 0xFFFFFF)] [InlineData(8, 0x414141)] [InlineData(9, 0x808080)]
+ [InlineData(255, 0xFFFFFF)] [InlineData(250, 0x333333)]
+ public void ZakladniBarvySedle(int index, int rgb) => Assert.Equal(rgb, AciPaleta.Rgb(index));
+
+ [Theory]
+ // Odstínová část: první čtveřice ze skupiny červené a modrozelené, ověřená proti AutoCADu.
+ [InlineData(10, 0xFF0000)] [InlineData(11, 0xFF7F7F)] [InlineData(12, 0xA50000)]
+ [InlineData(13, 0xA55252)] [InlineData(20, 0xFF3F00)] [InlineData(21, 0xFF9F7F)]
+ [InlineData(22, 0xA52900)] [InlineData(136, 0x004C4C)] [InlineData(18, 0x260000)]
+ public void OdstinySeDopocitavajiSpravne(int index, int rgb) => Assert.Equal(rgb, AciPaleta.Rgb(index));
+
+ [Fact]
+ public void MimoRozsahJeCerna()
+ {
+ Assert.Equal(0x000000, AciPaleta.Rgb(-1));
+ Assert.Equal(0x000000, AciPaleta.Rgb(256));
+ Assert.Equal(0x000000, AciPaleta.Rgb(0)); // 0 = ByBlock, samostatně se nekreslí
+ }
+
+ [Fact]
+ public void KazdyOdstinMaVlastniBarvu()
+ {
+ // 24 skupin po 10 – žádné dvě sousední skupiny nesmí vyjít stejně. Přesně tímhle se
+ // ACadSharp plete (29 a 39 tam mají tutéž barvu), a proto máme paletu vlastní.
+ for (int skupina = 0; skupina + 1 < 24; skupina++)
+ for (int pozice = 0; pozice < 10; pozice++)
+ Assert.NotEqual(AciPaleta.Rgb(10 + skupina * 10 + pozice),
+ AciPaleta.Rgb(20 + skupina * 10 + pozice));
+ }
+
+ /// Indexy, kde se ACadSharp.Color.GetIndexRGB od AutoCADu liší o víc než
+ /// zaokrouhlení: dva stupně šedi (8, 9), šedý klín na konci palety (250–254) a poslední
+ /// index většiny desítek odstínů (29, 119, 139, …), kde ACadSharp bere barvu ze sousední
+ /// skupiny – proto tam vycházejí i barvy mimo jas dané pozice.
+ private static readonly int[] ZnameOdchylky =
+ {
+ 8, 9, 29, 91, 109, 119, 129, 139, 145, 147, 149, 157, 159, 167, 169, 177, 179,
+ 187, 189, 192, 197, 199, 203, 209, 219, 229, 239, 249, 250, 251, 252, 253, 254,
+ };
+
+ [Fact]
+ public void ShodujeSeSAcadSharpKromeZnamychOdchylek()
+ {
+ // Pojistka proti tichému rozejití při upgradu balíčku. Jinde než v seznamu se smí lišit
+ // nanejvýš o 1 na kanál (jiné zaokrouhlení při dopočtu odstínu) – to okem nepoznat.
+ var zname = new HashSet(ZnameOdchylky);
+ var novaOdchylka = new List();
+ var zmizelaOdchylka = new List();
+
+ for (int i = 0; i <= 255; i++)
+ {
+ var acad = AcColor.GetIndexRGB((byte)i);
+ int nas = AciPaleta.Rgb(i);
+ int rozdil = Math.Max(Math.Abs(acad[0] - ((nas >> 16) & 0xFF)),
+ Math.Max(Math.Abs(acad[1] - ((nas >> 8) & 0xFF)),
+ Math.Abs(acad[2] - (nas & 0xFF))));
+ if (rozdil > 1 && !zname.Contains(i)) novaOdchylka.Add(i);
+ if (rozdil <= 1 && zname.Contains(i)) zmizelaOdchylka.Add(i);
+ }
+
+ Assert.Empty(novaOdchylka); // ACadSharp se rozešel s AutoCADem na dalším indexu
+ Assert.Empty(zmizelaOdchylka); // ACadSharp se opravil – seznam zúžit
+ }
+
+ [Fact]
+ public void NejblizsiIndexNajdePresnouShoduIPriblizek()
+ {
+ Assert.Equal(1, AciPaleta.NejblizsiIndex(0xFF0000));
+ Assert.Equal(5, AciPaleta.NejblizsiIndex(0x0000FF));
+ Assert.Equal(0xFF0000, AciPaleta.Rgb(AciPaleta.NejblizsiIndex(0xFE0101)));
+ }
+}
diff --git a/Rozpisky.Tests/BarvaTesty.cs b/Rozpisky.Tests/BarvaTesty.cs
new file mode 100644
index 0000000..ac30be8
--- /dev/null
+++ b/Rozpisky.Tests/BarvaTesty.cs
@@ -0,0 +1,64 @@
+using Rozpisky.Rendering;
+using Xunit;
+
+namespace Rozpisky.Tests;
+
+///
+/// Rozřešení stavu kresby: co se má nakreslit, když barvu neurčí entita, ale hladina.
+/// Chyba tady se projeví špatnou barvou v náhledu, ne pádem – proto explicitní testy.
+///
+public class BarvaTesty
+{
+ private static KresliciStav Stav(Barva entita, Barva hladina)
+ => new(HladinaStav.Vychozi with { Barva = hladina }, entita);
+
+ [Fact]
+ public void VlastniBarvaEntityPrebijiHladinu()
+ {
+ Assert.Equal(0x0000FF, Stav(Barva.Aci(5), Barva.Aci(1)).RgbProKresbu());
+ Assert.Equal(0x123456, Stav(Barva.Prima(0x123456), Barva.Aci(1)).RgbProKresbu());
+ }
+
+ [Fact]
+ public void ByLayerSiVezmeBarvuHladiny()
+ => Assert.Equal(0xFF0000, Stav(Barva.ZHladiny, Barva.Aci(1)).RgbProKresbu());
+
+ [Fact]
+ public void ByBlockMimoBlokSeChovaJakoByLayer()
+ => Assert.Equal(0xFF0000, Stav(Barva.ZBloku, Barva.Aci(1)).RgbProKresbu());
+
+ [Fact]
+ public void ByLayerNadHladinouBezBarvyDaCernou()
+ => Assert.Equal(0x000000, Stav(Barva.ZHladiny, Barva.ZHladiny).RgbProKresbu());
+
+ [Fact]
+ public void TloustkaPadaZEntityNaHladinuAPakNaVychozi()
+ {
+ var h = HladinaStav.Vychozi with { TloustkaMm = 0.5 };
+ Assert.Equal(0.35, new KresliciStav(h, Barva.ZHladiny, TloustkaMm: 0.35).TloustkaKresby(), 3);
+ Assert.Equal(0.5, new KresliciStav(h, Barva.ZHladiny).TloustkaKresby(), 3);
+ Assert.Equal(KresliciStav.VychoziTloustkaMm,
+ new KresliciStav(HladinaStav.Vychozi, Barva.ZHladiny).TloustkaKresby(), 3);
+ }
+
+ [Fact]
+ public void TypCaryPadaZEntityNaHladinuAPakNaContinuous()
+ {
+ var h = HladinaStav.Vychozi with { TypCary = "DASHED" };
+ Assert.Equal("CENTER", new KresliciStav(h, Barva.ZHladiny, TypCary: "CENTER").TypCaryKresby());
+ Assert.Equal("DASHED", new KresliciStav(h, Barva.ZHladiny).TypCaryKresby());
+ Assert.Equal("Continuous",
+ new KresliciStav(h with { TypCary = "" }, Barva.ZHladiny).TypCaryKresby());
+ }
+
+ [Fact]
+ public void IndexNeseIRgbProKresbu()
+ {
+ var b = Barva.Aci(5);
+ Assert.Equal(BarvaDruh.Index, b.Druh);
+ Assert.Equal(5, b.Index);
+ Assert.Equal(0x0000FF, b.Rgb);
+ Assert.True(b.JeVlastni);
+ Assert.False(Barva.ZHladiny.JeVlastni);
+ }
+}
diff --git a/Rozpisky.Tests/DxfBarvyRoundTripTesty.cs b/Rozpisky.Tests/DxfBarvyRoundTripTesty.cs
new file mode 100644
index 0000000..f467c39
--- /dev/null
+++ b/Rozpisky.Tests/DxfBarvyRoundTripTesty.cs
@@ -0,0 +1,99 @@
+using System.IO;
+using ACadSharp.Entities;
+using ACadSharp.IO;
+using Rozpisky.Dxf;
+using Rozpisky.Rendering;
+using Xunit;
+
+namespace Rozpisky.Tests;
+
+///
+/// Cesta barev tam a zpět: Rozpiska.dxf → → soubor →
+/// zpátky. Šablona i všech 9 log jsou zapsané výhradně indexovanými barvami AutoCADu
+/// (group 62); dřív z nich export dělal natvrdo RGB (group 420), takže výkres v CADu přestal
+/// reagovat na tabulku barev a ByLayer se rozpadlo na kopie barvy hladiny.
+///
+public class DxfBarvyRoundTripTesty : IDisposable
+{
+ private readonly string _soubor = Path.Combine(Path.GetTempPath(), "rozpisky-barvy-" + Guid.NewGuid().ToString("N") + ".dxf");
+
+ public void Dispose()
+ {
+ try { File.Delete(_soubor); } catch (IOException) { }
+ GC.SuppressFinalize(this);
+ }
+
+ /// Přehraje šablonu do DXF rendereru, zapíše a načte zpět.
+ private ACadSharp.CadDocument Kolecko(bool sRameckem = true)
+ {
+ var tpl = new DxfTemplate(TestData.Sablona);
+ var dxf = new DxfProfileRenderer();
+ tpl.Replay(dxf, values: null, includeNoPrint: sRameckem);
+ DxfWriter.Write(_soubor, dxf.Document, binary: true);
+ return DxfReader.Read(_soubor);
+ }
+
+ [Fact]
+ public void ZadnaEntitaNevyjdeJakoTrueColor()
+ {
+ var doc = Kolecko();
+ var prohresky = doc.Entities.OfType().Where(e => e.Color.IsTrueColor).ToList();
+ Assert.Empty(prohresky);
+ }
+
+ [Fact]
+ public void PouziteIndexyOdpovidajiZdroji()
+ {
+ // Indexy, které kreslítko dostalo ze šablony, musí být tytéž, jaké skončí v souboru.
+ var zaznam = new RecordingRenderer();
+ new DxfTemplate(TestData.Sablona).Replay(zaznam, values: null, includeNoPrint: true);
+ var zdrojove = zaznam.Stavy.Where(s => s.Barva.Druh == BarvaDruh.Index)
+ .Select(s => (int)s.Barva.Index).ToHashSet();
+
+ var doc = Kolecko();
+ var vysledne = doc.Entities.OfType()
+ .Where(e => !e.Color.IsByLayer && !e.Color.IsByBlock)
+ .Select(e => (int)e.Color.Index).ToHashSet();
+
+ Assert.NotEmpty(zdrojove);
+ Assert.Equal(zdrojove, vysledne);
+ }
+
+ [Fact]
+ public void ByLayerZustaneByLayer()
+ {
+ // Šablona má 41 entit s barvou 256 (ByLayer). Když se rozbalí na konkrétní RGB,
+ // přestane jít barva výkresu měnit přes hladinu – přesně to uživatel hlásí.
+ var zaznam = new RecordingRenderer();
+ new DxfTemplate(TestData.Sablona).Replay(zaznam, values: null, includeNoPrint: true);
+ int zdrojovych = zaznam.Stavy.Count(s => s.Barva.Druh == BarvaDruh.ByLayer);
+ Assert.True(zdrojovych > 0, "šablona žádnou ByLayer entitu nemá – test ztratil smysl");
+
+ var doc = Kolecko();
+ Assert.Contains(doc.Entities.OfType(), e => e.Color.IsByLayer);
+ }
+
+ [Fact]
+ public void HladinyMajiVlastnostiZTabulkyNeZPrvniEntity()
+ {
+ var zdroj = ACadSharp.IO.DxfReader.Read(TestData.Sablona);
+ var doc = Kolecko();
+
+ foreach (var vrstva in doc.Layers)
+ {
+ if (!zdroj.Layers.TryGetValue(vrstva.Name, out var puvodni)) continue;
+ Assert.Equal(puvodni.Color.Index, vrstva.Color.Index);
+ Assert.Equal(puvodni.PlotFlag, vrstva.PlotFlag);
+ }
+ }
+
+ [Fact]
+ public void RamecekNoPrintZustaneNetisknutelny()
+ {
+ // Rámeček A4 se do exportu kreslí jako reference, ale tisknout se nesmí.
+ var doc = Kolecko();
+ Assert.True(doc.Layers.TryGetValue(DxfTemplate.HladinaNoPrint, out var vrstva),
+ "hladina !!NOPRINT v exportu chybí");
+ Assert.False(vrstva!.PlotFlag);
+ }
+}
diff --git a/Rozpisky.Tests/ObliqueTesty.cs b/Rozpisky.Tests/ObliqueTesty.cs
index a794064..57959e4 100644
--- a/Rozpisky.Tests/ObliqueTesty.cs
+++ b/Rozpisky.Tests/ObliqueTesty.cs
@@ -41,7 +41,7 @@ public class ObliqueTesty
public void DxfExport_ZachovaObliqueNaTextu()
{
var dxf = new DxfProfileRenderer();
- dxf.Layer("0", 0x000000, 0.25);
+ dxf.Stav(new KresliciStav(HladinaStav.Vychozi, Barva.Aci(7), TloustkaMm: 0.25));
dxf.Text(10, 20, "Zkouška", 2.5, style: new TextStyle("Arial", ObliqueDeg: 3));
string path = Path.Combine(Path.GetTempPath(), $"rozpisky-oblique-{Guid.NewGuid():N}.dxf");
diff --git a/Rozpisky.Tests/ObloukyTesty.cs b/Rozpisky.Tests/ObloukyTesty.cs
index 86712ba..87cfb46 100644
--- a/Rozpisky.Tests/ObloukyTesty.cs
+++ b/Rozpisky.Tests/ObloukyTesty.cs
@@ -84,7 +84,7 @@ public class ObloukyTesty
var contour = new Contour(new[] { arc, line }, Closed: true);
var g = new DxfProfileRenderer();
- g.Layer("0", 0x000000);
+ g.Stav(KresliciStav.Vychozi);
g.Fill(new[] { contour });
// Export → dočasný DXF → import zpět a porovnání bboxu (sagitta ≤ 0,02 mm).
@@ -118,7 +118,7 @@ public class ObloukyTesty
var contour = new Contour(new[] { Edge.Arc(s.X, s.Y, e.X, e.Y, 1, 2, r, a0, sweep) }, Closed: false);
var g = new DxfProfileRenderer();
- g.Layer("0", 0);
+ g.Stav(KresliciStav.Vychozi);
g.Stroke(contour);
var arc = Assert.Single(g.Document.Entities.OfType());
diff --git a/Rozpisky.Tests/RecordingRenderer.cs b/Rozpisky.Tests/RecordingRenderer.cs
index 7e03f62..54fd95a 100644
--- a/Rozpisky.Tests/RecordingRenderer.cs
+++ b/Rozpisky.Tests/RecordingRenderer.cs
@@ -26,7 +26,12 @@ public sealed class RecordingRenderer : IProfileRenderer
public int LineCount { get; private set; }
public int CircleCount { get; private set; }
- public void Layer(string name, int rgb, double lineWeightMm = 0.25) { }
+ /// Stavy kresby v pořadí, jak přišly – testy tak můžou tvrdit i o barvě a hladině.
+ public List Stavy { get; } = new();
+ /// Stav platný pro naposledy zaznamenanou entitu.
+ public KresliciStav PosledniStav => Stavy.Count > 0 ? Stavy[^1] : KresliciStav.Vychozi;
+
+ public void Stav(in KresliciStav s) => Stavy.Add(s);
public void Line(double x1, double y1, double x2, double y2) => LineCount++;
diff --git a/Rozpisky.Tests/SkiaRenderTesty.cs b/Rozpisky.Tests/SkiaRenderTesty.cs
index b3e30bd..45cd678 100644
--- a/Rozpisky.Tests/SkiaRenderTesty.cs
+++ b/Rozpisky.Tests/SkiaRenderTesty.cs
@@ -36,7 +36,7 @@ public class SkiaRenderTesty
var edge = Edge.Arc(5, 0, 5, 0, 0, 0, r: 5, a0: 0, sweep: 2 * Math.PI);
var contour = new Contour(new[] { edge }, Closed: true);
- int filled = PocetVyplnenych(g => { g.Layer("0", 0); g.Fill(new[] { contour }); });
+ int filled = PocetVyplnenych(g => { g.Stav(KresliciStav.Vychozi); g.Fill(new[] { contour }); });
// Plocha kruhu r=5 mm × (10 px/mm)² = π·2500 ≈ 7854 px; tolerantně > 5000.
Assert.True(filled > 5000, $"plná kružnice se nevykreslila (vyplněno {filled} px)");
@@ -47,7 +47,7 @@ public class SkiaRenderTesty
{
// Kontrolní: běžný oblouk (< 360°) přes Stroke musí něco nakreslit.
var edge = Edge.Arc(5, 0, 0, 5, 0, 0, r: 5, a0: 0, sweep: Math.PI / 2);
- int filled = PocetVyplnenych(g => { g.Layer("0", 0); g.Stroke(new Contour(new[] { edge }, Closed: false)); });
+ int filled = PocetVyplnenych(g => { g.Stav(KresliciStav.Vychozi); g.Stroke(new Contour(new[] { edge }, Closed: false)); });
Assert.True(filled > 50, $"oblouk se nevykreslil (vyplněno {filled} px)");
}
}
diff --git a/Rozpisky.Tests/TestData.cs b/Rozpisky.Tests/TestData.cs
index 50d3988..c0ea1ae 100644
--- a/Rozpisky.Tests/TestData.cs
+++ b/Rozpisky.Tests/TestData.cs
@@ -11,6 +11,9 @@ public static class TestData
/// Plná cesta k logu ve zdrojové složce Rozpisky\Podklady\loga.
public static string Logo(string fileName) => Path.Combine(RepoRoot, "Rozpisky", "Podklady", "loga", fileName);
+ /// Plná cesta k DXF šabloně rozpisky.
+ public static string Sablona => Path.Combine(RepoRoot, "Rozpisky", "Podklady", "Rozpiska.dxf");
+
/// Všech 9 log dodávaných s programem.
public static readonly string[] VsechnaLoga =
{
diff --git a/Rozpisky/Dxf/DxfTemplate.cs b/Rozpisky/Dxf/DxfTemplate.cs
index 1a9e9e0..dceeb12 100644
--- a/Rozpisky/Dxf/DxfTemplate.cs
+++ b/Rozpisky/Dxf/DxfTemplate.cs
@@ -210,14 +210,41 @@ public sealed partial class DxfTemplate
bool noPrint = JeNoPrint(e);
if (noPrint && !includeNoPrint) return false; // vodicí rámeček vkládaného DXF se nekreslí
if (!noPrint && layer is not null && (!layer.PlotFlag || !layer.IsOn)) return false;
- var col = e.Color.IsByLayer && layer is not null ? layer.Color : e.Color; // ByLayer → barva hladiny
- int rgb = (col.R << 16) | (col.G << 8) | col.B; // ACadSharp rozbalí i index/true-color
int lw = (int)e.LineWeight; // LineWeightType: Wnn = nn/100 mm, záporné = ByLayer/Default/ByBlock
- if (lw < 0 && layer is not null) lw = (int)layer.LineWeight;
- g.Layer(layer?.Name ?? "0", rgb, lw > 0 ? lw / 100.0 : 0.18);
+ g.Stav(new KresliciStav(
+ HladinaZ(layer),
+ BarvaZ(e.Color),
+ e.LineType?.Name,
+ lw > 0 ? lw / 100.0 : null)); // null = tloušťka podle hladiny
return true;
}
+ /// Popis hladiny pro kreslítko – celý, ať se dá ve výstupu postavit znovu
+ /// (barva z tabulky hladin, tloušťka, typ čáry, příznak tisku).
+ private static HladinaStav HladinaZ(ACadSharp.Tables.Layer? layer)
+ {
+ if (layer is null) return HladinaStav.Vychozi;
+ int lw = (int)layer.LineWeight;
+ return new HladinaStav(
+ layer.Name,
+ BarvaZ(layer.Color),
+ layer.LineType?.Name ?? "Continuous",
+ lw > 0 ? lw / 100.0 : 0,
+ layer.PlotFlag,
+ layer.IsOn);
+ }
+
+ /// Barva z DXF i s původem: index (group 62), true color (420) nebo odkaz
+ /// ByLayer/ByBlock. Původ se nese dál, aby ho DXF export mohl zapsat beze změny –
+ /// zplošťování na RGB dřív z indexovaných barev dělalo natvrdo RGB.
+ private static Barva BarvaZ(ACadSharp.Color c)
+ {
+ if (c.IsByLayer) return Barva.ZHladiny;
+ if (c.IsByBlock) return Barva.ZBloku;
+ if (c.IsTrueColor) return Barva.Prima((c.R << 16) | (c.G << 8) | c.B);
+ return c.Index is >= 1 and <= 255 ? Barva.Aci(c.Index) : Barva.Aci(7);
+ }
+
/// Zjistí kotvu a zarovnání textu (Left+Baseline → vkládací bod, jinak zarovnávací bod).
private static (double x, double y, HAlign h, VAlign v) Place(
TextHorizontalAlignment ha, TextVerticalAlignmentType va, XYZ insert, XYZ align)
diff --git a/Rozpisky/Rendering/AciPaleta.cs b/Rozpisky/Rendering/AciPaleta.cs
new file mode 100644
index 0000000..6366bec
--- /dev/null
+++ b/Rozpisky/Rendering/AciPaleta.cs
@@ -0,0 +1,98 @@
+namespace Rozpisky.Rendering;
+
+///
+/// Paleta indexovaných barev AutoCADu (ACI 0–255). Index je to, co nese DXF group 62; na RGB se
+/// překládá až při rasterizaci (náhled, PNG, PDF) – do DXF se zapisuje zpátky jako číslo,
+/// takže se cestou tam a zpět nic neztratí.
+/// Rozsah 10–249 je dopočítaný, ne vypsaný: 24 odstínů po 15° × 5 jasů, každý ve variantě
+/// sytá / poloviční sytost. Krajní hodnoty (0–9, 250–255) jsou tabulkou, protože do schématu
+/// nezapadají.
+/// Proč ne ACadSharp.Color.GetIndexRGB: ta se od AutoCADu liší u indexů
+/// 8 a 9 (o dva stupně šedi), u 250–254 a u posledního indexu každé skupiny odstínů (19, 29, …,
+/// 249) – tam vrací hodnotu ze sousední skupiny, takže např. 29 a 39 dostanou stejnou barvu.
+/// Index 8 je přitom v Rozpiska.dxf použitý. Viz AciPaletaTesty.
+///
+public static class AciPaleta
+{
+ /// ACI 0–9. Index 0 (ByBlock) a 7 se kreslí podle pozadí – tady černá / bílá.
+ private static readonly int[] Zakladni =
+ {
+ 0x000000, // 0 – ByBlock, samostatně se nekreslí
+ 0xFF0000, 0xFFFF00, 0x00FF00, 0x00FFFF, 0x0000FF, 0xFF00FF, 0xFFFFFF,
+ 0x414141, // 8 – tmavá šedá (ACadSharp tu má 0x808080)
+ 0x808080, // 9 – šedá (ACadSharp tu má 0xC0C0C0)
+ };
+
+ /// ACI 250–255 – šedý klín zakončený bílou.
+ private static readonly int[] Sede = { 0x333333, 0x505050, 0x696969, 0x828282, 0xBEBEBE, 0xFFFFFF };
+
+ /// Jas pro pozici v desetici: pětice 100 / 65 / 50 / 30 / 15 %, každá dvakrát
+ /// (sudá pozice = plná sytost, lichá = poloviční).
+ private static readonly int[] Jasy = { 0xFF, 0xFF, 0xA5, 0xA5, 0x7F, 0x7F, 0x4C, 0x4C, 0x26, 0x26 };
+
+ /// RGB 0xRRGGBB pro daný ACI index. Mimo rozsah 0–255 → černá.
+ public static int Rgb(int index)
+ {
+ if (index is < 0 or > 255) return 0x000000;
+ if (index <= 9) return Zakladni[index];
+ if (index >= 250) return Sede[index - 250];
+ return Odstin(index);
+ }
+
+ /// Nejbližší ACI index k RGB (pro cizí soubory zapsané v true color). Prohledává
+ /// 1–255 podle eukleidovské vzdálenosti v RGB – hrubé, ale pro převod stačí.
+ public static byte NejblizsiIndex(int rgb)
+ {
+ int r = (rgb >> 16) & 0xFF, g = (rgb >> 8) & 0xFF, b = rgb & 0xFF;
+ byte nej = 7;
+ long nejVzdalenost = long.MaxValue;
+ for (int i = 1; i <= 255; i++)
+ {
+ int c = Rgb(i);
+ long dr = r - ((c >> 16) & 0xFF), dg = g - ((c >> 8) & 0xFF), db = b - (c & 0xFF);
+ long d = dr * dr + dg * dg + db * db;
+ if (d >= nejVzdalenost) continue;
+ nejVzdalenost = d;
+ nej = (byte)i;
+ if (d == 0) break;
+ }
+ return nej;
+ }
+
+ /// Barva odstínové části palety (10–249): odstín podle desítky, jas a sytost
+ /// podle pozice v ní. Lichá pozice = tatáž barva smíchaná do poloviny s jasem
+ /// (odbarvená), proto c + (jas − c) / 2.
+ private static int Odstin(int index)
+ {
+ int skupina = (index - 10) / 10; // 0–23 → úhel odstínu 0°, 15°, … 345°
+ int pozice = (index - 10) % 10; // 0–9 → jas a sytost
+ int jas = Jasy[pozice];
+ var (r, g, b) = Hsv(skupina * 15.0, jas);
+ if ((pozice & 1) == 1)
+ {
+ r += (jas - r) / 2;
+ g += (jas - g) / 2;
+ b += (jas - b) / 2;
+ }
+ return (r << 16) | (g << 8) | b;
+ }
+
+ /// Plně sytá barva daného odstínu a jasu (HSV se saturací 1). Dělení na šestiny
+ /// kruhu; v každé roste jeden kanál a klesá jiný. Ořezává se dolů, jako to dělá AutoCAD.
+ private static (int R, int G, int B) Hsv(double uhelDeg, int jas)
+ {
+ int sestina = (int)(uhelDeg / 60);
+ double podil = uhelDeg / 60 - sestina;
+ int rostouci = (int)(jas * podil);
+ int klesajici = (int)(jas * (1 - podil));
+ return sestina switch
+ {
+ 0 => (jas, rostouci, 0),
+ 1 => (klesajici, jas, 0),
+ 2 => (0, jas, rostouci),
+ 3 => (0, klesajici, jas),
+ 4 => (rostouci, 0, jas),
+ _ => (jas, 0, klesajici),
+ };
+ }
+}
diff --git a/Rozpisky/Rendering/BoundsRenderer.cs b/Rozpisky/Rendering/BoundsRenderer.cs
index 9a3113b..b06bf46 100644
--- a/Rozpisky/Rendering/BoundsRenderer.cs
+++ b/Rozpisky/Rendering/BoundsRenderer.cs
@@ -23,7 +23,7 @@ public sealed class BoundsRenderer : IProfileRenderer
if (y > MaxY) MaxY = y;
}
- public void Layer(string name, int rgb, double lineWeightMm = 0.25) { }
+ public void Stav(in KresliciStav s) { }
public void Line(double x1, double y1, double x2, double y2) { Add(x1, y1); Add(x2, y2); }
diff --git a/Rozpisky/Rendering/DxfProfileRenderer.cs b/Rozpisky/Rendering/DxfProfileRenderer.cs
index 9abf734..a655c4e 100644
--- a/Rozpisky/Rendering/DxfProfileRenderer.cs
+++ b/Rozpisky/Rendering/DxfProfileRenderer.cs
@@ -14,7 +14,7 @@ namespace Rozpisky.Rendering;
/// (6 primitiv), takže má stejné pokrytí jako vykreslovací engine: složitější DXF entity (spline,
/// oblouk, hatch) jsou už ve fázi čtení (DxfTemplate.Replay) převedené na čáry/polylinie/výplně.
/// Zachovává barvu, tloušťku čáry a styl/zarovnání písma přesně podle zdroje – stav se
-/// přebírá z (barva + tloušťka pro každou entitu) a z parametru style
+/// přebírá z (barva i s původem: ACI index / true color / ByLayer) a z parametru style
/// metody (font). Souřadnice jsou ve výkresových mm, osa Y nahoru – shodně s DXF.
/// Jednořádkový text → TEXT, víceřádkový (maxWidthMm > 0) → MTEXT.
///
@@ -36,27 +36,40 @@ public sealed class DxfProfileRenderer : IProfileRenderer
_doc.Layers.Add(_aktualniVrstva);
}
_vrstvy[_aktualniVrstva.Name] = _aktualniVrstva;
- _aktualniBarva = new Color(0, 0, 0);
+ _aktualniBarva = Color.ByLayer;
}
/// Hotový výkres se všemi rozpiskami (pro DxfWriter.Write).
public CadDocument Document => _doc;
- public void Layer(string name, int rgb, double lineWeightMm = 0.25)
+ public void Stav(in KresliciStav s)
{
- if (string.IsNullOrEmpty(name)) name = AcLayer.DefaultName;
- if (!_vrstvy.TryGetValue(name, out var vrstva))
+ _aktualniVrstva = NajdiVrstvu(s.Hladina);
+ _aktualniBarva = NaColor(s.Barva);
+ _aktualniTloustka = s.TloustkaMm is { } mm ? SnapTloustka(mm) : LineWeightType.ByLayer;
+ }
+
+ /// Najde/založí hladinu podle popisu ze zdroje. Vlastnosti se berou z tabulky hladin,
+ /// ne z první entity, která na hladině přistála – jinak by se ztratil příznak tisku
+ /// (!!NOPRINT) a barva hladiny by byla náhodná.
+ private AcLayer NajdiVrstvu(HladinaStav h)
+ {
+ string name = string.IsNullOrEmpty(h.Nazev) ? AcLayer.DefaultName : h.Nazev;
+ if (_vrstvy.TryGetValue(name, out var vrstva)) return vrstva;
+
+ if (!_doc.Layers.TryGetValue(name, out vrstva!))
{
- if (!_doc.Layers.TryGetValue(name, out vrstva!))
- {
- vrstva = new AcLayer(name) { Color = RgbNaColor(rgb) };
- _doc.Layers.Add(vrstva);
- }
- _vrstvy[name] = vrstva;
+ vrstva = new AcLayer(name);
+ _doc.Layers.Add(vrstva);
}
- _aktualniVrstva = vrstva;
- _aktualniBarva = RgbNaColor(rgb); // barva entity dle zdroje (ByLayer už rozbalil DxfTemplate)
- _aktualniTloustka = SnapTloustka(lineWeightMm);
+ vrstva.Color = h.Barva.JeVlastni ? NaColor(h.Barva) : new Color(7);
+ vrstva.LineWeight = h.TloustkaMm > 0 ? SnapTloustka(h.TloustkaMm) : LineWeightType.Default;
+ vrstva.PlotFlag = h.Tisknout;
+ vrstva.IsOn = h.Zapnuta;
+ if (!string.IsNullOrWhiteSpace(h.TypCary) && _doc.LineTypes.TryGetValue(h.TypCary, out var lt))
+ vrstva.LineType = lt;
+ _vrstvy[name] = vrstva;
+ return vrstva;
}
public void Line(double x1, double y1, double x2, double y2)
@@ -246,8 +259,16 @@ public sealed class DxfProfileRenderer : IProfileRenderer
return kandidat;
}
- private static Color RgbNaColor(int rgb)
- => new((byte)((rgb >> 16) & 0xFF), (byte)((rgb >> 8) & 0xFF), (byte)(rgb & 0xFF));
+ /// Barva do DXF ve stejném druhu, v jakém přišla ze zdroje: indexovaná zůstane
+ /// číslem (group 62), přímá se zapíše jako RGB (group 420) a ByLayer/ByBlock zůstane odkazem.
+ /// Dřív se všechno zplošťovalo na RGB, takže výkres v CADu přestal reagovat na barvy hladin.
+ private static Color NaColor(Barva b) => b.Druh switch
+ {
+ BarvaDruh.Index => new Color((short)b.Index),
+ BarvaDruh.True => new Color((byte)((b.Rgb >> 16) & 0xFF), (byte)((b.Rgb >> 8) & 0xFF), (byte)(b.Rgb & 0xFF)),
+ BarvaDruh.ByBlock => Color.ByBlock,
+ _ => Color.ByLayer,
+ };
/// Standardní tloušťky čar (1/100 mm) dle DXF; vstupní mm se přichytí k nejbližší.
private static readonly int[] StdTloustky =
diff --git a/Rozpisky/Rendering/IProfileRenderer.cs b/Rozpisky/Rendering/IProfileRenderer.cs
index ca96cff..c5cd576 100644
--- a/Rozpisky/Rendering/IProfileRenderer.cs
+++ b/Rozpisky/Rendering/IProfileRenderer.cs
@@ -84,10 +84,12 @@ public static class ContourGeometry
///
public interface IProfileRenderer
{
- /// Nastaví aktuální hladinu/styl kresby. = barva 0xRRGGBB
- /// (rozbalená z DXF, ByLayer i index/true-color), tloušťka v mm. Bílá se kreslí černě
+ /// Nastaví aktuální stav kresby – hladinu a to, čím ji entita přebíjí
+ /// (). Barva si nese svůj původ (ACI index / true color / ByLayer),
+ /// aby ji DXF export mohl zapsat tak, jak byla ve zdroji. Rastrové renderery si vyžádají
+ /// hotové RGB přes ; bílá se kreslí černě
/// (bílá na bílém papíře by zmizela).
- void Layer(string name, int rgb, double lineWeightMm = 0.25);
+ void Stav(in KresliciStav s);
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);
diff --git a/Rozpisky/Rendering/KresliciStav.cs b/Rozpisky/Rendering/KresliciStav.cs
new file mode 100644
index 0000000..dd11072
--- /dev/null
+++ b/Rozpisky/Rendering/KresliciStav.cs
@@ -0,0 +1,79 @@
+namespace Rozpisky.Rendering;
+
+/// Původ barvy podle DXF. a nenesou vlastní
+/// hodnotu – barva se dopočítá z hladiny (resp. z vkládaného bloku).
+public enum BarvaDruh : byte { ByLayer, ByBlock, Index, True }
+
+///
+/// Barva tak, jak je zapsaná v DXF – i s původem, ne jen výsledné RGB. Bez toho by se
+/// při exportu z indexované barvy (group 62, „barva 5") stala natvrdo RGB (group 420) a výkres
+/// by v CADu přestal reagovat na tabulku barev; ByLayer by se rozpadlo na kopie barvy hladiny.
+/// je vždy vyplněné pro kresbu na obrazovku; u ByLayer/ByBlock je
+/// bezvýznamné (rozřeší se až v ).
+///
+public readonly record struct Barva(BarvaDruh Druh, byte Index, int Rgb)
+{
+ /// Indexovaná barva AutoCADu (ACI 1–255).
+ public static Barva Aci(int index) => new(BarvaDruh.Index, (byte)index, AciPaleta.Rgb(index));
+
+ /// Přímá barva (true color, DXF group 420).
+ public static Barva Prima(int rgb) => new(BarvaDruh.True, 0, rgb & 0xFFFFFF);
+
+ /// Barva se převezme z hladiny (ACI 256).
+ public static readonly Barva ZHladiny = new(BarvaDruh.ByLayer, 0, 0);
+
+ /// Barva se převezme z bloku, do kterého je entita vložená (ACI 0).
+ public static readonly Barva ZBloku = new(BarvaDruh.ByBlock, 0, 0);
+
+ /// true = barva nese vlastní hodnotu a nemusí se dohledávat jinde.
+ public bool JeVlastni => Druh is BarvaDruh.Index or BarvaDruh.True;
+}
+
+///
+/// Vlastnosti hladiny ze zdrojového výkresu. Přenášejí se celé, aby se hladina dala ve výstupu
+/// postavit znovu – dřív se její barva odvozovala od první entity, která na ní přistála, a
+/// příznak tisku se ztrácel (rámeček !!NOPRINT pak ve výstupu vycházel tisknutelný).
+///
+public readonly record struct HladinaStav(
+ string Nazev, Barva Barva, string TypCary = "Continuous",
+ double TloustkaMm = 0, bool Tisknout = true, bool Zapnuta = true)
+{
+ /// Hladina „0" s barvou 7 – použije se, když zdroj hladinu neuvádí.
+ public static readonly HladinaStav Vychozi = new("0", Barva.Aci(7));
+}
+
+///
+/// Stav kresby pro jednu entitu: hladina a to, čím ji entita přebíjí. null u typu čáry
+/// i tloušťky znamená „podle hladiny" – stejná sémantika jako v DXF.
+///
+public readonly record struct KresliciStav(
+ HladinaStav Hladina, Barva Barva, string? TypCary = null, double? TloustkaMm = null)
+{
+ /// Výchozí tloušťka, když ji neurčí ani entita, ani hladina (DXF „Default").
+ public const double VychoziTloustkaMm = 0.18;
+
+ /// Barva pro rasterizaci: ByLayer i ByBlock se rozřeší barvou hladiny.
+ /// (ByBlock se sem dostane jen mimo blok, kde se chová jako ByLayer.)
+ public int RgbProKresbu()
+ {
+ if (Barva.JeVlastni) return Barva.Rgb;
+ return Hladina.Barva.JeVlastni ? Hladina.Barva.Rgb : 0x000000;
+ }
+
+ /// Tloušťka čáry v mm pro rasterizaci.
+ public double TloustkaKresby()
+ {
+ double mm = TloustkaMm ?? Hladina.TloustkaMm;
+ return mm > 0 ? mm : VychoziTloustkaMm;
+ }
+
+ /// Název typu čáry (Continuous, DASHED…).
+ public string TypCaryKresby()
+ {
+ var t = TypCary ?? Hladina.TypCary;
+ return string.IsNullOrWhiteSpace(t) ? "Continuous" : t;
+ }
+
+ /// Stav pro kresbu mimo zdrojový výkres (rámečky, pomocná grafika).
+ public static KresliciStav Vychozi => new(HladinaStav.Vychozi, Barva.ZHladiny);
+}
diff --git a/Rozpisky/Rendering/SkiaProfileRenderer.cs b/Rozpisky/Rendering/SkiaProfileRenderer.cs
index e0c66e0..87df9d0 100644
--- a/Rozpisky/Rendering/SkiaProfileRenderer.cs
+++ b/Rozpisky/Rendering/SkiaProfileRenderer.cs
@@ -116,9 +116,10 @@ public sealed class SkiaProfileRenderer : IProfileRenderer, IDisposable
private float StrokePx => Math.Max((float)(_lineWeightMm * _pxPerMm), _minStrokePx);
- public void Layer(string name, int rgb, double lineWeightMm = 0.25)
+ public void Stav(in KresliciStav s)
{
- _lineWeightMm = lineWeightMm;
+ _lineWeightMm = s.TloustkaKresby();
+ int rgb = s.RgbProKresbu();
byte r = (byte)((rgb >> 16) & 0xFF), g = (byte)((rgb >> 8) & 0xFF), b = (byte)(rgb & 0xFF);
if (r == 255 && g == 255 && b == 255) r = g = b = 0; // bílá na bílém papíře → černá
var c = new SKColor(r, g, b);
diff --git a/Rozpisky/Rendering/TransformRenderer.cs b/Rozpisky/Rendering/TransformRenderer.cs
index 69e06bb..9514eb5 100644
--- a/Rozpisky/Rendering/TransformRenderer.cs
+++ b/Rozpisky/Rendering/TransformRenderer.cs
@@ -23,8 +23,14 @@ public sealed class TransformRenderer : IProfileRenderer
private double X(double x) => _ox + _s * x;
private double Y(double y) => _oy + _s * y;
- public void Layer(string name, int rgb, double lineWeightMm = 0.25)
- => _inner.Layer(name, rgb, lineWeightMm * _s);
+ /// Stav propadá beze změny až na tloušťku čáry – ta je v mm, takže se škáluje
+ /// stejně jako geometrie (na entitě i na hladině).
+ public void Stav(in KresliciStav s)
+ => _inner.Stav(s with
+ {
+ TloustkaMm = s.TloustkaMm * _s,
+ Hladina = s.Hladina with { TloustkaMm = s.Hladina.TloustkaMm * _s },
+ });
public void Line(double x1, double y1, double x2, double y2)
=> _inner.Line(X(x1), Y(y1), X(x2), Y(y2));