├── .gitignore ├── images ├── 1.JPG ├── 10.JPG ├── 11.JPG ├── 11a.JPG ├── 11b.jpg ├── 12.JPG ├── 13.JPG ├── 14.jpg ├── 15.jpg ├── 16.jpg ├── 17.jpg ├── 18.jpg ├── 2.JPG ├── 2.psd ├── 3.JPG ├── 4.JPG ├── 5.JPG ├── 6.JPG ├── 7.JPG ├── 8.JPG ├── 9.JPG ├── Arrow2.jpg ├── Image11b.gst ├── Image18.gst ├── Image2.gst ├── screen1.jpg └── Image11.1.gst ├── Resources ├── 12.bmp ├── 34.bmp ├── Left.bmp ├── Right.bmp ├── redo.bmp ├── setup.bmp ├── undo.bmp ├── zx0.exe ├── zx1.exe ├── zx2.exe ├── Default.fnt ├── Delete.bmp ├── apultra.exe ├── config.bmp ├── config.png ├── FontMaker.ico ├── about_1_6.png ├── about_1_6.rdw ├── altirraPAL.pal ├── arrow-down.ico ├── arrow-up.ico ├── b_accept.bmp ├── b_cancel.bmp ├── font-shift.bmp ├── basicremfont.lst ├── CopyAreaHMirror.bmp ├── CopyAreaInvert.bmp ├── CopyAreaShiftUp.bmp ├── CopyAreaVMirror.bmp ├── ShiftLeftInsert.bmp ├── CopyAreaShiftDown.bmp ├── CopyAreaShiftLeft.bmp ├── DeleteShiftRight.bmp ├── ShiftRightInsert.bmp ├── CopyAreaRotateLeft.bmp ├── CopyAreaRotateRight.bmp ├── CopyAreaShiftRight.bmp ├── FontShiftLeftRotate.bmp └── FontShiftRightRotate.bmp ├── Properties ├── launchSettings.json ├── PublishProfiles │ ├── FolderWithRuntime.pubxml.user │ ├── FolderProfile.pubxml │ ├── FolderWithRuntime.pubxml │ └── FolderProfile.pubxml.user ├── Resources.Designer.cs └── Resources.resx ├── Program.cs ├── Status.cs ├── Constants.cs ├── FontMaker.sln ├── FontMakerConfigurationWindow.cs ├── Keyboard.cs ├── FontMaker.csproj.user ├── AtrViewInfoJson.cs ├── AtariViewUndoBuffer.cs ├── AtariViewConfigWindow.cs ├── FontMaker.csproj ├── AtariColorSelector.cs ├── AtariFontUndoBuffer.cs ├── AtariView.cs ├── AtariColorSelector.Designer.cs ├── Helpers.cs ├── Compressors.cs ├── FontMakerConfigurationWindow.Designer.cs ├── PageEditor.Designer.cs ├── AtariViewConfigWindow.resx ├── FontAnalysisWindow.resx ├── FontMakerConfigurationWindow.resx ├── AtariColorSelector.resx ├── ExportFontWindow.resx ├── ImportViewWindow.resx ├── ExportViewWindow.resx ├── PageData.cs ├── Configuration.cs ├── ViewActionsWindow.cs ├── PageEditor.cs ├── ExportFontWindow.Designer.cs ├── ImportViewWindow.cs ├── PageEditor.resx ├── General.cs ├── AtariViewConfigWindow.Designer.cs ├── .editorconfig └── TileSet.cs /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | bin/ 3 | obj/ 4 | 5 | -------------------------------------------------------------------------------- /images/1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/1.JPG -------------------------------------------------------------------------------- /images/10.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/10.JPG -------------------------------------------------------------------------------- /images/11.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/11.JPG -------------------------------------------------------------------------------- /images/11a.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/11a.JPG -------------------------------------------------------------------------------- /images/11b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/11b.jpg -------------------------------------------------------------------------------- /images/12.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/12.JPG -------------------------------------------------------------------------------- /images/13.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/13.JPG -------------------------------------------------------------------------------- /images/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/14.jpg -------------------------------------------------------------------------------- /images/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/15.jpg -------------------------------------------------------------------------------- /images/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/16.jpg -------------------------------------------------------------------------------- /images/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/17.jpg -------------------------------------------------------------------------------- /images/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/18.jpg -------------------------------------------------------------------------------- /images/2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/2.JPG -------------------------------------------------------------------------------- /images/2.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/2.psd -------------------------------------------------------------------------------- /images/3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/3.JPG -------------------------------------------------------------------------------- /images/4.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/4.JPG -------------------------------------------------------------------------------- /images/5.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/5.JPG -------------------------------------------------------------------------------- /images/6.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/6.JPG -------------------------------------------------------------------------------- /images/7.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/7.JPG -------------------------------------------------------------------------------- /images/8.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/8.JPG -------------------------------------------------------------------------------- /images/9.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/9.JPG -------------------------------------------------------------------------------- /Resources/12.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/12.bmp -------------------------------------------------------------------------------- /Resources/34.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/34.bmp -------------------------------------------------------------------------------- /Resources/Left.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/Left.bmp -------------------------------------------------------------------------------- /Resources/Right.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/Right.bmp -------------------------------------------------------------------------------- /Resources/redo.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/redo.bmp -------------------------------------------------------------------------------- /Resources/setup.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/setup.bmp -------------------------------------------------------------------------------- /Resources/undo.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/undo.bmp -------------------------------------------------------------------------------- /Resources/zx0.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/zx0.exe -------------------------------------------------------------------------------- /Resources/zx1.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/zx1.exe -------------------------------------------------------------------------------- /Resources/zx2.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/zx2.exe -------------------------------------------------------------------------------- /images/Arrow2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/Arrow2.jpg -------------------------------------------------------------------------------- /images/Image11b.gst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/Image11b.gst -------------------------------------------------------------------------------- /images/Image18.gst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/Image18.gst -------------------------------------------------------------------------------- /images/Image2.gst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/Image2.gst -------------------------------------------------------------------------------- /images/screen1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/screen1.jpg -------------------------------------------------------------------------------- /Resources/Default.fnt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/Default.fnt -------------------------------------------------------------------------------- /Resources/Delete.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/Delete.bmp -------------------------------------------------------------------------------- /Resources/apultra.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/apultra.exe -------------------------------------------------------------------------------- /Resources/config.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/config.bmp -------------------------------------------------------------------------------- /Resources/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/config.png -------------------------------------------------------------------------------- /images/Image11.1.gst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/images/Image11.1.gst -------------------------------------------------------------------------------- /Resources/FontMaker.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/FontMaker.ico -------------------------------------------------------------------------------- /Resources/about_1_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/about_1_6.png -------------------------------------------------------------------------------- /Resources/about_1_6.rdw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/about_1_6.rdw -------------------------------------------------------------------------------- /Resources/altirraPAL.pal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/altirraPAL.pal -------------------------------------------------------------------------------- /Resources/arrow-down.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/arrow-down.ico -------------------------------------------------------------------------------- /Resources/arrow-up.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/arrow-up.ico -------------------------------------------------------------------------------- /Resources/b_accept.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/b_accept.bmp -------------------------------------------------------------------------------- /Resources/b_cancel.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/b_cancel.bmp -------------------------------------------------------------------------------- /Resources/font-shift.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/font-shift.bmp -------------------------------------------------------------------------------- /Resources/basicremfont.lst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/basicremfont.lst -------------------------------------------------------------------------------- /Resources/CopyAreaHMirror.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/CopyAreaHMirror.bmp -------------------------------------------------------------------------------- /Resources/CopyAreaInvert.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/CopyAreaInvert.bmp -------------------------------------------------------------------------------- /Resources/CopyAreaShiftUp.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/CopyAreaShiftUp.bmp -------------------------------------------------------------------------------- /Resources/CopyAreaVMirror.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/CopyAreaVMirror.bmp -------------------------------------------------------------------------------- /Resources/ShiftLeftInsert.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/ShiftLeftInsert.bmp -------------------------------------------------------------------------------- /Resources/CopyAreaShiftDown.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/CopyAreaShiftDown.bmp -------------------------------------------------------------------------------- /Resources/CopyAreaShiftLeft.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/CopyAreaShiftLeft.bmp -------------------------------------------------------------------------------- /Resources/DeleteShiftRight.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/DeleteShiftRight.bmp -------------------------------------------------------------------------------- /Resources/ShiftRightInsert.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/ShiftRightInsert.bmp -------------------------------------------------------------------------------- /Resources/CopyAreaRotateLeft.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/CopyAreaRotateLeft.bmp -------------------------------------------------------------------------------- /Resources/CopyAreaRotateRight.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/CopyAreaRotateRight.bmp -------------------------------------------------------------------------------- /Resources/CopyAreaShiftRight.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/CopyAreaShiftRight.bmp -------------------------------------------------------------------------------- /Resources/FontShiftLeftRotate.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/FontShiftLeftRotate.bmp -------------------------------------------------------------------------------- /Resources/FontShiftRightRotate.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matosimi/atari-fontmaker/HEAD/Resources/FontShiftRightRotate.bmp -------------------------------------------------------------------------------- /Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "FontMaker": { 4 | "commandName": "Project" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /Properties/PublishProfiles/FolderWithRuntime.pubxml.user: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | True|2023-03-05T09:46:56.3779491Z;True|2023-03-05T11:44:46.7687606+02:00; 8 | 9 | 10 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker 2 | { 3 | internal static class Program 4 | { 5 | public static FontMakerForm MainForm { get; set; } 6 | 7 | /// 8 | /// The main entry point for the application. 9 | /// 10 | [STAThread] 11 | static void Main() 12 | { 13 | Application.SetHighDpiMode(HighDpiMode.DpiUnaware); 14 | 15 | // To customize application configuration such as set high DPI settings or default font, 16 | // see https://aka.ms/applicationconfiguration. 17 | ApplicationConfiguration.Initialize(); 18 | MainForm = new FontMakerForm(); 19 | Application.Run(MainForm); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\publish\FontMaker64 10 | FileSystem 11 | <_TargetId>Folder 12 | net9.0-windows7.0 13 | win-x64 14 | false 15 | true 16 | true 17 | 18 | -------------------------------------------------------------------------------- /Properties/PublishProfiles/FolderWithRuntime.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\publish\FontMaker64Full 10 | FileSystem 11 | <_TargetId>Folder 12 | net7.0-windows 13 | win-x64 14 | true 15 | true 16 | true 17 | 18 | -------------------------------------------------------------------------------- /Status.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker 2 | { 3 | internal class Status 4 | { 5 | } 6 | 7 | // All code for section E - Status display and some buttons 8 | public partial class FontMakerForm 9 | { 10 | 11 | public void CheckDuplicate() 12 | { 13 | if ((checkBoxShowDuplicates.Checked == false) || (checkBoxShowDuplicates.Enabled == false)) 14 | { 15 | return; 16 | } 17 | 18 | DuplicateCharacterIndex = SelectedCharacterIndex; 19 | 20 | if (FindDuplicateChar() != SelectedCharacterIndex) 21 | { 22 | timerDuplicates.Enabled = true; 23 | } 24 | else 25 | { 26 | timerDuplicates.Enabled = false; 27 | pictureBoxDuplicateIndicator.Visible = false; 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Constants.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker 2 | { 3 | internal static class Constants 4 | { 5 | public const int NumColors = 10; // The # of colors we can set at a maximum (1 lumo, 9 colors) 6 | 7 | public static readonly string Title = "Atari FontMaker"; 8 | 9 | public static Rectangle[] WhereAreTheFontBanksComingFrom = 10 | [ 11 | new(0, 0, 512, 256), 12 | new(0, 256, 512, 512), 13 | new(0, 512, 512, 768), 14 | new(0, 768, 512, 1024) 15 | ]; 16 | 17 | public static int[] FontYOffset = [0, 128, 256, 384]; 18 | 19 | public static int[] FontPageOffset = [0, 0, 256, 256]; 20 | 21 | public static readonly byte[] ColorIndex2Bits = [0, 0, 1, 2, 3, 3]; 22 | public static readonly byte[] Bits2ColorIndex = [1, 2, 3, 4]; 23 | 24 | public static readonly byte[] ColorIndex2FourBits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; 25 | public static readonly byte[] FourBits2ColorIndex = [0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 8, 4, 5, 6, 7]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Properties/PublishProfiles/FolderProfile.pubxml.user: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | True|2025-04-29T06:44:39.6498023Z||;True|2025-04-12T20:31:38.4741760+02:00||;True|2025-04-12T14:14:54.7812399+02:00||;False|2025-04-12T14:14:33.3943147+02:00||;True|2025-03-12T18:32:23.8776885+02:00||;True|2025-03-12T16:01:21.1344768+02:00||;True|2025-01-01T20:04:40.4267033+02:00||;True|2023-03-05T12:54:42.0857746+02:00||;True|2023-03-05T12:51:52.3865797+02:00||;True|2023-03-05T12:47:33.3242651+02:00||;True|2023-03-05T12:17:13.1889879+02:00||;True|2023-03-05T12:16:22.1133159+02:00||;True|2023-03-05T12:09:32.2235560+02:00||;True|2023-03-05T12:05:13.2002146+02:00||;True|2023-03-05T12:04:01.6005147+02:00||;True|2023-03-05T12:00:45.0555316+02:00||;True|2023-03-05T11:53:09.6768585+02:00||;True|2023-03-05T11:47:16.6970347+02:00||;True|2023-03-05T11:43:52.6460612+02:00||;True|2023-03-03T16:13:05.8276461+02:00||; 8 | 9 | 10 | -------------------------------------------------------------------------------- /FontMaker.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33213.308 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FontMaker", "FontMaker.csproj", "{9178804E-69ED-4308-848E-F5F6C3C7C078}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {9178804E-69ED-4308-848E-F5F6C3C7C078}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {9178804E-69ED-4308-848E-F5F6C3C7C078}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {9178804E-69ED-4308-848E-F5F6C3C7C078}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {9178804E-69ED-4308-848E-F5F6C3C7C078}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {7758BBDA-A93A-46F3-96C5-357AB66A3E35} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /FontMakerConfigurationWindow.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace FontMaker; 4 | public partial class FontMakerConfigurationWindow : Form 5 | { 6 | [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 7 | public Compressors.CompressorType CompressorId { get; set; } 8 | 9 | public FontMakerConfigurationWindow(Compressors.CompressorType compressorId) 10 | { 11 | InitializeComponent(); 12 | 13 | CompressorId = compressorId; 14 | 15 | switch (compressorId) 16 | { 17 | case Compressors.CompressorType.APULTRA: radioButtonApultra.Checked = true; break; 18 | case Compressors.CompressorType.ZX2: radioButtonZX2.Checked = true; break; 19 | case Compressors.CompressorType.ZX1: radioButtonZX1.Checked = true; break; 20 | case Compressors.CompressorType.ZX0: 21 | default: 22 | radioButtonZX0.Checked = true; break; 23 | } 24 | } 25 | 26 | private void buttonOk_Click(object sender, EventArgs e) 27 | { 28 | if (radioButtonZX0.Checked) 29 | CompressorId = Compressors.CompressorType.ZX0; 30 | else if (radioButtonZX1.Checked) 31 | CompressorId = Compressors.CompressorType.ZX1; 32 | else if (radioButtonZX2.Checked) 33 | CompressorId = Compressors.CompressorType.ZX2; 34 | else if (radioButtonApultra.Checked) 35 | CompressorId = Compressors.CompressorType.APULTRA; 36 | else 37 | { 38 | CompressorId = Compressors.CompressorType.ZX0; 39 | } 40 | 41 | DialogResult = DialogResult.OK; 42 | Close(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Keyboard.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker 2 | { 3 | internal class Keyboard 4 | { 5 | } 6 | 7 | public partial class FontMakerForm 8 | { 9 | 10 | public void ExecuteSelectPreviousCharacter() 11 | { 12 | if (!buttonMegaCopy.Checked) 13 | { 14 | if (CharacterEdited()) 15 | { 16 | AtariFontUndoBuffer.Add2Undo(true); 17 | } 18 | } 19 | 20 | SelectedCharacterIndex--; 21 | if (SelectedCharacterIndex < 0) SelectedCharacterIndex += 512; 22 | SetCharCursor(); 23 | } 24 | 25 | public void ExecuteSelectNextCharacter() 26 | { 27 | if (!buttonMegaCopy.Checked) 28 | { 29 | if (CharacterEdited()) 30 | { 31 | AtariFontUndoBuffer.Add2Undo(true); 32 | } 33 | } 34 | 35 | SelectedCharacterIndex++; 36 | if (SelectedCharacterIndex >= 512) SelectedCharacterIndex -= 512; 37 | SetCharCursor(); 38 | } 39 | 40 | public void ExecuteEscapeKeyPressed() 41 | { 42 | if (buttonMegaCopy.Checked) 43 | { 44 | switch (megaCopyStatus) 45 | { 46 | case MegaCopyStatusFlags.None: 47 | break; 48 | 49 | case MegaCopyStatusFlags.Selecting: 50 | ResetMegaCopyStatus(); 51 | break; 52 | 53 | case MegaCopyStatusFlags.Selected: 54 | break; 55 | 56 | case MegaCopyStatusFlags.Pasting: 57 | case MegaCopyStatusFlags.PastingView: 58 | case MegaCopyStatusFlags.PastingFont: 59 | ResetMegaCopyStatus(); 60 | break; 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /FontMaker.csproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <_LastSelectedProfileId>F:\Projects\Cerebus\Atari\AtariFontMaker\Properties\PublishProfiles\FolderProfile.pubxml 5 | 6 | 7 | 8 | Form 9 | 10 | 11 | Form 12 | 13 | 14 | Form 15 | 16 | 17 | Form 18 | 19 | 20 | Form 21 | 22 | 23 | Form 24 | 25 | 26 | Form 27 | 28 | 29 | Form 30 | 31 | 32 | Form 33 | 34 | 35 | Form 36 | 37 | 38 | Form 39 | 40 | 41 | -------------------------------------------------------------------------------- /AtrViewInfoJson.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker; 2 | /// 3 | /// The data structure of the .atrview file 4 | /// 5 | public class AtrViewInfoJson 6 | { 7 | public string? Version { get; set; } 8 | 9 | /// 10 | /// Mode 4/5 indicator 11 | /// 0 = mode 2 B & W 12 | /// 1 = mode 4 Color 13 | /// 2 = mode 5 Color 14 | /// 3 = mode 10 9 colors 15 | /// 16 | public string ColoredGfx { get; set; } = string.Empty; 17 | 18 | /// 19 | /// Characters in the view 20 | /// 21 | public string Chars { get; set; } = string.Empty; 22 | 23 | public int Width { get; set; } = 40; 24 | public int Height { get; set; } = 26; 25 | 26 | /// 27 | /// Use which font on which of the 26 lines of the view "1234" 28 | /// 29 | public string Lines { get; set; } = string.Empty; 30 | 31 | /// 32 | /// Hex encoded array of the 6 selected colors 33 | /// 34 | public string Colors { get; set; } = string.Empty; 35 | 36 | public string Fontname1 { get; set; } = string.Empty; 37 | public string Fontname2 { get; set; } = string.Empty; 38 | public string? Fontname3 { get; set; } 39 | public string? Fontname4 { get; set; } 40 | 41 | /// 42 | /// Hex encoded 1024 bytes per font 43 | /// 44 | public string Data { get; set; } = string.Empty; 45 | 46 | public string FortyBytes { get; set; } = string.Empty; 47 | 48 | /// 49 | /// For each page we have some data: 50 | /// - name 51 | /// - characters (40x26) 52 | /// - selected font 53 | /// 54 | public List? Pages { get; set; } 55 | 56 | /// 57 | /// For each tile we have: 58 | /// - index where to store it 59 | /// - characters (5x5) 60 | /// - selected font 61 | /// 62 | public List? Tiles { get; set; } 63 | } 64 | -------------------------------------------------------------------------------- /AtariViewUndoBuffer.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker; 2 | 3 | /// 4 | /// Every page contains an undo buffer. 5 | /// The buffer is fed with AtariView data before something is changed. 6 | /// We maintain a linked list of undo data, and a stack of redo data. 7 | /// The LinkList is limited to 250 entries, after which the oldest get discarded. 8 | /// 9 | public class AtariViewUndoBuffer 10 | { 11 | public const int UndoBufferSize = 250; 12 | 13 | private readonly LinkedList _undoCommands = []; 14 | private readonly Stack _redoCommands = new(); 15 | 16 | /// 17 | /// Push the current AtariView into the undo buffer 18 | /// 19 | public void Push() 20 | { 21 | while(_undoCommands.Count >= UndoBufferSize) 22 | { 23 | _undoCommands.RemoveFirst(); 24 | } 25 | _undoCommands.AddLast(AtariView.BuildForUndo()); 26 | if (_redoCommands.Count > 0) 27 | _redoCommands.Clear(); 28 | } 29 | 30 | public void Undo() 31 | { 32 | if (_undoCommands.Count > 0) 33 | { 34 | // Save the current screen 35 | _redoCommands.Push(AtariView.BuildForUndo()); 36 | 37 | // Get the last screen and restore it 38 | RestoreScreen(_undoCommands.Last()); 39 | _undoCommands.RemoveLast(); 40 | } 41 | } 42 | 43 | public void Redo() 44 | { 45 | if (_redoCommands.Count <= 0) 46 | { 47 | // Nothing to redo 48 | return; 49 | } 50 | 51 | // Save the current screen 52 | while (_undoCommands.Count >= UndoBufferSize) 53 | { 54 | _undoCommands.RemoveFirst(); 55 | } 56 | _undoCommands.AddLast(AtariView.BuildForUndo()); 57 | 58 | // Get the last screen and restore it 59 | RestoreScreen(_redoCommands.Pop()); 60 | } 61 | 62 | private static void RestoreScreen(AtariViewUndoInfo data) 63 | { 64 | if (data is { ViewBytes: not null }) 65 | { 66 | AtariView.RestoreFromUndo(data); 67 | } 68 | } 69 | 70 | public (bool, bool) GetRedoUndoButtonState() 71 | { 72 | return (_undoCommands.Count > 0, _redoCommands.Count > 0); 73 | } 74 | } -------------------------------------------------------------------------------- /AtariViewConfigWindow.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker; 2 | public partial class AtariViewConfigWindow : Form 3 | { 4 | public int NewWidth => (int)numericWidth.Value; 5 | public int NewHeight => (int)numericHeight.Value; 6 | public byte NewFontNr 7 | { 8 | get 9 | { 10 | if (radio2.Checked) 11 | return 2; 12 | if (radio3.Checked) 13 | return 3; 14 | if (radio4.Checked) 15 | return 4; 16 | return 1; 17 | } 18 | } 19 | 20 | public AtariViewConfigWindow() 21 | { 22 | InitializeComponent(); 23 | 24 | labelCurrentInfo.Text = $"{AtariView.Width}x{AtariView.Height} @ {AtariView.Width * AtariView.Height} bytes"; 25 | 26 | numericWidth.Value = AtariView.Width; 27 | numericHeight.Value = AtariView.Height; 28 | ActionNewInfoUpdate(); 29 | } 30 | 31 | private void numericWidth_ValueChanged(object sender, EventArgs e) 32 | { 33 | ActionNewInfoUpdate(); 34 | } 35 | 36 | private void numericHeight_ValueChanged(object sender, EventArgs e) 37 | { 38 | ActionNewInfoUpdate(); 39 | } 40 | 41 | private void ActionNewInfoUpdate(bool limit = true) 42 | { 43 | if (limit) 44 | { 45 | if (numericWidth.Value < 40) 46 | numericWidth.Value = 40; 47 | 48 | if (numericHeight.Value < 26) 49 | numericHeight.Value = 26; 50 | } 51 | 52 | labelNewInfo.Text = $"{numericWidth.Value}x{numericHeight.Value} @ {numericWidth.Value * numericHeight.Value} bytes"; 53 | 54 | if (numericWidth.Value != AtariView.Width || numericHeight.Value != AtariView.Height) 55 | { 56 | var xChange = (int)numericWidth.Value - AtariView.Width; 57 | var yChange = (int)numericHeight.Value - AtariView.Height; 58 | labelNewDifference.Text = $"Change in width: {xChange} Change in height: {yChange}"; 59 | //buttonResize.Enabled = true; 60 | } 61 | else 62 | { 63 | labelNewDifference.Text = "No change!"; 64 | //buttonResize.Enabled = false; 65 | } 66 | } 67 | 68 | private void numericWidth_KeyPress(object sender, KeyPressEventArgs e) 69 | { 70 | //ActionNewInfoUpdate(false); 71 | } 72 | 73 | private void buttonResize_Click(object sender, EventArgs e) 74 | { 75 | if ((int)numericWidth.Value != AtariView.Width || (int)numericHeight.Value != AtariView.Height) 76 | { 77 | // Something has changed 78 | var doResize = true; 79 | 80 | if ((int)numericWidth.Value < AtariView.Width || (int)numericHeight.Value < AtariView.Height) 81 | { 82 | // Ok before a shrink 83 | var ok = MessageBox.Show(@"Do you want to change the view size? You might loose data?", Constants.Title, MessageBoxButtons.YesNo, MessageBoxIcon.Question); 84 | doResize = ok == DialogResult.Yes; 85 | } 86 | 87 | if (doResize) 88 | { 89 | DialogResult = DialogResult.OK; 90 | Close(); 91 | return; 92 | } 93 | } 94 | else 95 | { 96 | Close(); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /FontMaker.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net9.0-windows7.0 6 | enable 7 | true 8 | enable 9 | true 10 | 11 | true 12 | false 13 | SystemAware 14 | Segoe UI, 8.25pt 15 | Resources\FontMaker.ico 16 | 1.6.17.4 17 | 1.6.17.4 18 | False 19 | 1.6.17.4 20 | True 21 | 22 | 23 | 24 | full 25 | 1701;1702;MSB3825 26 | 27 | 28 | 29 | full 30 | 1701;1702;MSB3825 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | True 62 | True 63 | Resources.resx 64 | 65 | 66 | 67 | 68 | 69 | PublicResXFileCodeGenerator 70 | Resources.Designer.cs 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace FontMaker.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | public class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | public static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FontMaker.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | public static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /AtariColorSelector.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker 2 | { 3 | public partial class AtariColorSelectorForm : Form 4 | { 5 | private static readonly SolidBrush WhiteBrush = new(Color.White); 6 | 7 | public AtariColorSelectorForm() 8 | { 9 | InitializeComponent(); 10 | 11 | this.FormClosed += FormClose!; 12 | this.Load += FormCreate!; 13 | } 14 | 15 | public void FormCreate(object sender, EventArgs e) 16 | { 17 | DoubleBuffered = true; 18 | DrawPalette(); 19 | DrawActualColor(); 20 | } 21 | 22 | public void DrawPalette() 23 | { 24 | using var foreColorBrush = new SolidBrush(ForeColor); 25 | 26 | var matrix = new Bitmap(128 + 16, 256 + 16); 27 | using var gr = Graphics.FromImage(matrix); 28 | gr.Clear(this.BackColor); 29 | 30 | for (var y = 0; y < 16; y++) 31 | { 32 | // Vertical marks 33 | gr.DrawString($"{y:X}", this.Font, foreColorBrush, 16 * 8 + 2, y * 16); 34 | for (var x = 0; x < 8; x++) 35 | { 36 | using var thisColorBrush = new SolidBrush(palette[y * 16 + x * 2]); 37 | gr.FillRectangle(thisColorBrush, x * 16, y * 16, 16, 16); 38 | } 39 | } 40 | using var pen = new Pen(WhiteBrush); 41 | gr.DrawRectangle(pen, (selectedColorIndex % 16) * 8, (selectedColorIndex / 16) * 16, 15, 15); 42 | 43 | // Horizontal marks 44 | for (var x = 0; x < 8; x++) 45 | gr.DrawString($"{(x * 2):X}", this.Font, foreColorBrush, 16 * x, 16 * 16 + 2); 46 | 47 | // Clear the old image and set new bitmap 48 | ImagePalette.Image?.Dispose(); 49 | ImagePalette.Image = matrix; 50 | } 51 | 52 | public void DrawActualColor() 53 | { 54 | var w = ImageSelected.Width; 55 | var h = ImageSelected.Height; 56 | LabelOldColor.Text = $@"${selectedColorIndex:X2} - {selectedColorIndex}"; 57 | LabelActualColor.Text = $@"${actualColorIndex:X2} - {actualColorIndex}"; 58 | 59 | var clr = new Bitmap(w, h); 60 | using var gr = Graphics.FromImage(clr); 61 | 62 | using var selColorBrush = new SolidBrush(palette[selectedColorIndex]); 63 | gr.FillRectangle(selColorBrush, 0, 0, w, h / 2); 64 | 65 | using var actualColorBrush = new SolidBrush(palette[actualColorIndex]); 66 | gr.FillRectangle(actualColorBrush, 0, h / 2, w, h / 2); 67 | 68 | ImageSelected.Image?.Dispose(); 69 | ImageSelected.Image = clr; 70 | } 71 | 72 | public void ImagePaletteMouseMove(object sender, MouseEventArgs e) 73 | { 74 | if (e.X < 16 * 8 && e.Y < 16 * 16) 75 | { 76 | actualColorIndex = (byte)((e.Y / 16) * 16 + (e.X / 16) * 2); 77 | actualColor = palette[actualColorIndex]; 78 | DrawActualColor(); 79 | } 80 | } 81 | 82 | public void ImagePaletteMouseDown(object sender, MouseEventArgs e) 83 | { 84 | selectedColorIndex = actualColorIndex; 85 | Close(); 86 | } 87 | 88 | public void SetSelectedColorIndex(byte paletteIndex) 89 | { 90 | selectedColorIndex = paletteIndex; 91 | } 92 | 93 | public void FormClose(object sender, FormClosedEventArgs action) 94 | { 95 | if (DialogResult == DialogResult.OK) 96 | { 97 | selectedColorIndex = actualColorIndex; 98 | } 99 | else 100 | { 101 | actualColorIndex = selectedColorIndex; 102 | } 103 | } 104 | 105 | public void SetPalette(Color[] pal) 106 | { 107 | pal.CopyTo(palette, 0); 108 | } 109 | 110 | internal Color[] palette = new Color[256]; 111 | public byte selectedColorIndex; 112 | public byte actualColorIndex; 113 | public Color actualColor = Color.Empty; 114 | 115 | private void AtariColorSelectorForm_KeyDown(object sender, KeyEventArgs e) 116 | { 117 | if (e.KeyCode == Keys.Escape) 118 | { 119 | Close(); 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /AtariFontUndoBuffer.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker 2 | { 3 | public static class AtariFontUndoBuffer 4 | { 5 | public const int UndoBufferSize = 250; 6 | 7 | public static byte[,] undoBuffer = new byte[UndoBufferSize + 1, AtariFont.NumFontBytes]; 8 | 9 | public static int[] undoBufferFlags = new int[UndoBufferSize + 1]; 10 | public static int undoBufferIndex; 11 | 12 | public static void Setup() 13 | { 14 | undoBufferIndex = 0; 15 | for (var i = 0; i <= UndoBufferSize; i++) 16 | { 17 | undoBufferFlags[i] = -1; 18 | } 19 | } 20 | 21 | public static void Add2UndoInitial() 22 | { 23 | var prevUndoIndex = undoBufferIndex; 24 | 25 | for (var i = 0; i < AtariFont.NumFontBytes; i++) 26 | { 27 | undoBuffer[undoBufferIndex, i] = AtariFont.FontBytes[i]; 28 | } 29 | 30 | undoBufferFlags[undoBufferIndex] = undoBufferFlags[prevUndoIndex] + 1; 31 | undoBufferFlags[(undoBufferIndex + 1) % UndoBufferSize] = -1; //disallow redo when change 32 | } 33 | 34 | public static void Add2Undo(bool difference) 35 | { 36 | if (difference) 37 | { 38 | var prevUndoIndex = undoBufferIndex; 39 | undoBufferIndex = (undoBufferIndex + 1) % UndoBufferSize; // size of undo buffer 40 | 41 | for (var i = 0; i < AtariFont.NumFontBytes; i++) 42 | { 43 | undoBuffer[undoBufferIndex, i] = AtariFont.FontBytes[i]; 44 | } 45 | 46 | undoBufferFlags[undoBufferIndex] = undoBufferFlags[prevUndoIndex] + 1; 47 | undoBufferFlags[(undoBufferIndex + 1) % UndoBufferSize] = -1; // disallow redo when change 48 | } 49 | } 50 | 51 | public static void Add2UndoFullDifferenceScan() 52 | { 53 | var i = 0; 54 | var difference = false; 55 | 56 | // check the difference between last undo buffer and current font 57 | while ((i < AtariFont.NumFontBytes) && (!difference)) 58 | { 59 | if (AtariFont.FontBytes[i] != undoBuffer[undoBufferIndex, i]) 60 | { 61 | difference = true; 62 | } 63 | 64 | i++; 65 | } 66 | 67 | if (difference) 68 | { 69 | AtariFontUndoBuffer.Add2Undo(true); 70 | } 71 | 72 | // Update the GUI state now 73 | } 74 | 75 | 76 | public static int GetPrevUndoIndex() 77 | { 78 | return undoBufferIndex - 1 < 0 ? UndoBufferSize - 1 : undoBufferIndex - 1; 79 | } 80 | 81 | public static void Undo() 82 | { 83 | var prevUndoIndex = GetPrevUndoIndex(); 84 | 85 | for (var i = 0; i < AtariFont.NumFontBytes; i++) 86 | { 87 | AtariFont.FontBytes[i] = undoBuffer[prevUndoIndex, i]; 88 | } 89 | 90 | undoBufferIndex = prevUndoIndex; 91 | } 92 | 93 | public static void Redo() 94 | { 95 | var nextUndoIndex = (undoBufferIndex + 1) % UndoBufferSize; 96 | 97 | if (undoBufferFlags[nextUndoIndex] > -1) 98 | { 99 | for (var i = 0; i < AtariFont.NumFontBytes; i++) 100 | { 101 | AtariFont.FontBytes[i] = undoBuffer[nextUndoIndex, i]; 102 | } 103 | } 104 | 105 | undoBufferIndex = nextUndoIndex; 106 | } 107 | 108 | public static (bool, bool) GetRedoUndoButtonState(bool edited) 109 | { 110 | bool redoButtonEnabled; 111 | bool undoButtonEnabled; 112 | 113 | var nextUndoBufferIndex = (undoBufferIndex + 1) % UndoBufferSize; 114 | var prevUndoBufferIndex = GetPrevUndoIndex(); 115 | 116 | // redo button handling 117 | if (undoBufferFlags[nextUndoBufferIndex] == -1) 118 | { 119 | redoButtonEnabled = false; 120 | } 121 | else if (edited) 122 | { 123 | redoButtonEnabled = false; 124 | } 125 | else 126 | { 127 | redoButtonEnabled = true; 128 | } 129 | 130 | // undo button handling 131 | if (edited) 132 | { 133 | undoButtonEnabled = true; 134 | } 135 | else if ((undoBufferFlags[undoBufferIndex] > undoBufferFlags[prevUndoBufferIndex]) && (undoBufferFlags[prevUndoBufferIndex] > -1)) 136 | { 137 | undoButtonEnabled = true; 138 | } 139 | else 140 | { 141 | undoButtonEnabled = false; 142 | } 143 | 144 | return (redoButtonEnabled, undoButtonEnabled); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /AtariView.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker 2 | { 3 | 4 | public class AtariViewUndoInfo 5 | { 6 | public byte[,] ViewBytes; 7 | public byte[] UseFontOnLine; 8 | 9 | public int Width { get; set; } 10 | public int Height { get; set; } 11 | 12 | public int OffsetX { get; set; } 13 | public int OffsetY { get; set; } 14 | } 15 | 16 | public static class AtariView 17 | { 18 | public const int VIEW_WIDTH = 40; 19 | public const int VIEW_HEIGHT = 26; 20 | public const int VIEW_HEIGHT_TALL = 13; 21 | 22 | public const int VIEW_PIXEL_WIDTH = 48 * 8 * 2; // Each char is 8x8 and the x2 is for the double scale factor 23 | public const int VIEW_PIXEL_HEIGHT = 26 * 8 * 2; 24 | 25 | #region Data 26 | 27 | /// 28 | /// All bytes showing on the 40x26 screen 29 | /// 30 | public static byte[,] ViewBytes = new byte[VIEW_WIDTH, VIEW_HEIGHT]; 31 | 32 | /// 33 | /// Which fontNr is used on which line of the view 34 | /// 35 | public static byte[] UseFontOnLine = new byte[VIEW_HEIGHT]; 36 | 37 | public static int Width { get; set; } = VIEW_WIDTH; 38 | public static int Height { get; set; } = VIEW_HEIGHT; 39 | 40 | public static int OffsetX { get; set; } = 0; 41 | public static int OffsetY { get; set; } = 0; 42 | 43 | #endregion 44 | 45 | public static AtariViewUndoInfo BuildForUndo() 46 | { 47 | return new AtariViewUndoInfo() 48 | { 49 | ViewBytes = ViewBytes.Clone() as byte[,], 50 | UseFontOnLine = UseFontOnLine.Clone() as byte[], 51 | Width = Width, 52 | Height = Height, 53 | OffsetX = OffsetX, 54 | OffsetY = OffsetY, 55 | }; 56 | } 57 | 58 | public static void RestoreFromUndo(AtariViewUndoInfo undoInfo) 59 | { 60 | ViewBytes = undoInfo.ViewBytes.Clone() as byte[,]; 61 | UseFontOnLine = undoInfo.UseFontOnLine.Clone() as byte[]; 62 | Width = undoInfo.Width; 63 | Height = undoInfo.Height; 64 | OffsetX = undoInfo.OffsetX; 65 | OffsetY = undoInfo.OffsetY; 66 | } 67 | 68 | public static void Setup() 69 | { 70 | for (var i = 0; i < UseFontOnLine.Length; i++) 71 | { 72 | UseFontOnLine[i] = 1; 73 | } 74 | } 75 | 76 | public static void ForcedResize(int width, int height) 77 | { 78 | Width = width; 79 | Height = height; 80 | ViewBytes = new byte[Width, Height]; 81 | UseFontOnLine = new byte[Height]; 82 | 83 | OffsetX = 0; 84 | OffsetY = 0; 85 | 86 | Setup(); 87 | } 88 | 89 | public static void Resize(int width, int height, byte fontNr) 90 | { 91 | // Change the size of the view to the new size 92 | var newViewBytes = new byte[width, height]; 93 | var newUseFontOnLine = new byte[height]; 94 | 95 | for (var y = 0; y < height; ++y) 96 | newUseFontOnLine[y] = fontNr; 97 | 98 | // Copy as much as possible of the old view into the new view 99 | for (var y = 0; y < height && y < Height; ++y) 100 | { 101 | for (var x = 0; x < width && x < Width; ++x) 102 | { 103 | newViewBytes[x, y] = ViewBytes[x, y]; 104 | } 105 | newUseFontOnLine[y] = UseFontOnLine[y]; 106 | } 107 | 108 | // Set the new view 109 | ViewBytes = newViewBytes; 110 | UseFontOnLine = newUseFontOnLine; 111 | Width = width; 112 | Height = height; 113 | 114 | if (OffsetX+40 >= width) 115 | OffsetX = width-40; 116 | if (OffsetY + 26 >= height) 117 | OffsetY = height - 26; 118 | } 119 | 120 | public static void Load(string lineTypes, string characterBytes, int viewWidth) 121 | { 122 | var useFontOnLine = Convert.FromHexString(lineTypes); 123 | for (var i = 0; i < useFontOnLine.Length; i++) 124 | { 125 | UseFontOnLine[i] = useFontOnLine[i]; 126 | // If OLD format issues 127 | // Font numbers 1-4 128 | if (UseFontOnLine[i] == 0) 129 | ++UseFontOnLine[i]; 130 | } 131 | 132 | var bytes = Convert.FromHexString(characterBytes); 133 | var idx = 0; 134 | for (var y = 0; y < Height; ++y) 135 | { 136 | for (var x = 0; x < viewWidth; ++x) 137 | { 138 | ViewBytes[x, y] = bytes[idx]; 139 | ++idx; 140 | } 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /AtariColorSelector.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker 2 | { 3 | partial class AtariColorSelectorForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | ImagePalette = new PictureBox(); 32 | ImageSelected = new PictureBox(); 33 | LabelActualColor = new Label(); 34 | LabelOldColor = new Label(); 35 | ((System.ComponentModel.ISupportInitialize)ImagePalette).BeginInit(); 36 | ((System.ComponentModel.ISupportInitialize)ImageSelected).BeginInit(); 37 | SuspendLayout(); 38 | // 39 | // ImagePalette 40 | // 41 | ImagePalette.Location = new Point(8, 0); 42 | ImagePalette.Name = "ImagePalette"; 43 | ImagePalette.Size = new Size(145, 272); 44 | ImagePalette.TabIndex = 0; 45 | ImagePalette.TabStop = false; 46 | ImagePalette.MouseDown += ImagePaletteMouseDown; 47 | ImagePalette.MouseMove += ImagePaletteMouseMove; 48 | // 49 | // ImageSelected 50 | // 51 | ImageSelected.Location = new Point(159, 72); 52 | ImageSelected.Name = "ImageSelected"; 53 | ImageSelected.Size = new Size(105, 121); 54 | ImageSelected.TabIndex = 1; 55 | ImageSelected.TabStop = false; 56 | // 57 | // LabelActualColor 58 | // 59 | LabelActualColor.AutoSize = true; 60 | LabelActualColor.Location = new Point(192, 199); 61 | LabelActualColor.Name = "LabelActualColor"; 62 | LabelActualColor.Size = new Size(40, 13); 63 | LabelActualColor.TabIndex = 2; 64 | LabelActualColor.Text = "$01 - 1"; 65 | LabelActualColor.TextAlign = ContentAlignment.MiddleCenter; 66 | // 67 | // LabelOldColor 68 | // 69 | LabelOldColor.AutoSize = true; 70 | LabelOldColor.Location = new Point(192, 53); 71 | LabelOldColor.Name = "LabelOldColor"; 72 | LabelOldColor.Size = new Size(40, 13); 73 | LabelOldColor.TabIndex = 3; 74 | LabelOldColor.Text = "$00 - 0"; 75 | LabelOldColor.TextAlign = ContentAlignment.MiddleCenter; 76 | // 77 | // AtariColorSelectorForm 78 | // 79 | AutoScaleDimensions = new SizeF(6F, 13F); 80 | AutoScaleMode = AutoScaleMode.Font; 81 | BackgroundImageLayout = ImageLayout.None; 82 | ClientSize = new Size(284, 280); 83 | Controls.Add(ImagePalette); 84 | Controls.Add(ImageSelected); 85 | Controls.Add(LabelActualColor); 86 | Controls.Add(LabelOldColor); 87 | FormBorderStyle = FormBorderStyle.FixedDialog; 88 | Name = "AtariColorSelectorForm"; 89 | Text = "AtariColorSelector"; 90 | KeyDown += AtariColorSelectorForm_KeyDown; 91 | ((System.ComponentModel.ISupportInitialize)ImagePalette).EndInit(); 92 | ((System.ComponentModel.ISupportInitialize)ImageSelected).EndInit(); 93 | ResumeLayout(false); 94 | PerformLayout(); 95 | } 96 | 97 | #endregion 98 | 99 | private System.Windows.Forms.PictureBox ImagePalette; 100 | private System.Windows.Forms.PictureBox ImageSelected; 101 | private System.Windows.Forms.Label LabelActualColor; 102 | private System.Windows.Forms.Label LabelOldColor; 103 | } 104 | } -------------------------------------------------------------------------------- /Helpers.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace FontMaker 6 | { 7 | public static class Helpers 8 | { 9 | public static byte AtariConvertChar(byte character) 10 | { 11 | if (character == 32) 12 | { 13 | return 0; 14 | } 15 | 16 | if (character is >= 48 and <= 90) 17 | { 18 | return (byte)(character - 32); 19 | } 20 | 21 | return character; 22 | } 23 | 24 | public static Image GetImage(PictureBox src) 25 | { 26 | var w = src.Bounds.Width; 27 | var h = src.Bounds.Height; 28 | 29 | if (src.Image != null) 30 | return src.Image; 31 | 32 | src.Image = new Bitmap(w, h); 33 | 34 | return src.Image; 35 | } 36 | 37 | public static Image NewImage(PictureBox src) 38 | { 39 | var w = src.Bounds.Width; 40 | var h = src.Bounds.Height; 41 | 42 | if (src.Image != null) 43 | src.Image.Dispose(); 44 | 45 | src.Image = new Bitmap(w, h); 46 | 47 | return src.Image; 48 | } 49 | 50 | /// 51 | /// Open a given url in the default browser 52 | /// 53 | /// url to open 54 | public static void OpenUrl(string url) 55 | { 56 | try 57 | { 58 | Process.Start(url); 59 | } 60 | catch 61 | { 62 | // hack because of this: https://github.com/dotnet/corefx/issues/10361 63 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 64 | { 65 | url = url.Replace("&", "^&"); 66 | Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); 67 | } 68 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 69 | { 70 | Process.Start("xdg-open", url); 71 | } 72 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 73 | { 74 | Process.Start("open", url); 75 | } 76 | else 77 | { 78 | throw; 79 | } 80 | } 81 | } 82 | 83 | public static void GetBuildInfo(ref int v1, ref int v2, ref int v3, ref int v4) 84 | { 85 | try 86 | { 87 | var thisAssembly = typeof(Helpers).Assembly; 88 | var thisAssemblyName = thisAssembly.GetName(); 89 | 90 | var ver = thisAssemblyName.Version; 91 | if (ver == null) return; 92 | v1 = ver.Major; 93 | v2 = ver.Minor; 94 | v3 = ver.Build; 95 | v4 = ver.Revision; 96 | } 97 | catch (Exception) 98 | { 99 | // ignored 100 | } 101 | } 102 | 103 | public static string GetBuildInfoAsString() 104 | { 105 | var v1 = 0; 106 | var v2 = 0; 107 | var v3 = 0; 108 | var v4 = 0; 109 | GetBuildInfo(ref v1, ref v2, ref v3, ref v4); 110 | return $"{v1}.{v2}.{v3}.{v4}"; 111 | } 112 | 113 | public static T GetResource(string shortResourceName) 114 | { 115 | var assembly = Assembly.GetExecutingAssembly(); 116 | var resourceName = $"FontMaker.Resources.{shortResourceName}"; 117 | if (typeof(T) == typeof(string)) 118 | { 119 | using var stream = assembly.GetManifestResourceStream(resourceName); 120 | using var reader = new StreamReader(stream); 121 | return (T)(object)reader.ReadToEnd(); 122 | } 123 | else if (typeof(T) == typeof(byte[])) 124 | { 125 | using var stream = assembly.GetManifestResourceStream(resourceName); 126 | using var reader = new BinaryReader(stream); 127 | return (T)(object)reader.ReadBytes((int)stream.Length); 128 | } 129 | else 130 | throw new Exception("Unsupported resource object type"); 131 | } 132 | 133 | /// 134 | /// Find the AtariPalette entry that best approximates the request RGB color 135 | /// 136 | /// Red color component 137 | /// Green color component 138 | /// Blue color component 139 | /// Reference AtariPalette where the best fit is searched in 140 | /// Index in the AtariPalette that best matches the input RGB 141 | public static byte FindClosest(byte rr, byte gg, byte bb, Color[] pal) 142 | { 143 | byte best = 0; 144 | var bestDistance = 9999999; 145 | 146 | for (var j = 0; j < 128; j++) 147 | { 148 | var i = j * 2; //only compare with EVEN AtariPalette indexes 149 | var distR = rr - pal[i].R; 150 | var distG = gg - pal[i].G; 151 | var distB = bb - pal[i].B; 152 | var newDistance = distR * distR + distG * distG + distB * distB; 153 | 154 | if (bestDistance > newDistance) 155 | { 156 | bestDistance = newDistance; 157 | best = (byte)i; 158 | } 159 | } 160 | 161 | return best; 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /Compressors.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace FontMaker; 4 | 5 | public static class Compressors 6 | { 7 | public enum CompressorType 8 | { 9 | ZX0 = 0, 10 | ZX1 = 1, 11 | ZX2 = 2, 12 | APULTRA = 3, 13 | } 14 | 15 | private static bool IsCompressor0Available { get; set;} 16 | private static bool IsCompressor1Available { get; set; } 17 | private static bool IsCompressor2Available { get; set; } 18 | private static bool IsCompressor3Available { get; set; } 19 | 20 | private static string TempPath { get; set; } 21 | private static string Zx0Exe { get; set; } 22 | private static string Zx1Exe { get; set; } 23 | private static string Zx2Exe { get; set; } 24 | private static string ApultraExe { get; set; } 25 | 26 | // ========================================================================== 27 | // ZX0 compressor interface 28 | // ========================================================================== 29 | public static void Prepare() 30 | { 31 | try 32 | { 33 | TempPath = Path.Combine(Path.GetTempPath(), "afm2025"); 34 | Directory.CreateDirectory(TempPath); 35 | 36 | Zx0Exe = Path.Combine(TempPath, "zx0.exe"); 37 | if (!File.Exists(Zx0Exe)) 38 | { 39 | // Create the zx0.exe file 40 | var buffer = Helpers.GetResource("zx0.exe"); 41 | using var fs = new FileStream(Zx0Exe, FileMode.Create, FileAccess.Write); 42 | fs.Write(buffer, 0, buffer.Length); 43 | fs.Close(); 44 | } 45 | 46 | Zx1Exe = Path.Combine(TempPath, "zx1.exe"); 47 | if (!File.Exists(Zx1Exe)) 48 | { 49 | // Create the zx1.exe file 50 | var buffer = Helpers.GetResource("zx1.exe"); 51 | using var fs = new FileStream(Zx1Exe, FileMode.Create, FileAccess.Write); 52 | fs.Write(buffer, 0, buffer.Length); 53 | fs.Close(); 54 | } 55 | 56 | Zx2Exe = Path.Combine(TempPath, "zx2.exe"); 57 | if (!File.Exists(Zx2Exe)) 58 | { 59 | // Create the zx2.exe file 60 | var buffer = Helpers.GetResource("zx2.exe"); 61 | using var fs = new FileStream(Zx2Exe, FileMode.Create, FileAccess.Write); 62 | fs.Write(buffer, 0, buffer.Length); 63 | fs.Close(); 64 | } 65 | 66 | ApultraExe = Path.Combine(TempPath, "apultra.exe"); 67 | if (!File.Exists(ApultraExe)) 68 | { 69 | // Create the apultra.exe file 70 | var buffer = Helpers.GetResource("apultra.exe"); 71 | using var fs = new FileStream(ApultraExe, FileMode.Create, FileAccess.Write); 72 | fs.Write(buffer, 0, buffer.Length); 73 | fs.Close(); 74 | } 75 | 76 | IsCompressor0Available = File.Exists(Zx0Exe); 77 | IsCompressor1Available = File.Exists(Zx1Exe); 78 | IsCompressor2Available = File.Exists(Zx2Exe); 79 | IsCompressor3Available = File.Exists(ApultraExe); 80 | } 81 | catch (Exception ex) 82 | { 83 | IsCompressor0Available = false; 84 | IsCompressor1Available = false; 85 | IsCompressor2Available = false; 86 | IsCompressor3Available = false; 87 | } 88 | } 89 | 90 | public static byte[] Compress(byte[] data, CompressorType which) 91 | { 92 | var (available, exe, cmdLine) = which switch 93 | { 94 | CompressorType.ZX0 => (IsCompressor0Available, Zx0Exe, "-f"), 95 | CompressorType.ZX1 => (IsCompressor1Available, Zx1Exe, "-f"), 96 | CompressorType.ZX2 => (IsCompressor2Available, Zx2Exe, "-f"), 97 | CompressorType.APULTRA => (IsCompressor2Available, ApultraExe, string.Empty), 98 | _ => (false, null, string.Empty) 99 | }; 100 | if (!available) return data; 101 | 102 | try 103 | { 104 | var tempFile = Path.Combine(TempPath, "temp.zx"); 105 | using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write)) 106 | { 107 | fs.Write(data, 0, data.Length); 108 | fs.Close(); 109 | } 110 | 111 | // Make sure the output file doesn't exist 112 | var outputPath = Path.Combine(TempPath, "output.zx"); 113 | if (File.Exists(outputPath)) 114 | File.Delete(outputPath); 115 | 116 | var process = new Process 117 | { 118 | StartInfo = new ProcessStartInfo 119 | { 120 | FileName = exe, 121 | Arguments = $"{cmdLine} {tempFile} {outputPath}", 122 | CreateNoWindow = true, 123 | UseShellExecute = false, 124 | RedirectStandardOutput = true, 125 | RedirectStandardError = true 126 | } 127 | }; 128 | process.Start(); 129 | process.WaitForExit(); 130 | var compressedData = File.ReadAllBytes(outputPath); 131 | File.Delete(outputPath); 132 | return compressedData; 133 | } 134 | catch (Exception ex) 135 | { 136 | // Handle the exception as needed 137 | return data; 138 | } 139 | } 140 | 141 | public static string GetName(CompressorType which) 142 | { 143 | return which switch 144 | { 145 | CompressorType.ZX0 => "ZX0", 146 | CompressorType.ZX1 => "ZX1", 147 | CompressorType.ZX2 => "ZX2", 148 | CompressorType.APULTRA => "apultra", 149 | _ => "Unknown" 150 | }; 151 | 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /FontMakerConfigurationWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker; 2 | 3 | partial class FontMakerConfigurationWindow 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | buttonCancel = new Button(); 32 | buttonOk = new Button(); 33 | groupBox1 = new GroupBox(); 34 | radioButtonApultra = new RadioButton(); 35 | radioButtonZX1 = new RadioButton(); 36 | radioButtonZX2 = new RadioButton(); 37 | radioButtonZX0 = new RadioButton(); 38 | groupBox1.SuspendLayout(); 39 | SuspendLayout(); 40 | // 41 | // buttonCancel 42 | // 43 | buttonCancel.Location = new Point(156, 84); 44 | buttonCancel.Name = "buttonCancel"; 45 | buttonCancel.Size = new Size(100, 40); 46 | buttonCancel.TabIndex = 5; 47 | buttonCancel.Text = "Cancel"; 48 | buttonCancel.UseVisualStyleBackColor = true; 49 | // 50 | // buttonOk 51 | // 52 | buttonOk.Location = new Point(12, 84); 53 | buttonOk.Name = "buttonOk"; 54 | buttonOk.Size = new Size(100, 40); 55 | buttonOk.TabIndex = 6; 56 | buttonOk.Text = "Ok"; 57 | buttonOk.UseVisualStyleBackColor = true; 58 | buttonOk.Click += buttonOk_Click; 59 | // 60 | // groupBox1 61 | // 62 | groupBox1.Controls.Add(radioButtonApultra); 63 | groupBox1.Controls.Add(radioButtonZX1); 64 | groupBox1.Controls.Add(radioButtonZX2); 65 | groupBox1.Controls.Add(radioButtonZX0); 66 | groupBox1.Location = new Point(12, 12); 67 | groupBox1.Name = "groupBox1"; 68 | groupBox1.Size = new Size(244, 63); 69 | groupBox1.TabIndex = 8; 70 | groupBox1.TabStop = false; 71 | groupBox1.Text = "Which compressor should be used:"; 72 | // 73 | // radioButtonApultra 74 | // 75 | radioButtonApultra.AutoSize = true; 76 | radioButtonApultra.Location = new Point(176, 32); 77 | radioButtonApultra.Name = "radioButtonApultra"; 78 | radioButtonApultra.Size = new Size(62, 17); 79 | radioButtonApultra.TabIndex = 3; 80 | radioButtonApultra.TabStop = true; 81 | radioButtonApultra.Text = "apultra"; 82 | radioButtonApultra.UseVisualStyleBackColor = true; 83 | // 84 | // radioButtonZX1 85 | // 86 | radioButtonZX1.AutoSize = true; 87 | radioButtonZX1.Location = new Point(64, 32); 88 | radioButtonZX1.Name = "radioButtonZX1"; 89 | radioButtonZX1.Size = new Size(43, 17); 90 | radioButtonZX1.TabIndex = 2; 91 | radioButtonZX1.TabStop = true; 92 | radioButtonZX1.Text = "ZX1"; 93 | radioButtonZX1.UseVisualStyleBackColor = true; 94 | // 95 | // radioButtonZX2 96 | // 97 | radioButtonZX2.AutoSize = true; 98 | radioButtonZX2.Location = new Point(120, 32); 99 | radioButtonZX2.Name = "radioButtonZX2"; 100 | radioButtonZX2.Size = new Size(43, 17); 101 | radioButtonZX2.TabIndex = 1; 102 | radioButtonZX2.TabStop = true; 103 | radioButtonZX2.Text = "ZX2"; 104 | radioButtonZX2.UseVisualStyleBackColor = true; 105 | // 106 | // radioButtonZX0 107 | // 108 | radioButtonZX0.AutoSize = true; 109 | radioButtonZX0.Location = new Point(8, 32); 110 | radioButtonZX0.Name = "radioButtonZX0"; 111 | radioButtonZX0.Size = new Size(43, 17); 112 | radioButtonZX0.TabIndex = 0; 113 | radioButtonZX0.TabStop = true; 114 | radioButtonZX0.Text = "ZX0"; 115 | radioButtonZX0.UseVisualStyleBackColor = true; 116 | // 117 | // FontMakerConfigurationWindow 118 | // 119 | AcceptButton = buttonOk; 120 | AutoScaleDimensions = new SizeF(6F, 13F); 121 | AutoScaleMode = AutoScaleMode.Font; 122 | CancelButton = buttonCancel; 123 | ClientSize = new Size(265, 130); 124 | Controls.Add(groupBox1); 125 | Controls.Add(buttonOk); 126 | Controls.Add(buttonCancel); 127 | FormBorderStyle = FormBorderStyle.FixedDialog; 128 | MaximizeBox = false; 129 | MinimizeBox = false; 130 | Name = "FontMakerConfigurationWindow"; 131 | StartPosition = FormStartPosition.CenterParent; 132 | Text = "Atari Font Maker Configuration"; 133 | groupBox1.ResumeLayout(false); 134 | groupBox1.PerformLayout(); 135 | ResumeLayout(false); 136 | } 137 | 138 | #endregion 139 | 140 | private Button buttonCancel; 141 | private Button buttonOk; 142 | private GroupBox groupBox1; 143 | private RadioButton radioButtonZX2; 144 | private RadioButton radioButtonZX0; 145 | private RadioButton radioButtonZX1; 146 | private RadioButton radioButtonApultra; 147 | } -------------------------------------------------------------------------------- /PageEditor.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker 2 | { 3 | partial class PageEditor 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | components = new System.ComponentModel.Container(); 32 | var resources = new System.ComponentModel.ComponentResourceManager(typeof(PageEditor)); 33 | btnCancel = new Button(); 34 | label1 = new Label(); 35 | txtPageName = new TextBox(); 36 | btnUpdate = new Button(); 37 | lbPages = new ListBox(); 38 | btnUp = new Button(); 39 | btnDown = new Button(); 40 | toolTips = new ToolTip(components); 41 | SuspendLayout(); 42 | // 43 | // btnCancel 44 | // 45 | btnCancel.Location = new Point(152, 222); 46 | btnCancel.Name = "btnCancel"; 47 | btnCancel.Size = new Size(75, 23); 48 | btnCancel.TabIndex = 4; 49 | btnCancel.Text = "Cancel"; 50 | btnCancel.UseVisualStyleBackColor = true; 51 | btnCancel.Click += btnCancel_Click; 52 | // 53 | // label1 54 | // 55 | label1.AutoSize = true; 56 | label1.Location = new Point(5, 16); 57 | label1.Name = "label1"; 58 | label1.Size = new Size(67, 13); 59 | label1.TabIndex = 1; 60 | label1.Text = "Page Name:"; 61 | // 62 | // txtPageName 63 | // 64 | txtPageName.Location = new Point(76, 12); 65 | txtPageName.Name = "txtPageName"; 66 | txtPageName.Size = new Size(180, 22); 67 | txtPageName.TabIndex = 1; 68 | toolTips.SetToolTip(txtPageName, "Edit the page name"); 69 | txtPageName.TextChanged += txtPageName_TextChanged; 70 | txtPageName.Enter += txtPageName_Enter; 71 | txtPageName.KeyDown += txtPageName_KeyDown; 72 | txtPageName.Leave += txtPageName_Leave; 73 | // 74 | // btnUpdate 75 | // 76 | btnUpdate.Location = new Point(22, 222); 77 | btnUpdate.Name = "btnUpdate"; 78 | btnUpdate.Size = new Size(75, 23); 79 | btnUpdate.TabIndex = 3; 80 | btnUpdate.Text = "Update"; 81 | toolTips.SetToolTip(btnUpdate, "Save the current page settings"); 82 | btnUpdate.UseVisualStyleBackColor = true; 83 | btnUpdate.Click += btnUpdate_Click; 84 | // 85 | // lbPages 86 | // 87 | lbPages.FormattingEnabled = true; 88 | lbPages.Location = new Point(5, 38); 89 | lbPages.Name = "lbPages"; 90 | lbPages.Size = new Size(222, 173); 91 | lbPages.TabIndex = 2; 92 | lbPages.SelectedIndexChanged += lbPages_SelectedIndexChanged; 93 | lbPages.Enter += lbPages_Enter; 94 | lbPages.KeyDown += lbPages_KeyDown; 95 | lbPages.Leave += lbPages_Leave; 96 | // 97 | // btnUp 98 | // 99 | btnUp.Image = (Image)resources.GetObject("btnUp.Image"); 100 | btnUp.Location = new Point(235, 74); 101 | btnUp.Name = "btnUp"; 102 | btnUp.Size = new Size(21, 23); 103 | btnUp.TabIndex = 3; 104 | btnUp.TabStop = false; 105 | toolTips.SetToolTip(btnUp, "Move the page up in the sequence"); 106 | btnUp.UseVisualStyleBackColor = true; 107 | btnUp.Click += btnUp_Click; 108 | // 109 | // btnDown 110 | // 111 | btnDown.Image = (Image)resources.GetObject("btnDown.Image"); 112 | btnDown.Location = new Point(235, 103); 113 | btnDown.Name = "btnDown"; 114 | btnDown.Size = new Size(21, 23); 115 | btnDown.TabIndex = 4; 116 | btnDown.TabStop = false; 117 | toolTips.SetToolTip(btnDown, "Move the page down in the sequence"); 118 | btnDown.UseVisualStyleBackColor = true; 119 | btnDown.Click += btnDown_Click; 120 | // 121 | // PageEditor 122 | // 123 | AutoScaleDimensions = new SizeF(6F, 13F); 124 | AutoScaleMode = AutoScaleMode.Font; 125 | CancelButton = btnCancel; 126 | ClientSize = new Size(262, 252); 127 | Controls.Add(btnDown); 128 | Controls.Add(btnUp); 129 | Controls.Add(lbPages); 130 | Controls.Add(btnUpdate); 131 | Controls.Add(txtPageName); 132 | Controls.Add(label1); 133 | Controls.Add(btnCancel); 134 | FormBorderStyle = FormBorderStyle.FixedDialog; 135 | MaximizeBox = false; 136 | MinimizeBox = false; 137 | Name = "PageEditor"; 138 | Text = "Edit page names and order"; 139 | Load += PageEditor_Load; 140 | ResumeLayout(false); 141 | PerformLayout(); 142 | } 143 | 144 | #endregion 145 | 146 | private Button btnCancel; 147 | private Label label1; 148 | private TextBox txtPageName; 149 | private Button btnUpdate; 150 | private ListBox lbPages; 151 | private Button btnUp; 152 | private Button btnDown; 153 | private ToolTip toolTips; 154 | } 155 | } -------------------------------------------------------------------------------- /AtariViewConfigWindow.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /FontAnalysisWindow.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /FontMakerConfigurationWindow.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /AtariColorSelector.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /ExportFontWindow.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | -------------------------------------------------------------------------------- /ImportViewWindow.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | 124 | 237, 17 125 | 126 | -------------------------------------------------------------------------------- /ExportViewWindow.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | 124 | 17, 17 125 | 126 | -------------------------------------------------------------------------------- /PageData.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable WFO1000 2 | namespace FontMaker; 3 | 4 | /// 5 | /// Page data container 6 | /// Unique name, view and font select data 7 | /// 8 | public class PageData 9 | { 10 | public PageData(byte[,] view, byte[] selectedFont) : 11 | this(1, "Page 1", view, selectedFont, 0) 12 | { 13 | } 14 | 15 | public PageData(int nr, string name, byte[,] view, byte[] selectedFont, int index) 16 | { 17 | Nr = nr; 18 | Name = name; 19 | View = (view.Clone() as byte[,])!; 20 | SelectedFont = (selectedFont.Clone() as byte[])!; 21 | Index = index; 22 | 23 | UndoBuffer = new AtariViewUndoBuffer(); 24 | } 25 | 26 | public PageData(SavedPageData? pageSrc, int index) 27 | { 28 | // Init the page from saved json 29 | 30 | // Fix for an upgrade 31 | if (pageSrc.Width == 0) 32 | pageSrc.Width = 40; 33 | if (pageSrc.Height == 0) 34 | pageSrc.Height = 26; 35 | 36 | var bytes = Convert.FromHexString(pageSrc.View); 37 | var idx = 0; 38 | var viewData = new byte[pageSrc.Width, pageSrc.Height]; 39 | 40 | for (var y = 0; y < pageSrc.Height; ++y) 41 | { 42 | for (var x = 0; x < pageSrc.Width; ++x) 43 | { 44 | viewData[x, y] = bytes[idx]; 45 | ++idx; 46 | } 47 | } 48 | 49 | Nr = pageSrc.Nr; 50 | Name = pageSrc.Name; 51 | Width = pageSrc.Width; 52 | Height = pageSrc.Height; 53 | View = viewData; 54 | SelectedFont = Convert.FromHexString(pageSrc.SelectedFont); 55 | Index = index; 56 | 57 | UndoBuffer = new AtariViewUndoBuffer(); 58 | } 59 | 60 | public PageData(PageData from) : 61 | this(from.Nr, from.Name, from.View, from.SelectedFont, from.Index) 62 | { 63 | Width = from.Width; 64 | Height = from.Height; 65 | } 66 | 67 | /// 68 | /// Save the current AtariView data to the page container 69 | /// 70 | /// 71 | /// 72 | public void Save() 73 | { 74 | View = AtariView.ViewBytes.Clone() as byte[,]; 75 | SelectedFont = AtariView.UseFontOnLine.Clone() as byte[]; 76 | Width = AtariView.Width; 77 | Height = AtariView.Height; 78 | OffsetX = AtariView.OffsetX; 79 | OffsetY = AtariView.OffsetY; 80 | } 81 | 82 | 83 | /// 84 | /// Unique nr of the page in the list of pages 85 | /// 86 | public int Nr { get; set; } 87 | 88 | public string Name { get; set; } 89 | 90 | public byte[,] View { get; set; } 91 | 92 | public byte[] SelectedFont { get; set; } 93 | 94 | public int Index { get; set; } 95 | 96 | public AtariViewUndoBuffer UndoBuffer { get; set; } 97 | 98 | public int Width { get; set; } = 40; 99 | public int Height { get; set; } = 26; 100 | public int OffsetX { get; set; } = 0; 101 | public int OffsetY { get; set; } = 0; 102 | } 103 | 104 | public class SavedPageData 105 | { 106 | public int Nr { get; set; } 107 | public string Name { get; set; } 108 | public string View { get; set; } 109 | public string SelectedFont { get; set; } 110 | 111 | public int Width { get; set; } = 40; 112 | public int Height { get; set; } = 26; 113 | 114 | public SavedPageData(PageData? srcPage) 115 | { 116 | if (srcPage == null) 117 | return; 118 | 119 | Nr = srcPage.Nr; 120 | Name = srcPage.Name; 121 | SelectedFont = Convert.ToHexString(srcPage.SelectedFont); 122 | Width = srcPage.Width; 123 | Height = srcPage.Height; 124 | 125 | var pageView = string.Empty; 126 | for (var y = 0; y < srcPage.Height; y++) 127 | { 128 | for (var x = 0; x < srcPage.Width; x++) 129 | { 130 | pageView = pageView + $"{srcPage.View[x, y]:X2}"; 131 | } 132 | } 133 | 134 | View = pageView; 135 | } 136 | } 137 | 138 | public partial class FontMakerForm 139 | { 140 | // Everything to do with pages goes here 141 | public List Pages { get; set; } = []; 142 | 143 | public PageData? CurrentPage { get; set; } 144 | 145 | /// 146 | /// The index in the combo box that is currently selected 147 | /// 148 | public int CurrentPageIndex { get; set; } = -1; 149 | 150 | public bool InPagesSetup { get; set; } 151 | 152 | /// 153 | /// Clear the old page data 154 | /// 155 | public void ClearPageList() 156 | { 157 | Pages = []; 158 | CurrentPageIndex = -1; 159 | CurrentPage = null; 160 | } 161 | 162 | /// 163 | /// After data has been loaded from file build the initial page data 164 | /// 165 | public void BuildPageList() 166 | { 167 | // If the page list is empty then create ONE default page 168 | if (Pages.Count == 0) 169 | { 170 | var pageOne = new PageData(AtariView.ViewBytes, AtariView.UseFontOnLine); 171 | Pages.Add(pageOne); 172 | 173 | CurrentPageIndex = 0; 174 | } 175 | 176 | InPagesSetup = true; 177 | comboBoxPages.Items.Clear(); 178 | comboBoxPages.ResetText(); 179 | 180 | foreach (var page in Pages) 181 | { 182 | var idx = comboBoxPages.Items.Add(page.Name); 183 | page.Index = idx; 184 | } 185 | 186 | comboBoxPages.SelectedIndex = 0; 187 | CurrentPageIndex = comboBoxPages.SelectedIndex; 188 | CurrentPage = Pages[CurrentPageIndex]; 189 | 190 | InPagesSetup = false; 191 | 192 | UpdatePageDisplay(); 193 | TransferPagesToViewActions(); 194 | } 195 | 196 | public void SavePageSwitch(int nextPage) 197 | { 198 | if (nextPage < 0 || nextPage >= Pages.Count) return; 199 | comboBoxPages.SelectedIndex = nextPage; 200 | } 201 | private void SaveCurrentPage() 202 | { 203 | // Save the current page data 204 | var currentPage = Pages[CurrentPageIndex]; 205 | currentPage.Save(); 206 | } 207 | 208 | private void SwopPage(bool saveCurrent) 209 | { 210 | var nextPageIndex = comboBoxPages.SelectedIndex; 211 | if (nextPageIndex == -1) return; 212 | 213 | if (saveCurrent) 214 | { 215 | SaveCurrentPage(); 216 | } 217 | 218 | SwopPageAction(nextPageIndex); 219 | } 220 | 221 | private void SwopToPage(int nextPageIndex) 222 | { 223 | SaveCurrentPage(); 224 | SwopPageAction(nextPageIndex); 225 | } 226 | 227 | private void SwopPageAction(int nextPageIndex) 228 | { 229 | // Select the next page and copy the data 230 | var page = Pages[nextPageIndex]; 231 | 232 | AtariView.ViewBytes = page.View.Clone() as byte[,]; 233 | AtariView.UseFontOnLine = page.SelectedFont.Clone() as byte[]; 234 | AtariView.Width = page.Width; 235 | AtariView.Height = page.Height; 236 | AtariView.OffsetX = page.OffsetX; 237 | AtariView.OffsetY = page.OffsetY; 238 | 239 | UpdateHVScrollBars(AtariView.OffsetX, AtariView.OffsetY); 240 | 241 | CurrentPageIndex = nextPageIndex; 242 | CurrentPage = Pages[CurrentPageIndex]; 243 | } 244 | 245 | private void UpdatePageDisplay() 246 | { 247 | labelCurrentPageInfo.Text = $"{comboBoxPages.SelectedIndex+1} of {comboBoxPages.Items.Count}"; 248 | RedrawView(); 249 | RedrawLineTypes(); 250 | 251 | buttonDeletePage.Enabled = Pages.Count > 1; 252 | 253 | UpdateViewUndoButtons(); 254 | } 255 | } -------------------------------------------------------------------------------- /Configuration.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using TinyJson; 3 | 4 | namespace FontMaker 5 | { 6 | public class ConfigurationJson 7 | { 8 | public List? ColorSets { get; set; } = []; 9 | public int AnalysisColor { get; set; } 10 | public int AnalysisAlpha { get; set; } 11 | 12 | public bool AnalysisDuplicates { get; set; } 13 | public int AnalysisDupColor { get; set; } 14 | public int AnalysisDupAlpha { get; set; } 15 | 16 | public bool ExportViewRemember {get; set; } 17 | public int ExportViewExportType { get; set; } 18 | public int ExportViewDataType { get; set; } 19 | public int ExportViewRegionX { get; set; } 20 | public int ExportViewRegionY { get; set; } 21 | public int ExportViewRegionW { get; set; } 22 | public int ExportViewRegionH { get; set; } 23 | public int ExportViewOffsetX { get; set; } 24 | public int ExportViewOffsetY { get; set; } 25 | 26 | public bool ExportViewTranspose { get; set; } 27 | 28 | public bool ImportViewRemember { get; set; } 29 | public int ImportLineWidth { get; set; } 30 | public int ImportSkipX { get; set; } 31 | public int ImportSkipY { get; set; } 32 | public int ImportWidth { get; set; } 33 | public int ImportHeight { get; set; } 34 | 35 | public int CompressorId { get; set; } // 0 = zx0, 1 = zx1, 2 = zx2 36 | 37 | } 38 | public static class Configuration 39 | { 40 | 41 | private static readonly string Filename = Path.Join(AppContext.BaseDirectory, "FontMaker.json"); 42 | 43 | public static ConfigurationJson Values { get; set; } = new ConfigurationJson(); 44 | 45 | public static void Load() 46 | { 47 | try 48 | { 49 | var jsonText = File.ReadAllText(Filename); // Load JSON configuration file 50 | Values = jsonText.FromJson(); // Parse the JSON into an object 51 | } 52 | catch 53 | { 54 | // ignored 55 | } 56 | 57 | VerifyDefaults(); 58 | } 59 | 60 | public static void Save() 61 | { 62 | try 63 | { 64 | var txt = Values.ToJson(); 65 | 66 | File.WriteAllText(Filename, txt, Encoding.UTF8); 67 | } 68 | catch 69 | { 70 | // ignored 71 | } 72 | } 73 | 74 | public static void VerifyDefaults() 75 | { 76 | Values ??= new ConfigurationJson(); 77 | // Make sure that there are 6 color sets 78 | Values.ColorSets ??= []; 79 | 80 | if (Values.ColorSets.Count < 6) 81 | { 82 | for (var i = Values.ColorSets.Count; i < 6; ++i) 83 | { 84 | Values.ColorSets.Add("0E0028CA9446"); 85 | } 86 | } 87 | 88 | if (Values.AnalysisColor < FontAnalysisWindow.AnalysisMinColorIndex || Values.AnalysisColor > FontAnalysisWindow.AnalysisMaxColorIndex) 89 | { 90 | Values.AnalysisColor = FontAnalysisWindow.AnalysisMinColorIndex; 91 | } 92 | 93 | if (Values.AnalysisAlpha < FontAnalysisWindow.AnalysisMinAlpha || Values.AnalysisAlpha > FontAnalysisWindow.AnalysisMaxAlpha) 94 | { 95 | Values.AnalysisAlpha = FontAnalysisWindow.AnalysisMinAlpha + (FontAnalysisWindow.AnalysisMaxAlpha - FontAnalysisWindow.AnalysisMinAlpha) / 2; 96 | } 97 | 98 | if (Values.AnalysisDupColor < FontAnalysisWindow.AnalysisMinColorIndex || Values.AnalysisDupColor > FontAnalysisWindow.AnalysisMaxDuplicateColorIndex) 99 | { 100 | Values.AnalysisDupColor = FontAnalysisWindow.AnalysisMinColorIndex; 101 | } 102 | if (Values.AnalysisDupAlpha < FontAnalysisWindow.AnalysisMinAlpha || Values.AnalysisDupAlpha > FontAnalysisWindow.AnalysisMaxAlpha) 103 | { 104 | Values.AnalysisDupAlpha = FontAnalysisWindow.AnalysisMinAlpha + (FontAnalysisWindow.AnalysisMaxAlpha - FontAnalysisWindow.AnalysisMinAlpha) / 2; 105 | } 106 | 107 | if (Values.ExportViewRegionX is < 0 or >= 40) Values.ExportViewRegionX = 0; 108 | if (Values.ExportViewRegionY is < 0 or >= 26) Values.ExportViewRegionY = 0; 109 | if (Values.ExportViewRegionX + Values.ExportViewRegionW >= 40) Values.ExportViewRegionW = 1; 110 | if (Values.ExportViewRegionY + Values.ExportViewRegionH >= 26) Values.ExportViewRegionH = 1; 111 | 112 | if (Values.ImportLineWidth is < 1) Values.ImportLineWidth = 1; 113 | if (Values.ImportWidth is < 1) Values.ImportWidth = 1; 114 | if (Values.ImportHeight is < 1) Values.ImportHeight = 1; 115 | } 116 | } 117 | 118 | public partial class FontMakerForm 119 | { 120 | public void LoadConfiguration() 121 | { 122 | Configuration.Load(); 123 | 124 | // Transfer the loaded values 125 | ColorSets = Configuration.Values.ColorSets; 126 | 127 | FontAnalysisWindowForm.SetDefaults( 128 | Configuration.Values.AnalysisColor, 129 | Configuration.Values.AnalysisAlpha, 130 | Configuration.Values.AnalysisDuplicates, 131 | Configuration.Values.AnalysisDupColor, 132 | Configuration.Values.AnalysisDupAlpha 133 | ); 134 | ExportViewWindowForm.LoadConfiguration( 135 | Configuration.Values.ExportViewRemember, 136 | Configuration.Values.ExportViewExportType, 137 | Configuration.Values.ExportViewDataType, 138 | new Rectangle(Configuration.Values.ExportViewRegionX, Configuration.Values.ExportViewRegionY, Configuration.Values.ExportViewRegionW, Configuration.Values.ExportViewRegionH), 139 | new Point(Configuration.Values.ExportViewOffsetX, Configuration.Values.ExportViewOffsetY), 140 | Configuration.Values.ExportViewTranspose 141 | ); 142 | 143 | ImportViewWindowForm.LoadConfiguration( 144 | Configuration.Values.ImportViewRemember, 145 | Configuration.Values.ImportLineWidth, 146 | Configuration.Values.ImportSkipX, 147 | Configuration.Values.ImportSkipY, 148 | Configuration.Values.ImportWidth, 149 | Configuration.Values.ImportHeight 150 | ); 151 | 152 | CompressorId = (Compressors.CompressorType)Configuration.Values.CompressorId; 153 | } 154 | 155 | public void SaveConfiguration() 156 | { 157 | // Transfer the settings into the configuration 158 | Configuration.Values.ColorSets = ColorSets; 159 | Configuration.Values.AnalysisColor = FontAnalysisWindowForm.GetHighlightColor; 160 | Configuration.Values.AnalysisAlpha = FontAnalysisWindowForm.GetHighlightAlpha; 161 | 162 | Configuration.Values.AnalysisDuplicates = FontAnalysisWindowForm.GetDuplicates; 163 | Configuration.Values.AnalysisDupColor = FontAnalysisWindowForm.GetDuplicateColor; 164 | Configuration.Values.AnalysisDupAlpha = FontAnalysisWindowForm.GetDuplicateAlpha; 165 | 166 | ( 167 | Configuration.Values.ExportViewRemember, 168 | Configuration.Values.ExportViewExportType, 169 | Configuration.Values.ExportViewDataType, 170 | var exportRegion, 171 | var exportOffset, 172 | Configuration.Values.ExportViewTranspose 173 | ) = ExportViewWindowForm.SaveConfiguration(); 174 | 175 | Configuration.Values.ExportViewRegionX = exportRegion.X; 176 | Configuration.Values.ExportViewRegionY = exportRegion.Y; 177 | Configuration.Values.ExportViewRegionW = exportRegion.Width; 178 | Configuration.Values.ExportViewRegionH = exportRegion.Height; 179 | Configuration.Values.ExportViewOffsetX = exportOffset.X; 180 | Configuration.Values.ExportViewOffsetY = exportOffset.Y; 181 | 182 | ( 183 | Configuration.Values.ImportViewRemember, 184 | Configuration.Values.ImportLineWidth, 185 | Configuration.Values.ImportSkipX, 186 | Configuration.Values.ImportSkipY, 187 | Configuration.Values.ImportWidth, 188 | Configuration.Values.ImportHeight 189 | ) = ImportViewWindowForm.SaveConfiguration(); 190 | 191 | Configuration.Values.CompressorId = (int)CompressorId; 192 | 193 | Configuration.Save(); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /ViewActionsWindow.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable WFO1000 2 | namespace FontMaker 3 | { 4 | public partial class ViewActionsWindow : Form 5 | { 6 | private FontMakerForm? MainForm { get; set; } 7 | 8 | /// 9 | /// The index in the combo box that is currently selected 10 | /// 11 | public int CurrentPageIndex { get; set; } = -1; 12 | 13 | public Rectangle ActionArea { get; set; } 14 | 15 | private bool InPagesSetup { get; set; } 16 | 17 | #region Data from the outside 18 | public List? Pages { get; set; } = null; 19 | #endregion 20 | 21 | private byte ReplaceThisChar { get; set; } 22 | private byte ReplaceWithThisChar { get; set; } 23 | private bool CanReplaceInArea { get; set; } 24 | 25 | private byte FillWithThisChar { get; set; } 26 | 27 | public ViewActionsWindow(FontMakerForm? mainForm) 28 | { 29 | MainForm = mainForm; 30 | InitializeComponent(); 31 | } 32 | 33 | private void btnClose_Click(object sender, EventArgs e) 34 | { 35 | this.Hide(); 36 | } 37 | 38 | private void ViewActionsWindow_FormClosing(object sender, FormClosingEventArgs e) 39 | { 40 | this.Hide(); 41 | e.Cancel = true; // This cancels the close event 42 | } 43 | 44 | public void RebuildPagesDropDown(List? pages, int selectIndex) 45 | { 46 | InPagesSetup = true; 47 | 48 | Pages = pages; 49 | // Clear the old list 50 | comboBoxPages.Items.Clear(); 51 | comboBoxPages.ResetText(); 52 | 53 | if (Pages is null || Pages.Count == 0) 54 | return; 55 | 56 | foreach (var page in Pages) 57 | { 58 | var idx = comboBoxPages.Items.Add(page.Name); 59 | page.Index = idx; 60 | } 61 | 62 | comboBoxPages.SelectedIndex = selectIndex; //comboBoxPages.Items.Count - 1; 63 | CurrentPageIndex = selectIndex; 64 | 65 | InPagesSetup = false; 66 | } 67 | 68 | /// 69 | /// 70 | /// 71 | /// 72 | public void SelectPage(int selectIndex) 73 | { 74 | InPagesSetup = true; 75 | 76 | comboBoxPages.SelectedIndex = selectIndex; //comboBoxPages.Items.Count - 1; 77 | CurrentPageIndex = selectIndex; 78 | 79 | InPagesSetup = false; 80 | } 81 | 82 | private void ViewActionsWindow_Pages_SelectedIndexChanged(object sender, EventArgs e) 83 | { 84 | if (InPagesSetup) return; 85 | 86 | MainForm?.PickPage(comboBoxPages.SelectedIndex); 87 | } 88 | 89 | private void pictureBoxX_Click(object sender, EventArgs e) 90 | { 91 | ReplaceThisChar = MainForm?.RenderCharIntoPictureBox(pictureBoxX) ?? 0; 92 | labelReplaceX.Text = $"#{ReplaceThisChar}"; 93 | 94 | EnableReplaceCharButton(); 95 | } 96 | 97 | private void pictureBoxY_Click(object sender, EventArgs e) 98 | { 99 | ReplaceWithThisChar = MainForm?.RenderCharIntoPictureBox(pictureBoxY) ?? 0; 100 | labelReplaceY.Text = $"#{ReplaceWithThisChar}"; 101 | 102 | EnableReplaceCharButton(); 103 | } 104 | private void checkFont_CheckedChanged(object sender, EventArgs e) 105 | { 106 | EnableReplaceCharButton(); 107 | } 108 | 109 | private void EnableReplaceCharButton() 110 | { 111 | var isEnabled = ReplaceThisChar != ReplaceWithThisChar; 112 | var haveOneFont = checkFont1.Checked || checkFont2.Checked || checkFont3.Checked || checkFont4.Checked; 113 | 114 | buttonReplaceXwithYInView.Enabled = isEnabled && haveOneFont; 115 | buttonReplaceXwithYInArea.Enabled = CanReplaceInArea && isEnabled && haveOneFont; 116 | } 117 | private void ButtonReplaceXwithYInViewClick(object sender, EventArgs e) 118 | { 119 | MainForm?.ReplaceCharXWithY(ReplaceThisChar, ReplaceWithThisChar, checkFont1.Checked, checkFont2.Checked, checkFont3.Checked, checkFont4.Checked, new Rectangle(0, 0, AtariView.Width - 1, AtariView.Height - 1)); 120 | } 121 | 122 | private void buttonReplaceXwithYInArea_Click(object sender, EventArgs e) 123 | { 124 | MainForm?.ReplaceCharXWithY(ReplaceThisChar, ReplaceWithThisChar, checkFont1.Checked, checkFont2.Checked, checkFont3.Checked, checkFont4.Checked, ActionArea); 125 | } 126 | 127 | #region External interface 128 | 129 | public void UpdateViewInformation(bool isMegaCopy, bool haveRegion, Rectangle region) 130 | { 131 | ActionArea = region; 132 | 133 | buttonAreaShiftLeft.Enabled = isMegaCopy && haveRegion; 134 | buttonAreaShiftRight.Enabled = isMegaCopy && haveRegion; 135 | buttonAreaShiftUp.Enabled = isMegaCopy && haveRegion; 136 | buttonAreaShiftDown.Enabled = isMegaCopy && haveRegion; 137 | buttonClearArea.Enabled = isMegaCopy && haveRegion; 138 | buttonFillArea.Enabled = isMegaCopy && haveRegion; 139 | 140 | CanReplaceInArea = isMegaCopy && haveRegion; 141 | 142 | if (isMegaCopy && haveRegion) 143 | { 144 | labelAreaInfo.Text = $"X:{region.Left} Y:{region.Top} W:{region.Width + 1} H:{region.Height + 1}"; 145 | } 146 | else 147 | { 148 | labelAreaInfo.Text = string.Empty; 149 | } 150 | 151 | EnableReplaceCharButton(); 152 | } 153 | #endregion 154 | 155 | private void buttonAreaShiftUp_Click(object sender, EventArgs e) 156 | { 157 | MainForm?.ActionAreaShift(comboBoxPages.SelectedIndex, FontMakerForm.DirectionFlags.Up, ActionArea); 158 | } 159 | private void buttonAreaShiftDown_Click(object sender, EventArgs e) 160 | { 161 | MainForm?.ActionAreaShift(comboBoxPages.SelectedIndex, FontMakerForm.DirectionFlags.Down, ActionArea); 162 | } 163 | private void buttonAreaShiftLeft_Click(object sender, EventArgs e) 164 | { 165 | MainForm?.ActionAreaShift(comboBoxPages.SelectedIndex, FontMakerForm.DirectionFlags.Left, ActionArea); 166 | } 167 | private void buttonAreaShiftRight_Click(object sender, EventArgs e) 168 | { 169 | MainForm?.ActionAreaShift(comboBoxPages.SelectedIndex, FontMakerForm.DirectionFlags.Right, ActionArea); 170 | } 171 | 172 | private void buttonViewShiftUp_Click(object sender, EventArgs e) 173 | { 174 | MainForm?.ActionAreaShift(comboBoxPages.SelectedIndex, FontMakerForm.DirectionFlags.Up, new Rectangle(0, 0, AtariView.Width - 1, AtariView.Height - 1)); 175 | } 176 | private void buttonViewShiftDown_Click(object sender, EventArgs e) 177 | { 178 | MainForm?.ActionAreaShift(comboBoxPages.SelectedIndex, FontMakerForm.DirectionFlags.Down, new Rectangle(0, 0, AtariView.Width - 1, AtariView.Height - 1)); 179 | } 180 | private void buttonViewShiftLeft_Click(object sender, EventArgs e) 181 | { 182 | MainForm?.ActionAreaShift(comboBoxPages.SelectedIndex, FontMakerForm.DirectionFlags.Left, new Rectangle(0, 0, AtariView.Width - 1, AtariView.Height - 1)); 183 | } 184 | private void buttonViewShiftRight_Click(object sender, EventArgs e) 185 | { 186 | MainForm?.ActionAreaShift(comboBoxPages.SelectedIndex, FontMakerForm.DirectionFlags.Right, new Rectangle(0, 0, AtariView.Width - 1, AtariView.Height - 1)); 187 | } 188 | 189 | private void buttonClearView_Click(object sender, EventArgs e) 190 | { 191 | MainForm?.FillArea(new Rectangle(0, 0, AtariView.Width-1, AtariView.Height-1), 0); 192 | } 193 | 194 | private void buttonClearArea_Click(object sender, EventArgs e) 195 | { 196 | MainForm?.FillArea(ActionArea, 0); 197 | } 198 | 199 | private void pictureBoxFill_Click(object sender, EventArgs e) 200 | { 201 | FillWithThisChar = MainForm?.RenderCharIntoPictureBox(pictureBoxFill) ?? 0; 202 | labelFiller.Text = $"#{FillWithThisChar}"; 203 | 204 | EnableFillerButton(); 205 | } 206 | private void EnableFillerButton() 207 | { 208 | var isEnabled = FillWithThisChar != ReplaceWithThisChar; 209 | var haveOneFont = checkFont1.Checked || checkFont2.Checked || checkFont3.Checked || checkFont4.Checked; 210 | 211 | buttonReplaceXwithYInView.Enabled = isEnabled && haveOneFont; 212 | buttonReplaceXwithYInArea.Enabled = CanReplaceInArea && isEnabled && haveOneFont; 213 | } 214 | 215 | private void buttonFillView_Click(object sender, EventArgs e) 216 | { 217 | MainForm?.FillArea(new Rectangle(0, 0, AtariView.Width - 1, AtariView.Height - 1), FillWithThisChar); 218 | } 219 | 220 | private void buttonFillArea_Click(object sender, EventArgs e) 221 | { 222 | MainForm?.FillArea(ActionArea, FillWithThisChar); 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /PageEditor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | 3 | namespace FontMaker 4 | { 5 | public partial class PageEditor : Form 6 | { 7 | private Color _restoreTextBoxColor; 8 | private Color _restoreListBoxToColor; 9 | 10 | /// 11 | /// Build the new list of page data in the correct order 12 | /// 13 | /// 14 | public List GetPages() 15 | { 16 | var lst = new List(); 17 | for (var idx = 0; idx < PageOrder.Count; ++idx) 18 | { 19 | lst.Add(MyPages[PageOrder[idx]]); 20 | } 21 | 22 | return lst; 23 | } 24 | 25 | /// 26 | /// Original page data copy 27 | /// 28 | private List MyPages { get; } 29 | 30 | /// 31 | /// What is the order the original pages will be arranged in. 32 | /// Uses the "Index" field of the page to set the order 33 | /// 34 | private ObservableCollection PageOrder { get; } 35 | 36 | /// 37 | /// Index into Pages[] to indicate which one is being edited 38 | /// 39 | private int CurrentPageIndex { get; set; } 40 | 41 | /// 42 | /// List of data to be displayed in the ListBox (Index, Name) 43 | /// 44 | private List _dynList; 45 | 46 | private bool _inSetup; 47 | 48 | /// 49 | /// Create the PageEditor dialog. 50 | /// Makes a deep copy of all the page data. 51 | /// 52 | /// List of current page data 53 | /// Selected page index 54 | public PageEditor(IEnumerable pages, int currentPageIndex) 55 | { 56 | InitializeComponent(); 57 | 58 | MyPages = new List(); 59 | foreach (var unit in pages) 60 | { 61 | MyPages.Add(new PageData(unit)); 62 | } 63 | CurrentPageIndex = currentPageIndex; // Which page is currently displayed 64 | PageOrder = new ObservableCollection(MyPages.Select(pg => pg.Index).ToList()); // Index list (used to reorder the pages) 65 | } 66 | 67 | /// 68 | /// Populate the GUI 69 | /// 70 | /// 71 | /// 72 | private void PageEditor_Load(object sender, EventArgs e) 73 | { 74 | _inSetup = true; 75 | 76 | var page = MyPages[CurrentPageIndex]; 77 | txtPageName.Text = page?.Name ?? "Unknown"; 78 | 79 | // Build the list for the re-order list box 80 | RebuildListBox(CurrentPageIndex); 81 | 82 | _restoreTextBoxColor = txtPageName.BackColor; 83 | _restoreListBoxToColor = lbPages.BackColor; 84 | 85 | _inSetup = false; 86 | } 87 | 88 | /// 89 | /// The page order list box is dynamically linked to the _dynList data. 90 | /// 91 | /// Which entry is selected 92 | private void RebuildListBox(int selectThisIndex) 93 | { 94 | _inSetup = true; 95 | 96 | _dynList = new List(); 97 | for (var idx = 0; idx < MyPages.Count; idx++) 98 | { 99 | var thisPage = MyPages[PageOrder[idx]]; 100 | _dynList.Add(new { Index = thisPage.Index, Name = thisPage.Name, Page = thisPage }); 101 | } 102 | 103 | lbPages.DataSource = null; 104 | lbPages.DataSource = _dynList; 105 | lbPages.DisplayMember = "Name"; 106 | lbPages.ValueMember = "Index"; 107 | 108 | lbPages.SetSelected(selectThisIndex, true); 109 | CurrentPageIndex = selectThisIndex; 110 | _inSetup = false; 111 | } 112 | 113 | /// 114 | /// Tell the caller that the editor is closed with OK 115 | /// 116 | private void btnUpdate_Click(object sender, EventArgs e) 117 | { 118 | // Fill the data areas 119 | DialogResult = DialogResult.OK; 120 | Close(); 121 | } 122 | 123 | /// 124 | /// Tell the caller that the editor is closed via cancel 125 | /// 126 | /// 127 | /// 128 | private void btnCancel_Click(object sender, EventArgs e) 129 | { 130 | DialogResult = DialogResult.Cancel; 131 | Close(); 132 | } 133 | 134 | /// 135 | /// The name of a page was edited 136 | /// - save it in the page 137 | /// - validate the [Update] button state 138 | /// - rebuild the re-order list 139 | /// 140 | private void txtPageName_TextChanged(object sender, EventArgs e) 141 | { 142 | if (_inSetup) return; 143 | 144 | var page = MyPages[PageOrder[lbPages.SelectedIndex]]; 145 | page.Name = txtPageName.Text; 146 | 147 | CheckValidState(); 148 | 149 | RebuildListBox(lbPages.SelectedIndex); 150 | } 151 | 152 | /// 153 | /// Check that all pages have a name. 154 | /// If not then the [Update] button is disabled 155 | /// 156 | private void CheckValidState() 157 | { 158 | var valid = true; 159 | for (var i = 0; valid && i < MyPages.Count; i++) 160 | { 161 | if (MyPages[i].Name.Trim().Length == 0) 162 | valid = false; 163 | } 164 | 165 | btnUpdate.Enabled = valid; 166 | } 167 | 168 | /// 169 | /// Move the selected entry up 170 | /// 171 | private void btnUp_Click(object sender, EventArgs e) 172 | { 173 | MoveSelectedItem(lbPages, -1); 174 | } 175 | 176 | /// 177 | /// Move the selected entry down 178 | /// 179 | private void btnDown_Click(object sender, EventArgs e) 180 | { 181 | MoveSelectedItem(lbPages, 1); 182 | } 183 | 184 | /// 185 | /// Do the actual movement in the PageOrder list 186 | /// 187 | /// The list box that we look into 188 | /// -1 / +1 189 | public void MoveSelectedItem(ListBox listBox, int direction) 190 | { 191 | 192 | // Checking selected item 193 | if (listBox.SelectedItem == null || listBox.SelectedIndex < 0) 194 | return; // No selected item - nothing to do 195 | 196 | // Calculate new index using move direction 197 | var newIndex = listBox.SelectedIndex + direction; 198 | 199 | // Checking bounds of the range 200 | if (newIndex < 0 || newIndex >= listBox.Items.Count) 201 | return; // Index out of range - nothing to do 202 | 203 | PageOrder.Move(listBox.SelectedIndex, newIndex); 204 | 205 | RebuildListBox(newIndex); 206 | } 207 | 208 | /// 209 | /// The reorder box selection has changed, so make the new page's name editable 210 | /// 211 | /// 212 | /// 213 | private void lbPages_SelectedIndexChanged(object sender, EventArgs e) 214 | { 215 | if (_inSetup) return; 216 | 217 | // A new page was selected 218 | // Map the new index to a page 219 | CurrentPageIndex = lbPages.SelectedIndex; 220 | 221 | var page = MyPages[PageOrder[CurrentPageIndex]]; 222 | 223 | _inSetup = true; 224 | 225 | txtPageName.Text = page.Name; 226 | 227 | _inSetup = false; 228 | } 229 | 230 | private void txtPageName_KeyDown(object sender, KeyEventArgs e) 231 | { 232 | if (e.KeyCode == Keys.Enter) 233 | { 234 | btnUpdate.Focus(); 235 | } 236 | } 237 | 238 | private void lbPages_KeyDown(object sender, KeyEventArgs e) 239 | { 240 | if (e.KeyCode == Keys.Enter) 241 | { 242 | txtPageName.Focus(); 243 | } 244 | } 245 | 246 | private void lbPages_Enter(object sender, EventArgs e) 247 | { 248 | lbPages.BackColor = Color.Beige; 249 | } 250 | 251 | private void lbPages_Leave(object sender, EventArgs e) 252 | { 253 | lbPages.BackColor = _restoreListBoxToColor; 254 | } 255 | 256 | private void txtPageName_Enter(object sender, EventArgs e) 257 | { 258 | txtPageName.BackColor = Color.Beige; 259 | } 260 | 261 | private void txtPageName_Leave(object sender, EventArgs e) 262 | { 263 | txtPageName.BackColor = _restoreTextBoxColor; 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /ExportFontWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker 2 | { 3 | partial class ExportFontWindow 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | Label1 = new Label(); 32 | Label2 = new Label(); 33 | Label3 = new Label(); 34 | ComboBoxExportType = new ComboBox(); 35 | ComboBoxDataType = new ComboBox(); 36 | Button_SaveAs = new Button(); 37 | Button_Cancel = new Button(); 38 | MemoExport = new RichTextBox(); 39 | ComboBoxFontNumber = new ComboBox(); 40 | ButtonCopyClipboard = new Button(); 41 | saveDialog = new SaveFileDialog(); 42 | withCompression = new CheckBox(); 43 | labelSizeInfo = new Label(); 44 | SuspendLayout(); 45 | // 46 | // Label1 47 | // 48 | Label1.AutoSize = true; 49 | Label1.Location = new Point(8, 46); 50 | Label1.Name = "Label1"; 51 | Label1.Size = new Size(85, 13); 52 | Label1.TabIndex = 0; 53 | Label1.Text = "Export font to :"; 54 | // 55 | // Label2 56 | // 57 | Label2.AutoSize = true; 58 | Label2.Location = new Point(8, 78); 59 | Label2.Name = "Label2"; 60 | Label2.Size = new Size(62, 13); 61 | Label2.TabIndex = 1; 62 | Label2.Text = "Data type :"; 63 | // 64 | // Label3 65 | // 66 | Label3.AutoSize = true; 67 | Label3.Location = new Point(8, 13); 68 | Label3.Name = "Label3"; 69 | Label3.Size = new Size(68, 13); 70 | Label3.TabIndex = 2; 71 | Label3.Text = "Select font :"; 72 | // 73 | // ComboBoxExportType 74 | // 75 | ComboBoxExportType.DropDownStyle = ComboBoxStyle.DropDownList; 76 | ComboBoxExportType.FormattingEnabled = true; 77 | ComboBoxExportType.Items.AddRange(new object[] { "Image BMP Mono", "Image BMP Color", "Assembler", "Action! ", "Atari Basic", "FastBasic", "MADS dta", "C data {}", "MadPascal Array", "Binary Data", "Basic listing file" }); 78 | ComboBoxExportType.Location = new Point(104, 43); 79 | ComboBoxExportType.Name = "ComboBoxExportType"; 80 | ComboBoxExportType.Size = new Size(216, 21); 81 | ComboBoxExportType.TabIndex = 0; 82 | ComboBoxExportType.SelectedIndexChanged += ComboBoxExportTypeChange; 83 | // 84 | // ComboBoxDataType 85 | // 86 | ComboBoxDataType.DropDownStyle = ComboBoxStyle.DropDownList; 87 | ComboBoxDataType.FormattingEnabled = true; 88 | ComboBoxDataType.Location = new Point(104, 75); 89 | ComboBoxDataType.Name = "ComboBoxDataType"; 90 | ComboBoxDataType.Size = new Size(216, 21); 91 | ComboBoxDataType.TabIndex = 1; 92 | ComboBoxDataType.SelectedIndexChanged += ComboBoxDataTypeChange; 93 | // 94 | // Button_SaveAs 95 | // 96 | Button_SaveAs.Location = new Point(326, 8); 97 | Button_SaveAs.Name = "Button_SaveAs"; 98 | Button_SaveAs.Size = new Size(81, 25); 99 | Button_SaveAs.TabIndex = 2; 100 | Button_SaveAs.Text = "Save as..."; 101 | Button_SaveAs.UseVisualStyleBackColor = true; 102 | Button_SaveAs.Click += ButtonSaveAsClick; 103 | // 104 | // Button_Cancel 105 | // 106 | Button_Cancel.Location = new Point(326, 73); 107 | Button_Cancel.Name = "Button_Cancel"; 108 | Button_Cancel.Size = new Size(81, 25); 109 | Button_Cancel.TabIndex = 3; 110 | Button_Cancel.Text = "Cancel"; 111 | Button_Cancel.UseVisualStyleBackColor = true; 112 | Button_Cancel.Click += Button_CancelClick; 113 | // 114 | // MemoExport 115 | // 116 | MemoExport.Location = new Point(8, 154); 117 | MemoExport.Name = "MemoExport"; 118 | MemoExport.Size = new Size(399, 353); 119 | MemoExport.TabIndex = 4; 120 | MemoExport.Text = ""; 121 | MemoExport.KeyPress += MemoExportKeyPress; 122 | // 123 | // ComboBoxFontNumber 124 | // 125 | ComboBoxFontNumber.DropDownStyle = ComboBoxStyle.DropDownList; 126 | ComboBoxFontNumber.FormattingEnabled = true; 127 | ComboBoxFontNumber.Items.AddRange(new object[] { "1", "2", "3", "4", "1+2", "3+4", "1+2+3+4" }); 128 | ComboBoxFontNumber.Location = new Point(104, 8); 129 | ComboBoxFontNumber.Name = "ComboBoxFontNumber"; 130 | ComboBoxFontNumber.Size = new Size(216, 21); 131 | ComboBoxFontNumber.TabIndex = 5; 132 | ComboBoxFontNumber.SelectedIndexChanged += ComboBoxFontNumber_SelectedIndexChanged; 133 | // 134 | // ButtonCopyClipboard 135 | // 136 | ButtonCopyClipboard.Location = new Point(326, 42); 137 | ButtonCopyClipboard.Name = "ButtonCopyClipboard"; 138 | ButtonCopyClipboard.Size = new Size(81, 25); 139 | ButtonCopyClipboard.TabIndex = 6; 140 | ButtonCopyClipboard.Text = "To Clipboard"; 141 | ButtonCopyClipboard.UseVisualStyleBackColor = true; 142 | ButtonCopyClipboard.Click += ButtonCopyClipboardClick; 143 | // 144 | // saveDialog 145 | // 146 | saveDialog.FileName = "font-export"; 147 | // 148 | // checkZX0 149 | // 150 | withCompression.AutoSize = true; 151 | withCompression.Location = new Point(104, 108); 152 | withCompression.Name = "withCompression"; 153 | withCompression.Size = new Size(169, 17); 154 | withCompression.TabIndex = 7; 155 | withCompression.Text = "Compress the data with ZX0"; 156 | withCompression.UseVisualStyleBackColor = true; 157 | withCompression.CheckedChanged += WithCompressionCheckedChanged; 158 | // 159 | // labelSizeInfo 160 | // 161 | labelSizeInfo.Location = new Point(8, 132); 162 | labelSizeInfo.Name = "labelSizeInfo"; 163 | labelSizeInfo.Size = new Size(399, 17); 164 | labelSizeInfo.TabIndex = 8; 165 | labelSizeInfo.Text = "..."; 166 | labelSizeInfo.TextAlign = ContentAlignment.MiddleCenter; 167 | // 168 | // ExportFontWindow 169 | // 170 | AutoScaleDimensions = new SizeF(6F, 13F); 171 | AutoScaleMode = AutoScaleMode.Font; 172 | BackgroundImageLayout = ImageLayout.None; 173 | CancelButton = Button_Cancel; 174 | ClientSize = new Size(416, 516); 175 | Controls.Add(labelSizeInfo); 176 | Controls.Add(withCompression); 177 | Controls.Add(Label1); 178 | Controls.Add(Label2); 179 | Controls.Add(Label3); 180 | Controls.Add(ComboBoxExportType); 181 | Controls.Add(ComboBoxDataType); 182 | Controls.Add(Button_SaveAs); 183 | Controls.Add(Button_Cancel); 184 | Controls.Add(MemoExport); 185 | Controls.Add(ComboBoxFontNumber); 186 | Controls.Add(ButtonCopyClipboard); 187 | FormBorderStyle = FormBorderStyle.FixedDialog; 188 | MaximizeBox = false; 189 | MinimizeBox = false; 190 | Name = "ExportFontWindow"; 191 | Padding = new Padding(505, 354, 0, 0); 192 | Text = "Export font to ..."; 193 | ResumeLayout(false); 194 | PerformLayout(); 195 | } 196 | 197 | #endregion 198 | 199 | private System.Windows.Forms.Label Label1; 200 | private System.Windows.Forms.Label Label2; 201 | private System.Windows.Forms.Label Label3; 202 | private System.Windows.Forms.ComboBox ComboBoxExportType; 203 | private System.Windows.Forms.ComboBox ComboBoxDataType; 204 | private System.Windows.Forms.Button Button_SaveAs; 205 | private System.Windows.Forms.Button Button_Cancel; 206 | private System.Windows.Forms.RichTextBox MemoExport; 207 | private System.Windows.Forms.ComboBox ComboBoxFontNumber; 208 | private System.Windows.Forms.Button ButtonCopyClipboard; 209 | private System.Windows.Forms.SaveFileDialog saveDialog; 210 | private CheckBox withCompression; 211 | private Label labelSizeInfo; 212 | } 213 | } -------------------------------------------------------------------------------- /ImportViewWindow.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable WFO1000 2 | 3 | namespace FontMaker; 4 | public partial class ImportViewWindow : Form 5 | { 6 | 7 | private const int PREVIEW_WIDTH = 40; 8 | private const int PREVIEW_HEIGHT = 26; 9 | public bool InColorMode { get; set; } 10 | public int WhichColorMode { get; set; } 11 | 12 | private byte[]? FileDataToImportFrom { get; set; } 13 | 14 | // 40x26 view buffer 15 | private byte[,]? ImportedViewBytes { get; set; } 16 | 17 | private bool RememberSelection { get; set; } 18 | private int PreviousLineWidth { get; set; } = 1; 19 | private int PreviousSkipX { get; set; } 20 | private int PreviousSkipY { get; set; } 21 | private int PreviousWidth { get; set; } = 1; 22 | private int PreviousHeight { get; set; } = 1; 23 | 24 | public ImportViewWindow() 25 | { 26 | InitializeComponent(); 27 | RememberSelection = true; 28 | UpdateDataFeedback(); 29 | } 30 | 31 | public void LoadConfiguration(bool rememberSelection, int lineWidth, int skipX, int skipY, int width, int height) 32 | { 33 | if (rememberSelection) 34 | { 35 | if (lineWidth < 1) 36 | lineWidth = 1; 37 | if (skipX < 0) skipX = 0; 38 | if (skipY < 0) skipY = 0; 39 | if (width < 1) width = 1; 40 | if (height < 1) height = 1; 41 | 42 | PreviousLineWidth = lineWidth; 43 | PreviousSkipX = skipX; 44 | PreviousSkipY = skipY; 45 | PreviousWidth = width; 46 | PreviousHeight = height; 47 | } 48 | } 49 | 50 | public (bool, int, int, int, int, int) SaveConfiguration() 51 | { 52 | return (RememberSelection, PreviousLineWidth, PreviousSkipX, PreviousSkipY, PreviousWidth, PreviousHeight); 53 | } 54 | 55 | /// 56 | /// Form is loading, set the saved/initial load configuration 57 | /// 58 | /// 59 | /// 60 | private void ImportViewWindow_Load(object sender, EventArgs e) 61 | { 62 | checkBoxRememberState.Checked = RememberSelection; 63 | if (RememberSelection) 64 | { 65 | numericLineWidth.Value = PreviousLineWidth; 66 | numericSkipX.Value = PreviousSkipX; 67 | numericSkipY.Value = PreviousSkipY; 68 | numericWidth.Value = PreviousWidth; 69 | numericHeight.Value = PreviousHeight; 70 | } 71 | } 72 | 73 | /// 74 | /// Form is closing, save the configuration if needed 75 | /// 76 | /// 77 | /// 78 | private void ImportViewWindow_FormClosing(object sender, FormClosingEventArgs e) 79 | { 80 | RememberSelection = checkBoxRememberState.Checked; 81 | if (checkBoxRememberState.Checked) 82 | { 83 | PreviousLineWidth = (int)numericLineWidth.Value; 84 | PreviousSkipX = (int)numericSkipX.Value; 85 | PreviousSkipY = (int)numericSkipY.Value; 86 | PreviousWidth = (int)numericWidth.Value; 87 | PreviousHeight = (int)numericHeight.Value; 88 | } 89 | } 90 | 91 | private void Button_Cancel_Click(object sender, EventArgs e) 92 | { 93 | Close(); 94 | } 95 | 96 | /// 97 | /// Load data to be imported. 98 | /// Loaded data will end up in 'FileDataToImportFrom' 99 | /// 100 | private void Button_LoadData_Click(object sender, EventArgs e) 101 | { 102 | dialogOpenFile.FileName = string.Empty; 103 | dialogOpenFile.Filter = $@"Any binary data|*.*"; 104 | var ok = dialogOpenFile.ShowDialog(); 105 | 106 | if (ok == DialogResult.OK) 107 | { 108 | FileDataToImportFrom = null; 109 | try 110 | { 111 | FileDataToImportFrom = File.ReadAllBytes(dialogOpenFile.FileName); 112 | if (FileDataToImportFrom == null || FileDataToImportFrom.Length == 0) 113 | { 114 | MessageBox.Show($@"Failed to load '{dialogOpenFile.FileName}'. File is empty."); 115 | FileDataToImportFrom = null; 116 | } 117 | UpdateDataFeedback(); 118 | } 119 | catch (Exception ex) 120 | { 121 | MessageBox.Show($@"Failed to load '{dialogOpenFile.FileName}'. Reason:{ex.Message}"); 122 | FileDataToImportFrom = null; 123 | UpdateDataFeedback(); 124 | } 125 | } 126 | } 127 | 128 | private void UpdateDataFeedback() 129 | { 130 | labelFileSize.Text = FileDataToImportFrom == null ? "(Load some data)" : $"{FileDataToImportFrom.Length} bytes"; 131 | 132 | numericSkipX.Enabled = FileDataToImportFrom != null; 133 | numericSkipY.Enabled = FileDataToImportFrom != null; 134 | numericWidth.Enabled = FileDataToImportFrom != null; 135 | numericHeight.Enabled = FileDataToImportFrom != null; 136 | numericLineWidth.Enabled = FileDataToImportFrom != null; 137 | 138 | UpdateConstraints(null, EventArgs.Empty); 139 | } 140 | 141 | private void UpdateConstraints(object? _, EventArgs __) 142 | { 143 | var widthOk = numericSkipX.Value + numericWidth.Value <= (int)numericLineWidth.Value; 144 | numericSkipX.ForeColor = widthOk ? Color.Black : Color.Red; 145 | numericWidth.ForeColor = widthOk ? Color.Black : Color.Red; 146 | 147 | var heightOk = ((int)numericLineWidth.Value * numericSkipY.Value + (int)numericLineWidth.Value * numericHeight.Value <= (FileDataToImportFrom?.Length ?? 0)); 148 | numericSkipY.ForeColor = heightOk ? Color.Black : Color.Red; 149 | numericHeight.ForeColor = heightOk ? Color.Black : Color.Red; 150 | 151 | labelActionInfo.Text = $"Skip {numericSkipX.Value} byte and then take {numericWidth.Value} bytes."; 152 | 153 | labelSomeMoreInfo.Text = FileDataToImportFrom == null ? "..." : $"# of lines: {(FileDataToImportFrom.Length / (int)numericLineWidth.Value)}"; 154 | 155 | Button_Import.Enabled = widthOk && heightOk; 156 | 157 | if (widthOk && heightOk) 158 | { 159 | GenerateData(); 160 | RenderTestData(); 161 | } 162 | } 163 | 164 | private void GenerateData() 165 | { 166 | if (FileDataToImportFrom == null) 167 | return; 168 | 169 | // Copy the data from FileData into ViewBytes 170 | ImportedViewBytes = new byte[AtariView.Width, AtariView.Height]; 171 | 172 | var lineWidth = (int)numericLineWidth.Value; 173 | var skipX = (int)numericSkipX.Value; 174 | var skipY = (int)numericSkipY.Value; 175 | var width = (int)numericWidth.Value; 176 | var height = (int)numericHeight.Value; 177 | 178 | var srcIndex = skipY * lineWidth; 179 | for (var y = 0; y < height; ++y) 180 | { 181 | for (var x = 0; x < width; ++x) 182 | { 183 | ImportedViewBytes[x, y] = FileDataToImportFrom[srcIndex + skipX + x]; 184 | } 185 | 186 | srcIndex += lineWidth; 187 | } 188 | } 189 | 190 | /// 191 | /// Render the imported data into the 40x26 view 192 | /// 193 | private void RenderTestData() 194 | { 195 | if (ImportedViewBytes == null) 196 | return; 197 | 198 | var colorOffset = InColorMode ? 512 : 0; 199 | var img = Helpers.GetImage(pictureBoxAtariViewSmall); 200 | using (var gr = Graphics.FromImage(img)) 201 | { 202 | var destRect = new Rectangle 203 | { 204 | Width = 1, 205 | Height = 1, 206 | }; 207 | 208 | var srcRect = new Rectangle 209 | { 210 | Width = 1, 211 | Height = 1, 212 | }; 213 | 214 | for (var y = 0; y < PREVIEW_HEIGHT; y++) 215 | { 216 | for (var x = 0; x < PREVIEW_WIDTH; x++) 217 | { 218 | var rx = ImportedViewBytes[x, y] % 32; 219 | var ry = ImportedViewBytes[x, y] / 32; 220 | 221 | destRect.X = x * 8; 222 | destRect.Y = y * 8; 223 | 224 | srcRect.X = rx * 16; 225 | srcRect.Y = ry * 16 + Constants.FontYOffset[AtariView.UseFontOnLine[y] - 1] + colorOffset; 226 | 227 | for (var h = 0; h < 8; ++h) 228 | { 229 | for (var w = 0; w < 8; ++w) 230 | { 231 | gr.DrawImage(AtariFontRenderer.BitmapFontBanks, destRect, srcRect, GraphicsUnit.Pixel); 232 | destRect.X++; 233 | srcRect.X += 2; 234 | } 235 | 236 | destRect.X -= 8; 237 | srcRect.X -= 16; 238 | destRect.Y++; 239 | srcRect.Y += 2; 240 | } 241 | 242 | } 243 | } 244 | } 245 | pictureBoxAtariViewSmall.Refresh(); 246 | } 247 | 248 | private void Button_Import_Click(object sender, EventArgs e) 249 | { 250 | if (ImportedViewBytes != null) 251 | { 252 | for (var y = 0; y < AtariView.Height; ++y) 253 | { 254 | for (var x = 0; x < AtariView.Width; ++x) 255 | { 256 | AtariView.ViewBytes[x, y] = ImportedViewBytes[x, y]; 257 | } 258 | } 259 | } 260 | 261 | Close(); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /PageEditor.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | 124 | 17, 17 125 | 126 | 127 | 128 | 129 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 130 | vAAADrwBlbxySQAAAZZJREFUOE+Vj8tLQkEYxYdIihZBUS3zH2gf6DaiopYRiSJR0QMsWkQRLXJVBFkU 131 | PQgxb8iNDBJ0IWmYJhk93bTRhfZcBCm1yUDzxMyVwqlrdeDH/ThzzjdzCZFRJgtF51JAbJzdd6cy2TL+ 132 | vKAAFOlWgiLRbIDSMOP1J1Ppaj73o+jN/ZYTMy1qnPcMOjebDrxv77+8hN28fiwSrcCKo0fPDLZEK6Bl 133 | IeBJptJVfI+J3ty3eWEmelteOW+J3oamxcPvL6FlneVMJF1baHfcYMifgMGfwJA/yb7SnEC74xY007Ic 134 | 8jy9pmtY+SWVKddaL0XSu4NWeww9nkd07+WgM0ebPQaarZ87PH96TVcTc+humAw4wNPhukeHM4fr4ets 135 | cPdzNgWvJ0kWUOT9D5XBhWZ7/IvtOKjHxwAU856kETfUQgQqIQq1NQqVEAH1+Ji8Rr2oW72SWJOgHh+T 136 | 15gPSlMYyvkwaimmMMi47x8LJgKonD5lVOSgHh+T12QQJcYQSqdCKDEesZl6fExeYz6pwPNn8cUCCz4A 137 | IQdbtv1Vld0AAAAASUVORK5CYII= 138 | 139 | 140 | 141 | 142 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 143 | vAAADrwBlbxySQAAAadJREFUOE+VkE8ow2EYx99kFzWlTG5y5Gy5Okj+XSmaxkFGaRxEaw4ksfxZZAdt 144 | /rV+DivaHCazzX7DaP4cHCgKWVG0uJja7Kvntxl7s7JvfXqfvu/3ed7n92PsL+n9+JN/a9ADlf0eAwev 145 | ElRnN0Dvh9YXhlZM0CeGsxyg80Hjfka3+1k6CfL4WGYNeqB2PkK9/YT2JGzIk8WAARea7aEEjhCaHCGQ 146 | x8cyq9+JRtstGmx3SW5BHh+TFAdkvMd6t1BlvUaV9SZ5XoM8PgYgl5kPH7SsexM8SsslKiSuoLRc/dz1 147 | bKTqGf+dnr1FYvmqlTOBddpQOhVEuekiRdmv+hvKULZyWjx5eY8qpFViccjaloIC61hH8XgAJcZzlMxy 148 | GM+lO8rULxzuvLxHi9K+h4Z0rZ2amdqKgpF9FBlOoDAEE0wGUTC6D7qrnRNdH5/xvLTmbwHIaVsMCEy1 149 | irxhEfKxAORjR1JNXr3RtxOORAv5vjTRJpqlYzNrXUauzguZzguq62a8mV/mJW1i8gvUSFRPuPbCkeQP 150 | +69ok5Z5n1Bj2HVGYplf/gJNOVXeqnqIXQAAAABJRU5ErkJggg== 151 | 152 | 153 | -------------------------------------------------------------------------------- /General.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker 2 | { 3 | 4 | internal class General 5 | { 6 | } 7 | 8 | public partial class FontMakerForm 9 | { 10 | // Section A - General: New, Load, Save, Save As, Clear, About and Quit buttons 11 | // Methods to implement the button actions 12 | 13 | #region Button and mouse event actions 14 | 15 | public void SimulateSafeLeftMouseButtonClick() 16 | { 17 | var old_megaCopyStatus = megaCopyStatus; 18 | megaCopyStatus = MegaCopyStatusFlags.None; 19 | var mouseEvent = new MouseEventArgs(MouseButtons.Left, 0, (SelectedCharacterIndex % 32) * 16, (SelectedCharacterIndex / 32) * 16, 0); 20 | ActionFontSelectorMouseDown(mouseEvent); 21 | ActionFontSelectorMouseUp(mouseEvent); 22 | megaCopyStatus = old_megaCopyStatus; 23 | } 24 | /// 25 | /// New the font and view data 26 | /// 27 | public void ActionNewFontAndView() 28 | { 29 | var re = MessageBox.Show(@"Are you sure you want to reset to the default character set and view? Everything will be lost!", Constants.Title, MessageBoxButtons.YesNo, MessageBoxIcon.Question); 30 | 31 | if (re == DialogResult.Yes) 32 | { 33 | CurrentDataFolder = AppContext.BaseDirectory; 34 | LoadViewFile(null, true); 35 | checkBoxFontBank.Checked = false; 36 | UpdateFormCaption(); 37 | RedrawFonts(); 38 | RedrawLineTypes(); 39 | RedrawView(); 40 | RedrawPal(); 41 | RedrawViewChar(); 42 | RedrawChar(); 43 | // SaveFont1_Click(null, EventArgs.Empty); 44 | } 45 | } 46 | 47 | /// 48 | /// Load 1 or Load 3 button was clicked 49 | /// Load a font into bank 1 or 3, or in dual mode into banks 1+2 or 3+4 50 | /// 51 | public void ActionLoadFont1() 52 | { 53 | dialogOpenFile.FileName = string.Empty; 54 | dialogOpenFile.InitialDirectory = CurrentDataFolder; 55 | dialogOpenFile.Filter = $@"Atari font {(checkBoxFontBank.Checked ? 3 : 1)} or Dual font (*.fnt,*.fn2)|*.fnt;*.fn2"; 56 | var ok = dialogOpenFile.ShowDialog(); 57 | 58 | if (ok == DialogResult.OK) 59 | { 60 | var fontBankOffset = checkBoxFontBank.Checked ? 2 : 0; 61 | var dual = Path.GetExtension(dialogOpenFile.FileName).ToLowerInvariant() == ".fn2"; 62 | 63 | AtariFont.LoadFont(dialogOpenFile.FileName, fontBankOffset, dual); 64 | 65 | CurrentDataFolder = Path.GetDirectoryName(dialogOpenFile.FileName) + Path.DirectorySeparatorChar; 66 | 67 | if (dual) 68 | { 69 | var tempString = dialogOpenFile.FileName.Substring(0, dialogOpenFile.FileName.Length - 4); 70 | if (checkBoxFontBank.Checked == false) 71 | { 72 | Font1Filename = tempString + "-fn2-1.fnt"; 73 | Font2Filename = tempString + "-fn2-2.fnt"; 74 | 75 | AtariFont.SaveFont(Font1Filename, 0); 76 | AtariFont.SaveFont(Font2Filename, 1); 77 | } 78 | else 79 | { 80 | Font3Filename = tempString + "-fn2-3.fnt"; 81 | Font4Filename = tempString + "-fn2-4.fnt"; 82 | 83 | AtariFont.SaveFont(Font3Filename, 2); 84 | AtariFont.SaveFont(Font4Filename, 3); 85 | } 86 | 87 | } 88 | else 89 | { 90 | if (checkBoxFontBank.Checked == false) 91 | Font1Filename = dialogOpenFile.FileName; 92 | else 93 | Font3Filename = dialogOpenFile.FileName; 94 | } 95 | 96 | UpdateFormCaption(); 97 | RedrawFonts(); 98 | 99 | SimulateSafeLeftMouseButtonClick(); 100 | 101 | RedrawView(); 102 | AtariFontUndoBuffer.Add2UndoFullDifferenceScan(); // Full font scan 103 | UpdateUndoButtons(false); 104 | } 105 | 106 | CheckDuplicate(); 107 | } 108 | 109 | /// 110 | /// Load 2 or Load 3 button was clicked 111 | /// Load a font into bank 2 or 4 112 | /// 113 | public void ActionLoadFont2() 114 | { 115 | dialogOpenFile.FileName = string.Empty; 116 | dialogOpenFile.InitialDirectory = CurrentDataFolder; 117 | dialogOpenFile.Filter = $@"Atari font {(checkBoxFontBank.Checked ? 4 : 2)} (*.fnt)|*.fnt"; 118 | var ok = dialogOpenFile.ShowDialog(); 119 | 120 | if (ok == DialogResult.OK) 121 | { 122 | var fontBankOffset = checkBoxFontBank.Checked ? 2 : 0; 123 | 124 | AtariFont.LoadFont(dialogOpenFile.FileName, fontBankOffset + 1, false); 125 | 126 | CurrentDataFolder = Path.GetDirectoryName(dialogOpenFile.FileName) + Path.DirectorySeparatorChar; 127 | 128 | if (checkBoxFontBank.Checked == false) 129 | Font2Filename = dialogOpenFile.FileName; 130 | else 131 | Font4Filename = dialogOpenFile.FileName; 132 | 133 | UpdateFormCaption(); 134 | RedrawFonts(); 135 | SimulateSafeLeftMouseButtonClick(); 136 | RedrawView(); 137 | AtariFontUndoBuffer.Add2UndoFullDifferenceScan(); //full font scan 138 | UpdateUndoButtons(false); 139 | } 140 | 141 | CheckDuplicate(); 142 | } 143 | 144 | /// 145 | /// Save the font in bank 1/3 away 146 | /// 147 | public void ActionSaveFont1() 148 | { 149 | var filename = checkBoxFontBank.Checked ? Font3Filename : Font1Filename; 150 | AtariFont.SaveFont(filename, checkBoxFontBank.Checked == false ? 0 : 2); 151 | } 152 | 153 | /// 154 | /// Save the font in bank 2/4 away 155 | /// 156 | public void ActionSaveFont2() 157 | { 158 | var filename = checkBoxFontBank.Checked ? Font4Filename : Font2Filename; 159 | AtariFont.SaveFont(filename, checkBoxFontBank.Checked == false ? 1 : 3); 160 | } 161 | 162 | public void ActionSaveFont1As() 163 | { 164 | dialogSaveFile.FileName = string.Empty; 165 | dialogSaveFile.InitialDirectory = CurrentDataFolder; 166 | dialogSaveFile.Filter = $@"Atari font {(checkBoxFontBank.Checked ? 3 : 1)} (*.fnt)|*.fnt"; 167 | dialogSaveFile.DefaultExt = "fnt"; 168 | var ok = dialogSaveFile.ShowDialog(); 169 | 170 | if (ok == DialogResult.OK) 171 | { 172 | AtariFont.SaveFont(dialogSaveFile.FileName, checkBoxFontBank.Checked == false ? 0 : 2); 173 | CurrentDataFolder = Path.GetDirectoryName(dialogSaveFile.FileName) + Path.DirectorySeparatorChar; 174 | 175 | if (checkBoxFontBank.Checked == false) 176 | Font1Filename = dialogSaveFile.FileName; 177 | else 178 | Font3Filename = dialogSaveFile.FileName; 179 | } 180 | 181 | UpdateFormCaption(); 182 | } 183 | 184 | public void ActionSaveFont2As() 185 | { 186 | dialogSaveFile.FileName = string.Empty; 187 | dialogSaveFile.InitialDirectory = CurrentDataFolder; 188 | dialogSaveFile.Filter = $@"Atari font {(checkBoxFontBank.Checked ? 4 : 2)} (*.fnt)|*.fnt"; 189 | dialogSaveFile.DefaultExt = "fnt"; 190 | var ok = dialogSaveFile.ShowDialog(); 191 | 192 | if (ok == DialogResult.OK) 193 | { 194 | AtariFont.SaveFont(dialogSaveFile.FileName, checkBoxFontBank.Checked == false ? 1 : 3); 195 | 196 | CurrentDataFolder = Path.GetDirectoryName(dialogSaveFile.FileName) + Path.DirectorySeparatorChar; 197 | 198 | if (checkBoxFontBank.Checked == false) 199 | Font2Filename = dialogSaveFile.FileName; 200 | else 201 | Font4Filename = dialogSaveFile.FileName; 202 | } 203 | 204 | UpdateFormCaption(); 205 | } 206 | 207 | public void ActionClearFont(int fontOffset) 208 | { 209 | var fontBankOffset = checkBoxFontBank.Checked ? 2 : 0; 210 | 211 | var re = MessageBox.Show($@"Are you sure to clear font {fontOffset + 1 + fontBankOffset}?", Constants.Title, MessageBoxButtons.YesNo, MessageBoxIcon.Question); 212 | 213 | if (re == DialogResult.Yes) 214 | { 215 | ClearFont(fontOffset + 0 + fontBankOffset); 216 | } 217 | } 218 | 219 | public void ActionShowAbout() 220 | { 221 | pictureBoxAbout.Left = pictureBoxAtariView.Left; 222 | pictureBoxAbout.Visible = !pictureBoxAbout.Visible; 223 | 224 | } 225 | 226 | public void ActionAboutUrl(bool onLeftSide) 227 | { 228 | Helpers.OpenUrl(onLeftSide ? "http://matosimi.atari.org" : "https://retro.cerebus.co.za"); 229 | pictureBoxAbout.Visible = false; 230 | } 231 | 232 | public void ActionExitApplication() 233 | { 234 | var re = MessageBox.Show(@"Are you sure you want to quit?", Constants.Title, MessageBoxButtons.YesNo, MessageBoxIcon.Question); 235 | 236 | if (re == DialogResult.Yes) 237 | { 238 | SaveConfiguration(); 239 | Exit(); 240 | } 241 | } 242 | 243 | #endregion 244 | 245 | 246 | private void ClearFont(int fontNr) 247 | { 248 | AtariFont.ClearFont(fontNr); 249 | 250 | RedrawFonts(); 251 | SimulateSafeLeftMouseButtonClick(); 252 | RedrawView(); 253 | CheckDuplicate(); 254 | } 255 | 256 | public void Exit() 257 | { 258 | Visible = false; 259 | timerAutoCloseAboutBox.Enabled = false; 260 | pictureBoxAbout.Visible = false; 261 | Environment.Exit(Environment.ExitCode); 262 | } 263 | 264 | public void SwitchToTileDrawing() 265 | { 266 | // The font and characters have been copied to the clipboard 267 | // Enabled specific clipboard modification/action buttons 268 | if (buttonMegaCopy.Checked == false) 269 | { 270 | // Switch into MegaCopy mode 271 | buttonMegaCopy.Checked = true; 272 | MegaCopy_Click(0, EventArgs.Empty); 273 | } 274 | 275 | 276 | ConfigureClipboardActionButtons(); 277 | 278 | UpdateClipboardInformation(); 279 | PastingToView = true; 280 | RevalidateClipboard(); 281 | ExecutePasteFromClipboard(); 282 | 283 | pictureBoxAtariView.Focus(); 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /AtariViewConfigWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace FontMaker; 2 | 3 | partial class AtariViewConfigWindow 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | numericHeight = new NumericUpDown(); 32 | numericWidth = new NumericUpDown(); 33 | label5 = new Label(); 34 | label6 = new Label(); 35 | label3 = new Label(); 36 | label1 = new Label(); 37 | label2 = new Label(); 38 | labelCurrentInfo = new Label(); 39 | buttonResize = new Button(); 40 | buttonCancel = new Button(); 41 | label4 = new Label(); 42 | labelNewInfo = new Label(); 43 | labelNewDifference = new Label(); 44 | radio1 = new RadioButton(); 45 | radio2 = new RadioButton(); 46 | radio4 = new RadioButton(); 47 | radio3 = new RadioButton(); 48 | label7 = new Label(); 49 | label8 = new Label(); 50 | label9 = new Label(); 51 | ((System.ComponentModel.ISupportInitialize)numericHeight).BeginInit(); 52 | ((System.ComponentModel.ISupportInitialize)numericWidth).BeginInit(); 53 | SuspendLayout(); 54 | // 55 | // numericHeight 56 | // 57 | numericHeight.Location = new Point(213, 63); 58 | numericHeight.Maximum = new decimal(new int[] { 1024, 0, 0, 0 }); 59 | numericHeight.Name = "numericHeight"; 60 | numericHeight.Size = new Size(62, 22); 61 | numericHeight.TabIndex = 2; 62 | numericHeight.TextAlign = HorizontalAlignment.Center; 63 | numericHeight.ThousandsSeparator = true; 64 | numericHeight.Value = new decimal(new int[] { 26, 0, 0, 0 }); 65 | numericHeight.ValueChanged += numericHeight_ValueChanged; 66 | numericHeight.KeyPress += numericWidth_KeyPress; 67 | // 68 | // numericWidth 69 | // 70 | numericWidth.Location = new Point(57, 63); 71 | numericWidth.Maximum = new decimal(new int[] { 1024, 0, 0, 0 }); 72 | numericWidth.Name = "numericWidth"; 73 | numericWidth.Size = new Size(62, 22); 74 | numericWidth.TabIndex = 1; 75 | numericWidth.TextAlign = HorizontalAlignment.Center; 76 | numericWidth.ThousandsSeparator = true; 77 | numericWidth.Value = new decimal(new int[] { 40, 0, 0, 0 }); 78 | numericWidth.ValueChanged += numericWidth_ValueChanged; 79 | numericWidth.KeyPress += numericWidth_KeyPress; 80 | // 81 | // label5 82 | // 83 | label5.AutoSize = true; 84 | label5.Location = new Point(165, 65); 85 | label5.Name = "label5"; 86 | label5.Size = new Size(45, 13); 87 | label5.TabIndex = 31; 88 | label5.Text = "Height:"; 89 | // 90 | // label6 91 | // 92 | label6.AutoSize = true; 93 | label6.Location = new Point(8, 65); 94 | label6.Name = "label6"; 95 | label6.Size = new Size(42, 13); 96 | label6.TabIndex = 30; 97 | label6.Text = "Width:"; 98 | // 99 | // label3 100 | // 101 | label3.AutoSize = true; 102 | label3.Font = new Font("Segoe UI", 8.25F, FontStyle.Bold); 103 | label3.Location = new Point(2, 2); 104 | label3.Name = "label3"; 105 | label3.Size = new Size(92, 13); 106 | label3.TabIndex = 34; 107 | label3.Text = "Page Dimension"; 108 | // 109 | // label1 110 | // 111 | label1.AutoSize = true; 112 | label1.Location = new Point(8, 21); 113 | label1.Name = "label1"; 114 | label1.Size = new Size(72, 13); 115 | label1.TabIndex = 35; 116 | label1.Text = "Current Size:"; 117 | // 118 | // label2 119 | // 120 | label2.AutoSize = true; 121 | label2.Location = new Point(8, 47); 122 | label2.Name = "label2"; 123 | label2.Size = new Size(56, 13); 124 | label2.TabIndex = 36; 125 | label2.Text = "New Size:"; 126 | // 127 | // labelCurrentInfo 128 | // 129 | labelCurrentInfo.AutoSize = true; 130 | labelCurrentInfo.Location = new Point(83, 21); 131 | labelCurrentInfo.Name = "labelCurrentInfo"; 132 | labelCurrentInfo.Size = new Size(11, 13); 133 | labelCurrentInfo.TabIndex = 42; 134 | labelCurrentInfo.Text = "-"; 135 | // 136 | // buttonResize 137 | // 138 | buttonResize.Location = new Point(10, 192); 139 | buttonResize.Name = "buttonResize"; 140 | buttonResize.Size = new Size(100, 40); 141 | buttonResize.TabIndex = 3; 142 | buttonResize.Text = "Resize"; 143 | buttonResize.UseVisualStyleBackColor = true; 144 | buttonResize.Click += buttonResize_Click; 145 | // 146 | // buttonCancel 147 | // 148 | buttonCancel.Location = new Point(176, 195); 149 | buttonCancel.Name = "buttonCancel"; 150 | buttonCancel.Size = new Size(100, 40); 151 | buttonCancel.TabIndex = 4; 152 | buttonCancel.Text = "Cancel"; 153 | buttonCancel.UseVisualStyleBackColor = true; 154 | // 155 | // label4 156 | // 157 | label4.BorderStyle = BorderStyle.FixedSingle; 158 | label4.Location = new Point(4, 42); 159 | label4.Name = "label4"; 160 | label4.Size = new Size(280, 2); 161 | label4.TabIndex = 45; 162 | // 163 | // labelNewInfo 164 | // 165 | labelNewInfo.Font = new Font("Segoe UI", 8.25F, FontStyle.Bold); 166 | labelNewInfo.Location = new Point(12, 90); 167 | labelNewInfo.Name = "labelNewInfo"; 168 | labelNewInfo.Size = new Size(263, 13); 169 | labelNewInfo.TabIndex = 46; 170 | labelNewInfo.Text = "-"; 171 | labelNewInfo.TextAlign = ContentAlignment.TopCenter; 172 | labelNewInfo.UseMnemonic = false; 173 | // 174 | // labelNewDifference 175 | // 176 | labelNewDifference.Font = new Font("Segoe UI", 8.25F, FontStyle.Bold); 177 | labelNewDifference.Location = new Point(12, 107); 178 | labelNewDifference.Name = "labelNewDifference"; 179 | labelNewDifference.Size = new Size(263, 13); 180 | labelNewDifference.TabIndex = 47; 181 | labelNewDifference.Text = "-"; 182 | labelNewDifference.TextAlign = ContentAlignment.TopCenter; 183 | labelNewDifference.UseMnemonic = false; 184 | // 185 | // radio1 186 | // 187 | radio1.AutoSize = true; 188 | radio1.Checked = true; 189 | radio1.Location = new Point(72, 154); 190 | radio1.Name = "radio1"; 191 | radio1.Size = new Size(31, 17); 192 | radio1.TabIndex = 49; 193 | radio1.TabStop = true; 194 | radio1.Text = "1"; 195 | radio1.UseVisualStyleBackColor = true; 196 | // 197 | // radio2 198 | // 199 | radio2.AutoSize = true; 200 | radio2.Location = new Point(109, 154); 201 | radio2.Name = "radio2"; 202 | radio2.Size = new Size(31, 17); 203 | radio2.TabIndex = 50; 204 | radio2.Text = "2"; 205 | radio2.UseVisualStyleBackColor = true; 206 | // 207 | // radio4 208 | // 209 | radio4.AutoSize = true; 210 | radio4.Location = new Point(183, 154); 211 | radio4.Name = "radio4"; 212 | radio4.Size = new Size(31, 17); 213 | radio4.TabIndex = 52; 214 | radio4.Text = "4"; 215 | radio4.UseVisualStyleBackColor = true; 216 | // 217 | // radio3 218 | // 219 | radio3.AutoSize = true; 220 | radio3.Location = new Point(146, 154); 221 | radio3.Name = "radio3"; 222 | radio3.Size = new Size(31, 17); 223 | radio3.TabIndex = 51; 224 | radio3.Text = "3"; 225 | radio3.UseVisualStyleBackColor = true; 226 | // 227 | // label7 228 | // 229 | label7.AutoSize = true; 230 | label7.Location = new Point(39, 133); 231 | label7.Name = "label7"; 232 | label7.Size = new Size(208, 13); 233 | label7.TabIndex = 53; 234 | label7.Text = "What font nr to use for resized height?"; 235 | // 236 | // label8 237 | // 238 | label8.BorderStyle = BorderStyle.FixedSingle; 239 | label8.Location = new Point(3, 127); 240 | label8.Name = "label8"; 241 | label8.Size = new Size(280, 2); 242 | label8.TabIndex = 54; 243 | // 244 | // label9 245 | // 246 | label9.BorderStyle = BorderStyle.FixedSingle; 247 | label9.Location = new Point(3, 177); 248 | label9.Name = "label9"; 249 | label9.Size = new Size(280, 2); 250 | label9.TabIndex = 55; 251 | // 252 | // AtariViewConfigWindow 253 | // 254 | AutoScaleDimensions = new SizeF(6F, 13F); 255 | AutoScaleMode = AutoScaleMode.Font; 256 | CancelButton = buttonCancel; 257 | ClientSize = new Size(287, 241); 258 | Controls.Add(label9); 259 | Controls.Add(label8); 260 | Controls.Add(label7); 261 | Controls.Add(radio4); 262 | Controls.Add(radio3); 263 | Controls.Add(radio2); 264 | Controls.Add(radio1); 265 | Controls.Add(labelNewDifference); 266 | Controls.Add(labelNewInfo); 267 | Controls.Add(label4); 268 | Controls.Add(buttonCancel); 269 | Controls.Add(buttonResize); 270 | Controls.Add(labelCurrentInfo); 271 | Controls.Add(label2); 272 | Controls.Add(label1); 273 | Controls.Add(label3); 274 | Controls.Add(numericHeight); 275 | Controls.Add(numericWidth); 276 | Controls.Add(label5); 277 | Controls.Add(label6); 278 | FormBorderStyle = FormBorderStyle.FixedDialog; 279 | KeyPreview = true; 280 | MaximizeBox = false; 281 | MinimizeBox = false; 282 | Name = "AtariViewConfigWindow"; 283 | StartPosition = FormStartPosition.CenterParent; 284 | Text = "Update Page Size"; 285 | ((System.ComponentModel.ISupportInitialize)numericHeight).EndInit(); 286 | ((System.ComponentModel.ISupportInitialize)numericWidth).EndInit(); 287 | ResumeLayout(false); 288 | PerformLayout(); 289 | } 290 | 291 | #endregion 292 | 293 | private NumericUpDown numericHeight; 294 | private NumericUpDown numericWidth; 295 | private Label label5; 296 | private Label label6; 297 | private Label label3; 298 | private Label label1; 299 | private Label label2; 300 | private Label labelCurrentInfo; 301 | private Button buttonResize; 302 | private Button buttonCancel; 303 | private Label label4; 304 | private Label labelNewInfo; 305 | private Label labelNewDifference; 306 | private RadioButton radio1; 307 | private RadioButton radio2; 308 | private RadioButton radio4; 309 | private RadioButton radio3; 310 | private Label label7; 311 | private Label label8; 312 | private Label label9; 313 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 2 | root = true 3 | 4 | # C# files 5 | [*.cs] 6 | 7 | #### Core EditorConfig Options #### 8 | 9 | # Indentation and spacing 10 | indent_size = 4 11 | indent_style = tab 12 | tab_width = 4 13 | 14 | # New line preferences 15 | end_of_line = crlf 16 | insert_final_newline = false 17 | 18 | #### .NET Code Actions #### 19 | 20 | # Type members 21 | dotnet_hide_advanced_members = false 22 | dotnet_member_insertion_location = with_other_members_of_the_same_kind 23 | dotnet_property_generation_behavior = prefer_throwing_properties 24 | 25 | # Symbol search 26 | dotnet_search_reference_assemblies = true 27 | 28 | #### .NET Coding Conventions #### 29 | 30 | # Organize usings 31 | dotnet_separate_import_directive_groups = false 32 | dotnet_sort_system_directives_first = false 33 | file_header_template = unset 34 | 35 | # this. and Me. preferences 36 | dotnet_style_qualification_for_event = false 37 | dotnet_style_qualification_for_field = false 38 | dotnet_style_qualification_for_method = false 39 | dotnet_style_qualification_for_property = false 40 | 41 | # Language keywords vs BCL types preferences 42 | dotnet_style_predefined_type_for_locals_parameters_members = true 43 | dotnet_style_predefined_type_for_member_access = true 44 | 45 | # Parentheses preferences 46 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity 47 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity 48 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary 49 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity 50 | 51 | # Modifier preferences 52 | dotnet_style_require_accessibility_modifiers = for_non_interface_members 53 | 54 | # Expression-level preferences 55 | dotnet_prefer_system_hash_code = true 56 | dotnet_style_coalesce_expression = true 57 | dotnet_style_collection_initializer = true 58 | dotnet_style_explicit_tuple_names = true 59 | dotnet_style_namespace_match_folder = true 60 | dotnet_style_null_propagation = true 61 | dotnet_style_object_initializer = true 62 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 63 | dotnet_style_prefer_auto_properties = true 64 | dotnet_style_prefer_collection_expression = when_types_loosely_match 65 | dotnet_style_prefer_compound_assignment = true 66 | dotnet_style_prefer_conditional_expression_over_assignment = true 67 | dotnet_style_prefer_conditional_expression_over_return = true 68 | dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed 69 | dotnet_style_prefer_inferred_anonymous_type_member_names = true 70 | dotnet_style_prefer_inferred_tuple_names = true 71 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true 72 | dotnet_style_prefer_simplified_boolean_expressions = true 73 | dotnet_style_prefer_simplified_interpolation = true 74 | 75 | # Field preferences 76 | dotnet_style_readonly_field = true 77 | 78 | # Parameter preferences 79 | dotnet_code_quality_unused_parameters = all 80 | 81 | # Suppression preferences 82 | dotnet_remove_unnecessary_suppression_exclusions = none 83 | 84 | # New line preferences 85 | dotnet_style_allow_multiple_blank_lines_experimental = true 86 | dotnet_style_allow_statement_immediately_after_block_experimental = true 87 | 88 | #### C# Coding Conventions #### 89 | 90 | # var preferences 91 | csharp_style_var_elsewhere = true 92 | csharp_style_var_for_built_in_types = true 93 | csharp_style_var_when_type_is_apparent = true 94 | 95 | # Expression-bodied members 96 | csharp_style_expression_bodied_accessors = true 97 | csharp_style_expression_bodied_constructors = false 98 | csharp_style_expression_bodied_indexers = true 99 | csharp_style_expression_bodied_lambdas = true 100 | csharp_style_expression_bodied_local_functions = false 101 | csharp_style_expression_bodied_methods = false 102 | csharp_style_expression_bodied_operators = false 103 | csharp_style_expression_bodied_properties = true 104 | 105 | # Pattern matching preferences 106 | csharp_style_pattern_matching_over_as_with_null_check = true 107 | csharp_style_pattern_matching_over_is_with_cast_check = true 108 | csharp_style_prefer_extended_property_pattern = true 109 | csharp_style_prefer_not_pattern = true 110 | csharp_style_prefer_pattern_matching = true 111 | csharp_style_prefer_switch_expression = true 112 | 113 | # Null-checking preferences 114 | csharp_style_conditional_delegate_call = true 115 | 116 | # Modifier preferences 117 | csharp_prefer_static_anonymous_function = true 118 | csharp_prefer_static_local_function = true 119 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async 120 | csharp_style_prefer_readonly_struct = true 121 | csharp_style_prefer_readonly_struct_member = true 122 | 123 | # Code-block preferences 124 | csharp_prefer_braces = true 125 | csharp_prefer_simple_using_statement = true 126 | csharp_prefer_system_threading_lock = true 127 | csharp_style_namespace_declarations = file_scoped 128 | csharp_style_prefer_method_group_conversion = true 129 | csharp_style_prefer_primary_constructors = true 130 | csharp_style_prefer_top_level_statements = true 131 | 132 | # Expression-level preferences 133 | csharp_prefer_simple_default_expression = true 134 | csharp_style_deconstructed_variable_declaration = true 135 | csharp_style_implicit_object_creation_when_type_is_apparent = true 136 | csharp_style_inlined_variable_declaration = true 137 | csharp_style_prefer_index_operator = true 138 | csharp_style_prefer_local_over_anonymous_function = true 139 | csharp_style_prefer_null_check_over_type_check = true 140 | csharp_style_prefer_range_operator = true 141 | csharp_style_prefer_tuple_swap = true 142 | csharp_style_prefer_utf8_string_literals = true 143 | csharp_style_throw_expression = true 144 | csharp_style_unused_value_assignment_preference = discard_variable 145 | csharp_style_unused_value_expression_statement_preference = discard_variable 146 | 147 | # 'using' directive preferences 148 | csharp_using_directive_placement = outside_namespace 149 | 150 | # New line preferences 151 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true 152 | csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true 153 | csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true 154 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true 155 | csharp_style_allow_embedded_statements_on_same_line_experimental = false 156 | 157 | #### C# Formatting Rules #### 158 | 159 | # New line preferences 160 | csharp_new_line_before_catch = true 161 | csharp_new_line_before_else = true 162 | csharp_new_line_before_finally = true 163 | csharp_new_line_before_members_in_anonymous_types = true 164 | csharp_new_line_before_members_in_object_initializers = true 165 | csharp_new_line_before_open_brace = all 166 | csharp_new_line_between_query_expression_clauses = true 167 | 168 | # Indentation preferences 169 | csharp_indent_block_contents = true 170 | csharp_indent_braces = false 171 | csharp_indent_case_contents = true 172 | csharp_indent_case_contents_when_block = false 173 | csharp_indent_labels = one_less_than_current 174 | csharp_indent_switch_labels = true 175 | 176 | # Space preferences 177 | csharp_space_after_cast = false 178 | csharp_space_after_colon_in_inheritance_clause = true 179 | csharp_space_after_comma = true 180 | csharp_space_after_dot = false 181 | csharp_space_after_keywords_in_control_flow_statements = true 182 | csharp_space_after_semicolon_in_for_statement = true 183 | csharp_space_around_binary_operators = before_and_after 184 | csharp_space_around_declaration_statements = false 185 | csharp_space_before_colon_in_inheritance_clause = true 186 | csharp_space_before_comma = false 187 | csharp_space_before_dot = false 188 | csharp_space_before_open_square_brackets = false 189 | csharp_space_before_semicolon_in_for_statement = false 190 | csharp_space_between_empty_square_brackets = false 191 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 192 | csharp_space_between_method_call_name_and_opening_parenthesis = false 193 | csharp_space_between_method_call_parameter_list_parentheses = false 194 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 195 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 196 | csharp_space_between_method_declaration_parameter_list_parentheses = false 197 | csharp_space_between_parentheses = false 198 | csharp_space_between_square_brackets = false 199 | 200 | # Wrapping preferences 201 | csharp_preserve_single_line_blocks = true 202 | csharp_preserve_single_line_statements = true 203 | 204 | #### Naming styles #### 205 | 206 | # Naming rules 207 | 208 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 209 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 210 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 211 | 212 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 213 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 214 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 215 | 216 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 217 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 218 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 219 | 220 | # Symbol specifications 221 | 222 | dotnet_naming_symbols.interface.applicable_kinds = interface 223 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 224 | dotnet_naming_symbols.interface.required_modifiers = 225 | 226 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 227 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 228 | dotnet_naming_symbols.types.required_modifiers = 229 | 230 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 231 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 232 | dotnet_naming_symbols.non_field_members.required_modifiers = 233 | 234 | # Naming styles 235 | 236 | dotnet_naming_style.pascal_case.required_prefix = 237 | dotnet_naming_style.pascal_case.required_suffix = 238 | dotnet_naming_style.pascal_case.word_separator = 239 | dotnet_naming_style.pascal_case.capitalization = pascal_case 240 | 241 | dotnet_naming_style.begins_with_i.required_prefix = I 242 | dotnet_naming_style.begins_with_i.required_suffix = 243 | dotnet_naming_style.begins_with_i.word_separator = 244 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 245 | -------------------------------------------------------------------------------- /TileSet.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable WFO1000 2 | using System.Text; 3 | 4 | namespace FontMaker; 5 | 6 | #region Load/Save tile / tile-set data 7 | public class AtrTileSetJson 8 | { 9 | public const string MY_VERSION = "1"; 10 | 11 | public string? Version { get; set; } 12 | 13 | /// 14 | /// For each tile we have: 15 | /// - index where to store it 16 | /// - characters (5x5) 17 | /// - selected font 18 | /// 19 | public List? Tiles { get; set; } 20 | } 21 | 22 | public class AtrTileJson 23 | { 24 | public const string MY_VERSION = "1"; 25 | public string? Version { get; set; } 26 | public SavedTileData Tile { get; set; } 27 | } 28 | 29 | public class SavedTileData 30 | { 31 | public int Nr { get; set; } 32 | public string View { get; set; } 33 | public string Font { get; set; } 34 | /// 35 | /// Indicate which spot is a null 36 | /// 1 = null 37 | /// 0 = data 38 | /// 39 | public string Nulls { get; set; } 40 | 41 | public int Width { get; set; } = 5; 42 | public int Height { get; set; } = 5; 43 | } 44 | #endregion 45 | 46 | // A tile set is a collection of tiles that can be used to paint onto the view window. 47 | 48 | public class TileData 49 | { 50 | public const int OLD_TILE_WIDTH = 5; 51 | public const int OLD_TILE_HEIGHT = 5; 52 | 53 | public const int TILE_WIDTH = 8; 54 | public const int TILE_HEIGHT = 8; 55 | 56 | public bool IsValid() 57 | { 58 | for (var y = 0; y < TILE_HEIGHT; y++) 59 | { 60 | for (var x = 0; x < TILE_WIDTH; ++x) 61 | { 62 | if (View[x, y] != null) 63 | return true; 64 | } 65 | } 66 | 67 | return false; 68 | } 69 | 70 | /// 71 | /// The 5x5 byte? array that represents the characters (or null) of the tile 72 | /// 73 | public byte?[,] View { get; set; } 74 | public byte[] SelectedFont { get; set;} = new byte[TILE_HEIGHT]; // 1,2,3,4 75 | 76 | private static readonly byte?[,] WorkBuffer = new byte?[TILE_WIDTH, TILE_HEIGHT]; 77 | 78 | public TileData() 79 | { 80 | View = new byte?[TILE_WIDTH, TILE_HEIGHT]; 81 | for (var a = 0; a < TILE_HEIGHT; a++) 82 | { 83 | SelectedFont[a] = 1; 84 | } 85 | for (var a = 0; a < TILE_WIDTH; a++) 86 | { 87 | for (var b = 0; b < TILE_HEIGHT; b++) 88 | { 89 | View[a, b] = null; 90 | } 91 | } 92 | } 93 | 94 | public TileData(byte?[,]? view, byte[] selectedFont) 95 | { 96 | if (view == null) 97 | View = new byte?[TILE_WIDTH, TILE_HEIGHT]; 98 | else 99 | View = (view.Clone() as byte?[,])!; 100 | SelectedFont = (selectedFont.Clone() as byte[])!; 101 | } 102 | 103 | public TileData(TileData from) : 104 | this(from.View, from.SelectedFont) 105 | { 106 | } 107 | 108 | /// 109 | /// Generate a string representation of the tile data 110 | /// 111 | /// 112 | public SavedTileData? Save(int tileNr) 113 | { 114 | var numNulls = 0; 115 | // Check if the tile is empty 116 | var sbView = new StringBuilder(); 117 | var sbNulls = new StringBuilder(); 118 | for (var y = 0; y < TILE_HEIGHT; y++) 119 | { 120 | for (var x = 0; x < TILE_WIDTH; x++) 121 | { 122 | var theChar = View[x, y]; 123 | if (theChar == null) 124 | { 125 | sbNulls.Append('1'); 126 | theChar = 0; 127 | ++numNulls; 128 | } 129 | else 130 | { 131 | sbNulls.Append('0'); 132 | } 133 | sbView.Append($"{theChar:X2}"); 134 | } 135 | } 136 | 137 | if (numNulls == TILE_HEIGHT * TILE_WIDTH) 138 | return null; 139 | 140 | var data = new SavedTileData() 141 | { 142 | Nr = tileNr, 143 | View = sbView.ToString(), 144 | Font = Convert.ToHexString(SelectedFont), 145 | Nulls = sbNulls.ToString(), 146 | Width = TILE_WIDTH, 147 | Height = TILE_HEIGHT, 148 | }; 149 | return data; 150 | } 151 | 152 | public bool Load(SavedTileData data) 153 | { 154 | var width = data.Width; 155 | var height = data.Height; 156 | 157 | if (width == 0) width = OLD_TILE_WIDTH; 158 | if (height == 0) height = OLD_TILE_HEIGHT; 159 | 160 | var viewData = Convert.FromHexString(data.View); // Convert the HEX string into bytes and store in the View 161 | 162 | var index = 0; 163 | for (var y = 0; y < height; ++y) 164 | { 165 | for (var x = 0; x < width; ++x) 166 | { 167 | View[x, y] = data.Nulls[index] == '0' ? viewData[index] : null; 168 | ++index; 169 | } 170 | } 171 | 172 | var selectedFont = Convert.FromHexString(data.Font); 173 | for (var i = 0; i < height; ++i) 174 | { 175 | SelectedFont[i] = selectedFont[i]; 176 | } 177 | 178 | return true; 179 | } 180 | 181 | public void RotateRight() 182 | { 183 | Push(); 184 | for (var y = 0; y < TILE_HEIGHT; ++y) 185 | { 186 | for (var x = 0; x < TILE_WIDTH; ++x) 187 | { 188 | WorkBuffer[x, y] = View[y, TILE_WIDTH - x - 1]; 189 | } 190 | } 191 | for (var y = 0; y < TILE_HEIGHT; ++y) 192 | { 193 | for (var x = 0; x < TILE_WIDTH; ++x) 194 | { 195 | View[x, y] = WorkBuffer[x, y]; 196 | } 197 | } 198 | } 199 | 200 | public void RotateLeft() 201 | { 202 | Push(); 203 | for (var y = 0; y < TILE_HEIGHT; ++y) 204 | { 205 | for (var x = 0; x < TILE_WIDTH; ++x) 206 | { 207 | WorkBuffer[x, y] = View[TILE_HEIGHT - y - 1, x]; 208 | } 209 | } 210 | for (var y = 0; y < TILE_HEIGHT; ++y) 211 | { 212 | for (var x = 0; x < TILE_WIDTH; ++x) 213 | { 214 | View[x, y] = WorkBuffer[x, y]; 215 | } 216 | } 217 | } 218 | 219 | public void MirrorHorizontal() 220 | { 221 | Push(); 222 | for (var y = 0; y < TILE_HEIGHT; ++y) 223 | { 224 | for (var x = 0; x < TILE_WIDTH / 2; ++x) 225 | { 226 | (View[x, y], View[TILE_WIDTH - x - 1, y]) = (View[TILE_WIDTH - x - 1, y], View[x, y]); 227 | } 228 | } 229 | } 230 | 231 | public void MirrorVertical() 232 | { 233 | Push(); 234 | for (var x = 0; x < TILE_WIDTH; ++x) 235 | { 236 | for (var y = 0; y < TILE_HEIGHT / 2; ++y) 237 | { 238 | (View[x, y], View[x, TILE_HEIGHT - y - 1]) = (View[x, TILE_HEIGHT - y - 1], View[x, y]); 239 | } 240 | } 241 | } 242 | 243 | public void ShiftLeft() 244 | { 245 | Push(); 246 | for (var y = 0; y < TILE_HEIGHT; ++y) 247 | { 248 | // Store the leftmost byte of the current row 249 | var leftmost = View[0, y]; 250 | 251 | // Shift all elements in the row to the left 252 | for (var x = 0; x < TILE_WIDTH - 1; ++x) 253 | { 254 | View[x, y] = View[x + 1, y]; 255 | } 256 | 257 | // Place the leftmost byte in the rightmost position 258 | View[TILE_WIDTH - 1, y] = leftmost; 259 | } 260 | } 261 | 262 | public void ShiftRight() 263 | { 264 | Push(); 265 | for (var y = 0; y < TILE_HEIGHT; ++y) 266 | { 267 | // Store the rightmost byte of the current row 268 | var rightmost = View[TILE_WIDTH - 1, y]; 269 | 270 | // Shift all elements in the row to the right 271 | for (var x = TILE_WIDTH - 1; x > 0; --x) 272 | { 273 | View[x, y] = View[x - 1, y]; 274 | } 275 | 276 | // Place the rightmost byte in the leftmost position 277 | View[0, y] = rightmost; 278 | } 279 | } 280 | 281 | public void ShiftUp() 282 | { 283 | Push(); 284 | for (var x = 0; x < TILE_WIDTH; ++x) 285 | { 286 | // Store the topmost byte of the current column 287 | var topmost = View[x, 0]; 288 | 289 | // Shift all elements in the column up 290 | for (var y = 0; y < TILE_HEIGHT - 1; ++y) 291 | { 292 | View[x, y] = View[x, y + 1]; 293 | } 294 | 295 | // Place the topmost byte in the bottommost position 296 | View[x, TILE_HEIGHT - 1] = topmost; 297 | } 298 | } 299 | 300 | public void ShiftDown() 301 | { 302 | Push(); 303 | for (var x = 0; x < TILE_WIDTH; ++x) 304 | { 305 | // Store the bottommost byte of the current column 306 | var bottommost = View[x, TILE_HEIGHT - 1]; 307 | 308 | // Shift all elements in the column down 309 | for (var y = TILE_HEIGHT - 1; y > 0; --y) 310 | { 311 | View[x, y] = View[x, y - 1]; 312 | } 313 | 314 | // Place the bottommost byte in the topmost position 315 | View[x, 0] = bottommost; 316 | } 317 | } 318 | 319 | #region Undo/Redo 320 | 321 | public const int UndoBufferSize = 250; 322 | 323 | private readonly LinkedList _undoCommands = new(); 324 | private readonly Stack _redoCommands = new(); 325 | 326 | public void Push() 327 | { 328 | while (_undoCommands.Count >= UndoBufferSize) 329 | { 330 | _undoCommands.RemoveFirst(); 331 | } 332 | _undoCommands.AddLast(View.Clone() as byte?[,]); 333 | if (_redoCommands.Count > 0) 334 | _redoCommands.Clear(); 335 | } 336 | 337 | public void Undo() 338 | { 339 | if (_undoCommands.Count > 0) 340 | { 341 | // Save the current screen 342 | _redoCommands.Push(View.Clone() as byte?[,]); 343 | 344 | // Get the last screen and restore it 345 | var data = _undoCommands.Last(); 346 | _undoCommands.RemoveLast(); 347 | if (data != null) 348 | MyBlockCopy(data); 349 | } 350 | } 351 | 352 | public void Redo() 353 | { 354 | if (_redoCommands.Count > 0) 355 | { 356 | // Save the current screen 357 | while (_undoCommands.Count >= UndoBufferSize) 358 | { 359 | _undoCommands.RemoveFirst(); 360 | } 361 | _undoCommands.AddLast(View.Clone() as byte?[,]); 362 | 363 | // Get the last screen and restore it 364 | var data = _redoCommands.Pop(); 365 | if (data != null) 366 | MyBlockCopy(data); 367 | } 368 | } 369 | 370 | private void MyBlockCopy(byte?[,]? data) 371 | { 372 | if (data == null) 373 | return; 374 | for (var y = 0; y < TILE_HEIGHT; ++y) 375 | { 376 | for (var x = 0; x < TILE_WIDTH; ++x) 377 | { 378 | View[x, y] = data[x, y]; 379 | } 380 | } 381 | } 382 | 383 | public (bool, bool) GetRedoUndoButtonState() 384 | { 385 | return (_undoCommands.Count > 0 ? true : false, _redoCommands.Count > 0 ? true : false); 386 | } 387 | 388 | #endregion 389 | } 390 | 391 | 392 | /// 393 | /// All functions that help with TileSet management 394 | /// 395 | public static class TileSet 396 | { 397 | public const int NUM_TILES_IN_SET = 256; 398 | 399 | public static TileData[] Tiles { get; set; } = new TileData[NUM_TILES_IN_SET]; 400 | 401 | public static TileData? CurrentTile { get; set; } 402 | 403 | //public static TileData? CopiedTile { get; set; } 404 | 405 | public static void Setup() 406 | { 407 | for (var i = 0; i < Tiles.Length; i++) 408 | { 409 | Tiles[i] = new TileData(); 410 | } 411 | 412 | CurrentTile = Tiles[0]; 413 | } 414 | 415 | /// 416 | /// Save a specific tile's data to the 'SavedTileData' format 417 | /// 418 | /// 419 | /// 420 | public static SavedTileData? Save(int tileNr) 421 | { 422 | return Tiles[tileNr].Save(tileNr); 423 | } 424 | 425 | /// 426 | /// Load a tile's data via the 'SavedTileData' format 427 | /// 428 | /// 429 | /// true if the load was ok, false if there was an error 430 | public static bool Load(SavedTileData data) 431 | { 432 | try 433 | { 434 | return Tiles[data.Nr].Load(data); 435 | } 436 | catch (Exception _) 437 | { 438 | return false; 439 | } 440 | } 441 | } --------------------------------------------------------------------------------