Initial upload of project

This commit is contained in:
NinjaPug
2025-04-04 19:26:10 -04:00
parent dd928a6f3f
commit 10838f33a9
7 changed files with 654 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/bin
/obj
/.vs

8
FontGlyphExporter.csproj Normal file
View File

@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
</Project>

25
FontGlyphExporter.sln Normal file
View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35828.75 d17.13
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FontGlyphExporter", "FontGlyphExporter.csproj", "{2E8081B2-DFAC-3227-72D0-366CF7F45501}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2E8081B2-DFAC-3227-72D0-366CF7F45501}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E8081B2-DFAC-3227-72D0-366CF7F45501}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E8081B2-DFAC-3227-72D0-366CF7F45501}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E8081B2-DFAC-3227-72D0-366CF7F45501}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {17BAE8BB-58F1-4268-9740-4AA4ACE38243}
EndGlobalSection
EndGlobal

191
MainForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,191 @@
namespace FontGlyphExporter
{
partial class MainForm
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.TextBox txtTTF;
private System.Windows.Forms.TextBox txtCSS;
private System.Windows.Forms.Button btnBrowseTTF;
private System.Windows.Forms.Button btnBrowseCSS;
private System.Windows.Forms.Button btnLoad;
private System.Windows.Forms.Button btnExport;
private System.Windows.Forms.FlowLayoutPanel iconFlowPanel;
private System.Windows.Forms.Label lblCounter;
private System.Windows.Forms.ComboBox cboRegexPattern;
private System.Windows.Forms.Label lblRegexPattern;
private System.Windows.Forms.TextBox txtCustomRegex;
private System.Windows.Forms.Label lblCustomRegex;
private System.Windows.Forms.Panel pnlSizes;
private System.Windows.Forms.Label lblSizes;
private System.Windows.Forms.CheckBox chkSize4;
private System.Windows.Forms.CheckBox chkSize8;
private System.Windows.Forms.CheckBox chkSize12;
private System.Windows.Forms.CheckBox chkSize24;
private System.Windows.Forms.Label lblSelectedSizes;
private void InitializeComponent()
{
this.txtTTF = new System.Windows.Forms.TextBox();
this.txtCSS = new System.Windows.Forms.TextBox();
this.btnBrowseTTF = new System.Windows.Forms.Button();
this.btnBrowseCSS = new System.Windows.Forms.Button();
this.btnLoad = new System.Windows.Forms.Button();
this.btnExport = new System.Windows.Forms.Button();
this.lblCounter = new System.Windows.Forms.Label();
this.iconFlowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.cboRegexPattern = new System.Windows.Forms.ComboBox();
this.lblRegexPattern = new System.Windows.Forms.Label();
this.txtCustomRegex = new System.Windows.Forms.TextBox();
this.lblCustomRegex = new System.Windows.Forms.Label();
this.pnlSizes = new System.Windows.Forms.Panel();
this.lblSizes = new System.Windows.Forms.Label();
this.chkSize4 = new System.Windows.Forms.CheckBox();
this.chkSize8 = new System.Windows.Forms.CheckBox();
this.chkSize12 = new System.Windows.Forms.CheckBox();
this.chkSize24 = new System.Windows.Forms.CheckBox();
this.lblSelectedSizes = new System.Windows.Forms.Label();
this.pnlSizes.SuspendLayout();
this.SuspendLayout();
//
// txtTTF
this.txtTTF.Location = new System.Drawing.Point(12, 12);
this.txtTTF.Size = new System.Drawing.Size(400, 23);
//
// btnBrowseTTF
this.btnBrowseTTF.Location = new System.Drawing.Point(418, 12);
this.btnBrowseTTF.Size = new System.Drawing.Size(100, 23);
this.btnBrowseTTF.Text = "Browse TTF";
this.btnBrowseTTF.Click += new System.EventHandler(this.btnBrowseTTF_Click);
//
// txtCSS
this.txtCSS.Location = new System.Drawing.Point(12, 41);
this.txtCSS.Size = new System.Drawing.Size(400, 23);
//
// btnBrowseCSS
this.btnBrowseCSS.Location = new System.Drawing.Point(418, 41);
this.btnBrowseCSS.Size = new System.Drawing.Size(100, 23);
this.btnBrowseCSS.Text = "Browse CSS";
this.btnBrowseCSS.Click += new System.EventHandler(this.btnBrowseCSS_Click);
//
// lblRegexPattern
this.lblRegexPattern.Location = new System.Drawing.Point(540, 12);
this.lblRegexPattern.Size = new System.Drawing.Size(80, 23);
this.lblRegexPattern.Text = "Regex Pattern:";
this.lblRegexPattern.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// cboRegexPattern
this.cboRegexPattern.Location = new System.Drawing.Point(625, 12);
this.cboRegexPattern.Size = new System.Drawing.Size(150, 23);
this.cboRegexPattern.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cboRegexPattern.Items.AddRange(new object[] {
"Font Awesome 5/6",
"Font Awesome 4",
"Material Icons",
"Custom"
});
this.cboRegexPattern.SelectedIndexChanged += new System.EventHandler(this.cboRegexPattern_SelectedIndexChanged);
//
// lblCustomRegex
this.lblCustomRegex.Location = new System.Drawing.Point(540, 41);
this.lblCustomRegex.Size = new System.Drawing.Size(80, 23);
this.lblCustomRegex.Text = "Custom Regex:";
this.lblCustomRegex.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.lblCustomRegex.Visible = false;
//
// txtCustomRegex
this.txtCustomRegex.Location = new System.Drawing.Point(625, 41);
this.txtCustomRegex.Size = new System.Drawing.Size(150, 23);
this.txtCustomRegex.Visible = false;
//
// lblSizes
this.lblSizes.Location = new System.Drawing.Point(418, 70);
this.lblSizes.Size = new System.Drawing.Size(100, 23);
this.lblSizes.Text = "Export Sizes:";
this.lblSizes.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// pnlSizes
this.pnlSizes.Location = new System.Drawing.Point(520, 70);
this.pnlSizes.Size = new System.Drawing.Size(255, 25);
//
// chkSize4
this.chkSize4.Location = new System.Drawing.Point(5, 3);
this.chkSize4.Size = new System.Drawing.Size(50, 20);
this.chkSize4.Text = "4x4";
this.chkSize4.CheckedChanged += new System.EventHandler(this.sizeCheckbox_CheckedChanged);
this.pnlSizes.Controls.Add(this.chkSize4);
//
// chkSize8
this.chkSize8.Location = new System.Drawing.Point(65, 3);
this.chkSize8.Size = new System.Drawing.Size(50, 20);
this.chkSize8.Text = "8x8";
this.chkSize8.CheckedChanged += new System.EventHandler(this.sizeCheckbox_CheckedChanged);
this.pnlSizes.Controls.Add(this.chkSize8);
//
// chkSize12
this.chkSize12.Location = new System.Drawing.Point(125, 3);
this.chkSize12.Size = new System.Drawing.Size(60, 20);
this.chkSize12.Text = "12x12";
this.chkSize12.CheckedChanged += new System.EventHandler(this.sizeCheckbox_CheckedChanged);
this.pnlSizes.Controls.Add(this.chkSize12);
//
// chkSize24
this.chkSize24.Location = new System.Drawing.Point(195, 3);
this.chkSize24.Size = new System.Drawing.Size(60, 20);
this.chkSize24.Text = "24x24";
this.chkSize24.CheckedChanged += new System.EventHandler(this.sizeCheckbox_CheckedChanged);
this.pnlSizes.Controls.Add(this.chkSize24);
//
// lblSelectedSizes
this.lblSelectedSizes.Location = new System.Drawing.Point(520, 95);
this.lblSelectedSizes.Size = new System.Drawing.Size(255, 15);
this.lblSelectedSizes.Text = "Selected sizes: 12, 24px";
//
// btnLoad
this.btnLoad.Location = new System.Drawing.Point(12, 70);
this.btnLoad.Size = new System.Drawing.Size(100, 23);
this.btnLoad.Text = "Load Icons";
this.btnLoad.Click += new System.EventHandler(this.btnLoad_Click);
//
// btnExport
this.btnExport.Location = new System.Drawing.Point(118, 70);
this.btnExport.Size = new System.Drawing.Size(120, 23);
this.btnExport.Text = "Export Selected";
this.btnExport.Click += new System.EventHandler(this.btnExport_Click);
//
// lblCounter
this.lblCounter.Location = new System.Drawing.Point(250, 70);
this.lblCounter.Size = new System.Drawing.Size(150, 23);
this.lblCounter.Text = "Selected: 0 / 0";
//
// iconFlowPanel
this.iconFlowPanel.Location = new System.Drawing.Point(12, 120);
this.iconFlowPanel.Size = new System.Drawing.Size(760, 430);
this.iconFlowPanel.AutoScroll = true;
this.iconFlowPanel.WrapContents = true;
this.iconFlowPanel.FlowDirection = System.Windows.Forms.FlowDirection.LeftToRight;
//
// MainForm
this.ClientSize = new System.Drawing.Size(784, 561);
this.Controls.Add(this.txtTTF);
this.Controls.Add(this.btnBrowseTTF);
this.Controls.Add(this.txtCSS);
this.Controls.Add(this.btnBrowseCSS);
this.Controls.Add(this.lblRegexPattern);
this.Controls.Add(this.cboRegexPattern);
this.Controls.Add(this.lblCustomRegex);
this.Controls.Add(this.txtCustomRegex);
this.Controls.Add(this.lblSizes);
this.Controls.Add(this.pnlSizes);
this.Controls.Add(this.lblSelectedSizes);
this.Controls.Add(this.btnLoad);
this.Controls.Add(this.btnExport);
this.Controls.Add(this.lblCounter);
this.Controls.Add(this.iconFlowPanel);
this.Name = "MainForm";
this.Text = "Font Awesome Icon Exporter";
this.pnlSizes.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
}
}

