feat(io): add Gravograph .CHR font reader with text-to-geometry
Add ChrFont, a reader for Gravograph .CHR engraving fonts, plus UI to convert placed text into engraved geometry in the CAD converter. The .CHR files are obfuscated with a single-byte XOR. Different GravoStyle releases use different keys (0x2F in older versions, 0xCF in the 7000 series, and others across the font library), so the key is auto-detected from byte 1 of the file: the font name is ASCII stored as UTF-16LE, so the high byte of its first character is 0x00 in plaintext and the raw byte equals the key. This reads every font in a GravoStyle install regardless of version, not just one hardcoded key. UI: right-clicking a text item in EntityView raises TextConvertRequested; CadConverterForm renders it via ChrFont with H/V alignment and adds the result on an ENGRAVE layer. Tests use Xunit.SkippableFact and a gitignored test-config.json so the suite points at a local .CHR file without committing proprietary assets. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,7 @@ namespace OpenNest.Controls
|
||||
|
||||
public event EventHandler<Line> LinePicked;
|
||||
public event EventHandler PickCancelled;
|
||||
public event EventHandler<CadText> TextConvertRequested;
|
||||
|
||||
private bool isPickingBendLine;
|
||||
public bool IsPickingBendLine
|
||||
@@ -76,6 +77,13 @@ namespace OpenNest.Controls
|
||||
if (line != null)
|
||||
LinePicked?.Invoke(this, line);
|
||||
}
|
||||
|
||||
if (e.Button == MouseButtons.Right)
|
||||
{
|
||||
var text = HitTestText(e.Location);
|
||||
if (text != null)
|
||||
ShowTextContextMenu(text, e.Location);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
@@ -328,6 +336,41 @@ namespace OpenNest.Controls
|
||||
return bestLine;
|
||||
}
|
||||
|
||||
private CadText HitTestText(Point controlPoint)
|
||||
{
|
||||
if (Texts == null || Texts.Count == 0)
|
||||
return null;
|
||||
|
||||
var worldPoint = PointControlToWorld(controlPoint);
|
||||
var tolerance = LengthGuiToWorld(8);
|
||||
|
||||
foreach (var text in Texts)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text.Value))
|
||||
continue;
|
||||
|
||||
var estimatedWidth = text.Height * text.Value.Length * 0.6;
|
||||
var minX = text.Position.X - tolerance;
|
||||
var maxX = text.Position.X + estimatedWidth + tolerance;
|
||||
var minY = text.Position.Y - tolerance;
|
||||
var maxY = text.Position.Y + text.Height + tolerance;
|
||||
|
||||
if (worldPoint.X >= minX && worldPoint.X <= maxX &&
|
||||
worldPoint.Y >= minY && worldPoint.Y <= maxY)
|
||||
return text;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ShowTextContextMenu(CadText text, Point location)
|
||||
{
|
||||
var menu = new ContextMenuStrip();
|
||||
var item = menu.Items.Add($"Convert \"{text.Value}\" to Geometry");
|
||||
item.Click += (s, e) => TextConvertRequested?.Invoke(this, text);
|
||||
menu.Show(this, location);
|
||||
}
|
||||
|
||||
private void DrawEntityLabels(Graphics g)
|
||||
{
|
||||
for (var i = 0; i < Entities.Count; i++)
|
||||
|
||||
Reference in New Issue
Block a user