Textové styly: zachovat jména ze zdroje a opravit MTEXT

Šablona má styly pojmenované česky („Popis základní", „Název stavby"),
export je zahazoval a vyráběl náhradní jména podle fontu (Verdana, Verdana_B,
Arial_1) – pro každou kombinaci fontu a sklonu nový záznam. Ve správci stylů
v CADu pak nešlo nic najít ani hromadně změnit. Jméno stylu se teď nese
v `TextStyle.Nazev` až do zápisu; jeden zdrojový styl = jeden styl ve výstupu.

Dál:
- SHX styly (vektorové fonty AutoCADu) se přenášejí i s odkazem na soubor a
  příznakem `IsShape`. Kreslí se pořád náhradním fontem – SHX se jako TrueType
  načíst nedá –, ale v exportu zůstane původní styl, ne Standard.
- MTEXT si drží natočení. Nešlo přiřadit `Rotation` (v ACadSharp je jen ke
  čtení), takže se nastavuje směrovým vektorem osy X; natočený víceřádkový
  atribut dřív vycházel vodorovně.
- MTEXT si drží šířkový faktor přes kód \W. Jako vlastnost entity ho nemá.
  Čtečka ho zase odloupne – ACadSharp ho v PlainText nechává, takže bez toho
  by se „W0.8;" vykreslilo jako text.
- Hodnota MTEXTu se escapuje celá (zpětné lomítko, složené závorky), ne jen
  zlomy řádků. Cesta typu C:\projekt v poli dřív rozbila formátování.

99 testů zelených.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 12:18:14 +02:00
co-authored by Claude Opus 5
parent d66aec5299
commit 95003ce835
5 changed files with 204 additions and 14 deletions
+102
View File
@@ -0,0 +1,102 @@
using System.IO;
using ACadSharp.Entities;
using ACadSharp.IO;
using Rozpisky.Dxf;
using Rozpisky.Rendering;
using Xunit;
namespace Rozpisky.Tests;
/// <summary>
/// Textové styly a MTEXT tam a zpět. Šablona má styly pojmenované česky („Popis základní",
/// „Název stavby"); export je dřív zahodil a vyrobil náhradní jména podle fontu (Verdana_1),
/// takže ve správci stylů v CADu nešlo nic najít ani hromadně změnit.
/// </summary>
public class TextStylyRoundTripTesty : IDisposable
{
private readonly string _soubor = Path.Combine(Path.GetTempPath(), "rozpisky-styly-" + Guid.NewGuid().ToString("N") + ".dxf");
public void Dispose()
{
try { File.Delete(_soubor); } catch (IOException) { }
GC.SuppressFinalize(this);
}
private ACadSharp.CadDocument Kolecko(Action<IProfileRenderer> kresba)
{
var dxf = new DxfProfileRenderer();
kresba(dxf);
DxfWriter.Write(_soubor, dxf.Document, binary: true);
return DxfReader.Read(_soubor);
}
[Fact]
public void StylySiDrziJmenaZeZdroje()
{
var doc = Kolecko(g => new DxfTemplate(TestData.Sablona).Replay(g));
var jmena = doc.TextStyles.Select(s => s.Name).ToList();
Assert.Contains("Popis základní", jmena);
Assert.Contains("Název stavby", jmena);
// Náhradní jména podle fontu nesmí vzniknout, když zdroj styl pojmenovaný má.
Assert.DoesNotContain(jmena, j => j.StartsWith("Verdana", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void JedenZdrojovyStylDaJedenStylVeVystupu()
{
// Dřív vznikal nový styl pro každou kombinaci fontu a sklonu, takže jich ve výstupu
// bylo víc než ve zdroji a texty se rozpadly mezi ně.
var zdroj = DxfReader.Read(TestData.Sablona);
var doc = Kolecko(g => new DxfTemplate(TestData.Sablona).Replay(g));
var pouzite = doc.Entities.OfType<TextEntity>().Select(t => t.Style?.Name)
.Concat(doc.Entities.OfType<MText>().Select(m => m.Style?.Name))
.Where(n => n is not null).Distinct().ToList();
Assert.NotEmpty(pouzite);
foreach (var n in pouzite)
Assert.True(zdroj.TextStyles.Contains(n!), $"styl {n} ve zdroji není");
}
[Fact]
public void MtextSiDrziNatoceni()
{
var doc = Kolecko(g => g.Text(0, 0, "dva\nřádky", 3, maxWidthMm: 40, rotationDeg: 30));
var mt = Assert.Single(doc.Entities.OfType<MText>());
Assert.Equal(30, mt.Rotation * 180 / Math.PI, 3);
}
[Fact]
public void MtextSiDrziSirkovyFaktor()
{
var doc = Kolecko(g => g.Text(0, 0, "text", 3, maxWidthMm: 40, widthFactor: 0.8));
var mt = Assert.Single(doc.Entities.OfType<MText>());
Assert.StartsWith(@"\W0.8;", mt.Value);
// A vlastní čtečka ho musí zase odloupnout, jinak by se „W0.8;" vykreslilo jako text.
var (cisty, wf) = DxfTemplate.MTextSirka(mt.Value, mt.PlainText);
Assert.Equal("text", cisty);
Assert.Equal(0.8, wf!.Value, 3);
}
[Fact]
public void ZpetneLomitkoAZavorkyNerozbijiMtext()
{
// Reálná hodnota z pole: cesta nebo poznámka se zpětným lomítkem. Bez escapování
// by ji CAD přečetl jako formátovací kód a část textu by zmizela.
const string hodnota = @"C:\projekt {2025} \P";
var doc = Kolecko(g => g.Text(0, 0, hodnota, 3, maxWidthMm: 60));
var mt = Assert.Single(doc.Entities.OfType<MText>());
Assert.Equal(hodnota, mt.PlainText);
}
[Fact]
public void ZlomRadkuSePreloziNaMtextovyKod()
{
var doc = Kolecko(g => g.Text(0, 0, "první\ndruhý", 3, maxWidthMm: 60));
var mt = Assert.Single(doc.Entities.OfType<MText>());
Assert.Contains(@"\P", mt.Value);
Assert.Equal("první\ndruhý", mt.PlainText.Replace("\r\n", "\n"));
}
}