291
MainForm.cs Normal file
View File

@@ -0,0 +1,291 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Text;
using System.IO;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace FontGlyphExporter
{
public partial class MainForm : Form
{
private string fontPath = "";
private string cssPath = "";
private List<IconInfo> allIcons = new();
private List<IconInfo> selectedIcons = new();
private PrivateFontCollection fontCollection = new();
private Font font;
private Dictionary<string, string> regexPatterns = new();
private List<int> selectedSizes = new();
public MainForm()
{
InitializeComponent();
InitializeRegexPatterns();
InitializeSizeCheckboxes();
cboRegexPattern.SelectedIndex = 0;
}
private void InitializeRegexPatterns()
{
regexPatterns.Add("Font Awesome 5/6", @"\.fa-([a-z0-9-]+)\s*{[^}]*--fa:\s*""\\(f[a-f0-9]{3,4})""");
regexPatterns.Add("Font Awesome 4", @"\.fa-([a-z0-9-]+):before\s*{[^}]*content:\s*""\\(f[a-f0-9]{3,4})""");
regexPatterns.Add("Material Icons", @"\.material-icons-([a-z0-9-]+):before\s*{[^}]*content:\s*""\\(e[a-f0-9]{3,4})""");
regexPatterns.Add("Custom", "");
}
private void InitializeSizeCheckboxes()
{
chkSize4.Tag = 4;
chkSize8.Tag = 8;
chkSize12.Tag = 12;
chkSize24.Tag = 24;
// Default selection
chkSize12.Checked = true;
chkSize24.Checked = true;
UpdateSelectedSizes();
}
private void UpdateSelectedSizes()
{
selectedSizes.Clear();
foreach (Control c in pnlSizes.Controls)
{
if (c is CheckBox chk && chk.Checked && chk.Tag is int size)
{
selectedSizes.Add(size);
}
}
// Update the selected sizes label
lblSelectedSizes.Text = selectedSizes.Count > 0
? $"Selected sizes: {string.Join(", ", selectedSizes)}px"
: "No sizes selected";
}
private void btnBrowseTTF_Click(object sender, EventArgs e)
{
using OpenFileDialog ofd = new() { Filter = "Font Files (*.ttf)|*.ttf" };
if (ofd.ShowDialog() == DialogResult.OK)
{
fontPath = ofd.FileName;
txtTTF.Text = fontPath;
}
}
private void btnBrowseCSS_Click(object sender, EventArgs e)
{
using OpenFileDialog ofd = new() { Filter = "CSS Files (*.css)|*.css" };
if (ofd.ShowDialog() == DialogResult.OK)
{
cssPath = ofd.FileName;
txtCSS.Text = cssPath;
}
}
private void btnLoad_Click(object sender, EventArgs e)
{
if (!File.Exists(fontPath) || !File.Exists(cssPath))
{
MessageBox.Show("Please select valid CSS and TTF files.");
return;
}
fontCollection = new PrivateFontCollection();
fontCollection.AddFontFile(fontPath);
font = new Font(fontCollection.Families[0], 16);
string css = File.ReadAllText(cssPath);
string patternToUse;
if (cboRegexPattern.SelectedItem.ToString() == "Custom")
{
patternToUse = txtCustomRegex.Text;
if (string.IsNullOrWhiteSpace(patternToUse))
{
MessageBox.Show("Please enter a valid regex pattern.");
return;
}
}
else
{
patternToUse = regexPatterns[cboRegexPattern.SelectedItem.ToString()];
}
try
{
var matches = Regex.Matches(css, patternToUse, RegexOptions.IgnoreCase);
allIcons.Clear();
foreach (Match match in matches)
{
if (match.Groups.Count < 3) continue;
string name = match.Groups[1].Value;
string unicode = match.Groups[2].Value;
allIcons.Add(new IconInfo { name = name, unicode = unicode });
}
if (allIcons.Count == 0)
{
MessageBox.Show("No icons found with the selected pattern. Try a different pattern or check your CSS file.");
return;
}
PopulateGrid();
}
catch (ArgumentException ex)
{
MessageBox.Show($"Invalid regex pattern: {ex.Message}");
}
}
private void PopulateGrid()
{
iconFlowPanel.Controls.Clear();
selectedIcons.Clear();
UpdateCounter();
foreach (var icon in allIcons)
{
var panel = new Panel
{
Width = 80,
Height = 100,
Margin = new Padding(5),
BorderStyle = BorderStyle.FixedSingle,
Tag = icon
};
var bmp = new Bitmap(48, 48);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.White);
g.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;
string glyph = char.ConvertFromUtf32(Convert.ToInt32(icon.unicode, 16));
g.DrawString(glyph, font, Brushes.Black, 0, 0);
}
var pic = new PictureBox
{
Image = bmp,
SizeMode = PictureBoxSizeMode.CenterImage,
Width = 48,
Height = 48,
Top = 5,
Left = 15
};
var lbl = new Label
{
Text = icon.name,
Width = 70,
Top = 60,
Left = 5,
TextAlign = ContentAlignment.MiddleCenter
};
panel.Controls.Add(pic);
panel.Controls.Add(lbl);
panel.Click += (s, e) =>
{
if (selectedIcons.Contains(icon))
{
selectedIcons.Remove(icon);
panel.BackColor = SystemColors.Control;
}
else
{
selectedIcons.Add(icon);
panel.BackColor = Color.LightBlue;
}
UpdateCounter();
};
iconFlowPanel.Controls.Add(panel);
}
}
private void UpdateCounter()
{
lblCounter.Text = $"Selected: {selectedIcons.Count} / {allIcons.Count}";
}
private void btnExport_Click(object sender, EventArgs e)
{
if (selectedIcons.Count == 0)
{
MessageBox.Show("Please select at least one icon to export.");
return;
}
if (selectedSizes.Count == 0)
{
MessageBox.Show("Please select at least one size for export.");
return;
}
var icons = new List<object>();
var outputDir = Path.Combine("output", "icons");
Directory.CreateDirectory(outputDir);
foreach (var icon in selectedIcons)
{
foreach (var size in selectedSizes)
{
string glyph = char.ConvertFromUtf32(Convert.ToInt32(icon.unicode, 16));
string file = $"icon_{icon.name}_{size}x{size}.bin";
string path = Path.Combine(outputDir, file);
using Bitmap bmp = new(size, size);
using Graphics g = Graphics.FromImage(bmp);
g.Clear(Color.White);
g.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;
g.DrawString(glyph, new Font(font.FontFamily, size), Brushes.Black, 0, 0);
using FileStream fs = new(path, FileMode.Create);
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x += 8)
{
byte b = 0;
for (int bit = 0; bit < 8 && (x + bit) < bmp.Width; bit++)
{
var pixel = bmp.GetPixel(x + bit, y);
if (pixel.R < 128) b |= (byte)(1 << bit);
}
fs.WriteByte(b);
}
}
icons.Add(new { name = icon.name, file = file, width = size, height = size });
}
}
File.WriteAllText("output/icons_index.json", JsonSerializer.Serialize(icons, new JsonSerializerOptions { WriteIndented = true }));
MessageBox.Show("Export complete!");
}
private void cboRegexPattern_SelectedIndexChanged(object sender, EventArgs e)
{
bool isCustom = cboRegexPattern.SelectedItem.ToString() == "Custom";
txtCustomRegex.Visible = isCustom;
lblCustomRegex.Visible = isCustom;
}
private void sizeCheckbox_CheckedChanged(object sender, EventArgs e)
{
UpdateSelectedSizes();
}
public class IconInfo
{
public string name { get; set; }
public string unicode { get; set; }
}
}
}

16
Program.cs Normal file
View File

@@ -0,0 +1,16 @@
using System;
using System.Windows.Forms;
namespace FontGlyphExporter
{
internal static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
}
}

120
readme.md Normal file
View File

@@ -0,0 +1,120 @@
# Font Glyph Exporter
A Windows Forms application for browsing, selecting, and exporting Font Awesome icons (or other icon fonts) as binary files for use in embedded systems, microcontrollers, or other display applications.
![Application Screenshot](screenshot.png)
## Features
- Browse and preview icons from Font TTF files
- Support for multiple font formats through configurable regex patterns
- Easy icon selection with visual preview
- Export icons in various sizes (4x4, 8x8, 12x12, 24x24)
- Exports as 1-bit monochrome binary format for efficiency
- Output indexed JSON for easy integration with your applications
## Requirements
- .NET 6.0 or later
- Windows OS
- Font TTF file
- Corresponding CSS file with icon mappings
## Getting Started
1. Download the latest release or build from source
2. Run the application
3. Browse to select your TTF font file
4. Browse to select your CSS file with icon mappings
5. Select the appropriate regex pattern for your font or create a custom one
6. Click "Load Icons" to view all available icons
7. Select the icons you want to export
8. Choose desired output sizes using the checkboxes
9. Click "Export Selected" to generate the binary files
## Building from Source
```bash
# Clone the repository
git clone https://github.com/programmingPug/FontGlyphExporter.git
# Navigate to the project directory
cd FontGlyphExporter
# Build the project
dotnet build
# Run the application
dotnet run
```
## How It Works
The application:
1. Loads the TTF font file into a private font collection
2. Parses the CSS file using regex to extract icon names and Unicode values
3. Renders each icon in the UI for preview
4. When exporting, creates a 1-bit monochrome bitmap for each selected icon at each selected size
5. Saves these bitmaps as binary files, where each bit represents a pixel (1 = black, 0 = white)
6. Generates a JSON index file mapping icon names to their file information
## Regex Patterns
The application includes several predefined regex patterns:
- **Font Awesome 5/6**: `.fa-([a-z0-9-]+)\s*{[^}]*--fa:\s*"\\(f[a-f0-9]{3,4})"`
- **Font Awesome 4**: `.fa-([a-z0-9-]+):before\s*{[^}]*content:\s*"\\(f[a-f0-9]{3,4})"`
- **Material Icons**: `.material-icons-([a-z0-9-]+):before\s*{[^}]*content:\s*"\\(e[a-f0-9]{3,4})"`
- **Custom**: Enter your own regex pattern
The regex should capture two groups:
1. The icon name
2. The Unicode hex value
## Output Format
### Binary Files
The binary files are stored in the `output/icons` directory. Each bit represents a pixel:
- 1 = Black pixel (icon)
- 0 = White pixel (background)
Each row is padded to a byte boundary, with bits ordered from least significant to most significant.
### JSON Index
The program generates a JSON index file (`output/icons_index.json`) with metadata for all exported icons:
```json
[
{
"name": "calendar",
"file": "icon_calendar_12x12.bin",
"width": 12,
"height": 12
},
{
"name": "calendar",
"file": "icon_calendar_24x24.bin",
"width": 24,
"height": 24
}
]
```
## Use Cases
- Embedded systems with monochrome displays
- Microcontroller applications
- LCD/OLED screens
- E-paper displays
- Any application requiring lightweight icon representation
## License
This project is licensed under the MIT License - see the LICENSE file for details.
## Acknowledgments
- .NET Windows Forms for the UI framework