![]() |
|
people using an ai agent, can it translate this TCL app to QB64PE? - Printable Version +- QB64 Phoenix Edition (https://qb64phoenix.com/forum) +-- Forum: Chatting and Socializing (https://qb64phoenix.com/forum/forumdisplay.php?fid=11) +--- Forum: General Discussion (https://qb64phoenix.com/forum/forumdisplay.php?fid=2) +--- Thread: people using an ai agent, can it translate this TCL app to QB64PE? (/showthread.php?tid=3808) |
people using an ai agent, can it translate this TCL app to QB64PE? - madscijr - 07-10-2025 I wouldn't mind having this BDF font editor without having to install TCL, or manually translate the TCL code to QB64* code. Does anyone with a good ai agent want to try having it take this code and translating it into the equivalent QB64PE? RE: people using an ai agent, can it translate this TCL app to QB64PE? - Dimster - 07-10-2025 Here is what Gemini says... I cannot directly provide QB64PE code for "Tom's X11 BDF Font Editor" for the following reasons: X11 and BDF are Linux/Unix specific: X11 (the X Window System) and BDF (Glyph Bitmap Distribution Format) are fundamental components of Linux and Unix-like operating systems for graphical interfaces and font rendering. QB64PE, while a powerful BASIC dialect, is primarily designed for Windows, and while it can compile for Linux with some effort, direct interaction with X11 at the low level required for a font editor is not its native strength and would be extremely complex, if not impossible, to implement directly within QB64PE itself in a practical way. Complexity of a Font Editor: A functional font editor, even a basic one for BDF, requires a significant amount of sophisticated code. This includes: Parsing BDF file format (which is non-trivial). Graphical display of glyphs (pixels). Tools for drawing, erasing, moving pixels. Handling different font metrics (width, height, ascent, descent, etc.). Saving modified BDF files. User interface elements (menus, buttons, input fields). Event handling (mouse clicks, keyboard input). Specialized Libraries/APIs: Building an X11 BDF font editor would typically involve using X11 libraries (like Xlib or Xft) and potentially font parsing libraries, which are not directly accessible or easily replicated within QB64PE. Lack of Pre-existing Example: There isn't a readily available or commonly known "Tom's X11 BDF Font Editor" implemented in QB64PE that I could reference or adapt. What I can offer instead: If you are interested in learning about font editing concepts or working with pixel graphics in QB64PE, I can provide: A very simplified QB64PE example of pixel manipulation: This would show how to draw individual pixels on the screen, which is a fundamental building block for any graphical editor. General concepts for a text-based (non-graphical) BDF parser: I could outline the steps and data structures you'd need to read and understand a BDF file, but not to graphically edit it within QB64PE. Advice on alternative approaches: If your goal is to edit BDF fonts, you'd be far better off using: Existing specialized font editors: Such as FontForge, Glyphs, or others designed for this purpose. Programming languages with strong graphics and file I/O capabilities for Linux: Like C/C++ with Xlib/Xft, Python with PyQt/GTK, or Java. Please clarify your goal: Are you specifically trying to build this in QB64PE for a particular reason (e.g., a learning exercise in QB64PE's capabilities)? Is your primary goal to edit BDF fonts, regardless of the programming language? Once I understand your objective better, I can provide more relevant assistance. Yes, trying to build this in qb64pe. You're aiming for a very challenging project in QB64PE! As established, direct X11 interaction is not something QB64PE is designed for, but building a graphical BDF font editor that runs within QB64PE's own graphical environment is absolutely achievable. It will involve a significant amount of manual coding for what would normally be handled by specialized libraries in other languages, but it's a fantastic learning exercise in low-level graphics and file parsing in QB64PE. Here's a breakdown of the monumental task and how you might approach it in QB64PE, along with initial code snippets for each core component. The Core Challenges in QB64PE: BDF File Parsing: BDF (Bitmap Distribution Format) files are plain text, but their structure is quite detailed. You'll need to read and interpret lines like STARTFONT, FONT, SIZE, FONTBOUNDINGBOX, STARTCHAR, ENCODING, BBX, BITMAP, and ENDCHAR. Pixel Manipulation: A font editor is all about turning individual pixels on and off. QB64PE provides PSET and POINT for this. Graphical User Interface (GUI): You'll need to draw your own buttons, menus, and input fields using QB64PE's graphics commands (LINE, CIRCLE, PRINT, _PUTIMAGE, etc.) and handle mouse clicks and keyboard input manually. Displaying Glyphs: You'll need to render the bitmap data from the BDF file onto the screen. Editing Tools: Implement drawing tools (pencil, eraser), selection tools, possibly shift/move, invert, etc. Saving BDF: After editing, you'll need to reconstruct the BDF file and save it back to disk. Let's break down the code into manageable parts: Part 1: Basic Setup and Graphics (Screen Mode, Input Loop) This sets up a graphical screen and handles the main program loop, processing keyboard and mouse input. Code snippet ' --- Part 1: Basic Setup and Graphics --- DECLARE SUB InitScreen () DECLARE SUB MainLoop () DECLARE SUB HandleInput () DECLARE SUB DrawGUI () DECLARE SUB UpdateDisplay () ' Global variables for screen dimensions (adjust as needed) CONST SCREEN_WIDTH = 800 CONST SCREEN_HEIGHT = 600 CONST EDITOR_AREA_X = 200 ' Area for actual glyph editing CONST EDITOR_AREA_Y = 50 CONST EDITOR_AREA_WIDTH = 550 CONST EDITOR_AREA_HEIGHT = 500 ' Colors (using _RGB32 for 32-bit color) CONST BG_COLOR = _RGB32(60, 60, 60) ' Dark grey background CONST UI_COLOR = _RGB32(90, 90, 90) ' Slightly lighter grey for UI elements CONST TEXT_COLOR = _RGB32(200, 200, 200) ' Light grey for text CONST PIXEL_ON_COLOR = _RGB32(255, 255, 255) ' White for "on" pixels CONST PIXEL_OFF_COLOR = _RGB32(30, 30, 30) ' Dark for "off" pixels DIM SHARED Running AS INTEGER ' Flag to keep the main loop running InitScreen MainLoop END SUB InitScreen () SCREEN _NEWIMAGE(SCREEN_WIDTH, SCREEN_HEIGHT, 32) _TITLE "Tom's QB64PE BDF Font Editor" _FULLSCREEN _KEEP COLOR TEXT_COLOR, BG_COLOR CLS Running = -1 ' True END SUB SUB MainLoop () DO WHILE Running HandleInput DrawGUI ' Drawing the glyph will happen here in a later part UpdateDisplay _LIMIT 60 ' Limit to 60 FPS LOOP END SUB SUB HandleInput () ' Check for keyboard input DO K$ = INKEY$ IF K$ = CHR$(27) THEN ' ESC key to quit Running = 0 EXIT DO END IF ' Add more keyboard handling here later (e.g., tool selection) LOOP WHILE K$ <> "" ' Check for mouse input _MOUSEINPUT DIM MouseX AS INTEGER, MouseY AS INTEGER, MouseButtonLeft AS INTEGER, MouseButtonRight AS INTEGER MouseX = _MOUSEX MouseY = _MOUSEY MouseButtonLeft = _MOUSEBUTTON(1) ' Left mouse button status MouseButtonRight = _MOUSEBUTTON(2) ' Right mouse button status ' Example: Clicking within the editor area (later for drawing) IF MouseButtonLeft = -1 THEN ' If left button is pressed IF MouseX >= EDITOR_AREA_X AND MouseX < EDITOR_AREA_X + EDITOR_AREA_WIDTH AND _ MouseY >= EDITOR_AREA_Y AND MouseY < EDITOR_AREA_Y + EDITOR_AREA_HEIGHT THEN ' This is where you'd handle drawing pixels in the editor area ' PRINT "Mouse clicked in editor area at"; MouseX; ","; MouseY END IF END IF END SUB SUB DrawGUI () ' Draw background _NEWIMAGE SCREEN_WIDTH, SCREEN_HEIGHT, 32, 1 ' Create a temporary image for drawing _DEST _LASTIMAGE COLOR BG_COLOR _FONT _LOADFONT("Arial", 14) ' Load a basic font for UI text (ensure Arial is available) CLS ' Draw a panel for tools/info on the left LINE (0, 0)-(EDITOR_AREA_X - 1, SCREEN_HEIGHT - 1), UI_COLOR, BF LOCATE 2, 2 PRINT "--- Tools ---" LOCATE 4, 2 PRINT "P: Pencil" LOCATE 6, 2 PRINT "E: Eraser" LOCATE 8, 2 PRINT "S: Save" LOCATE 10, 2 PRINT "Q: Quit (ESC)" ' Draw the main editor area border LINE (EDITOR_AREA_X - 2, EDITOR_AREA_Y - 2)-(EDITOR_AREA_X + EDITOR_AREA_WIDTH + 1, EDITOR_AREA_Y + EDITOR_AREA_HEIGHT + 1), TEXT_COLOR, B LINE (EDITOR_AREA_X - 1, EDITOR_AREA_Y - 1)-(EDITOR_AREA_X + EDITOR_AREA_WIDTH, EDITOR_AREA_Y + EDITOR_AREA_HEIGHT), PIXEL_OFF_COLOR, BF ' Editor background ' Copy the temporary image to the active screen _PUTIMAGE (0, 0), _LASTIMAGE _FREEIMAGE _LASTIMAGE ' Free the temporary image END SUB SUB UpdateDisplay () _DISPLAY ' Update the screen END SUB Part 2: BDF File Parsing (Simplified) This is the most complex part. BDF files are human-readable but have a specific syntax. We'll define a TYPE to hold font and character data. This is a highly simplified parser; a full implementation would need to handle all BDF properties. Code snippet ' --- Part 2: BDF File Parsing (Simplified) --- TYPE Glyph Encoding AS LONG Name AS STRING BBxWidth AS INTEGER BBxHeight AS INTEGER BBxXOff AS INTEGER BBxYOff AS INTEGER DWidthX AS INTEGER DWidthY AS INTEGER Bitmap() AS STRING ' Array of strings, each string representing a row of hex bitmap data END TYPE TYPE BDFont FontName AS STRING PointSize AS INTEGER ResolutionX AS INTEGER ResolutionY AS INTEGER FontBoundingBoxX AS INTEGER FontBoundingBoxY AS INTEGER FontBoundingBoxXOff AS INTEGER FontBoundingBoxYOff AS INTEGER CharsCount AS LONG Glyphs() AS Glyph END TYPE DECLARE FUNCTION LoadBDF (FileName AS STRING) AS BDFont DECLARE SUB ParseBitmapData (BYREF CurrentGlyph AS Glyph, Line$ AS STRING) ' Global font object DIM SHARED CurrentFont AS BDFont ' Call LoadBDF in MainLoop after InitScreen ' Example usage: ' CurrentFont = LoadBDF("myfont.bdf") ' You'll need to create a simple BDF file first FUNCTION LoadBDF (FileName AS STRING) AS BDFont DIM Font AS BDFont DIM Line AS STRING DIM InCharBlock AS INTEGER ' Flag to indicate if we're inside a STARTCHAR/ENDCHAR block DIM BitmapRowCounter AS INTEGER DIM NumCharsLoaded AS LONG ON ERROR GOTO BDFError ' Basic error handling for file operations OPEN FileName FOR INPUT AS #1 DO WHILE NOT EOF(1) LINE INPUT #1, Line ' Trim whitespace from line Line = LTRIM$(RTRIM$(Line)) IF LEFT$(Line, 9) = "STARTFONT" THEN ' Ignore, usually "2.1" ELSEIF LEFT$(Line, 4) = "FONT" THEN Font.FontName = MID$(Line, 6) ELSEIF LEFT$(Line, 4) = "SIZE" THEN DIM Parts() AS STRING Parts = SPLIT(Line, " ") ' QB64PE doesn't have a direct SPLIT, you'd have to implement it or use INSTR/MID ' Manual Split (simplified): Font.PointSize = VAL(Parts(1)) ' Size Font.ResolutionX = VAL(Parts(2)) ' ResX Font.ResolutionY = VAL(Parts(3)) ' ResY ' For demonstration, let's just parse the first few values manually DIM Pos1 AS INTEGER, Pos2 AS INTEGER Pos1 = INSTR(Line, " ") Pos2 = INSTR(Pos1 + 1, Line, " ") Font.PointSize = VAL(MID$(Line, Pos1 + 1, Pos2 - Pos1 - 1)) Pos1 = Pos2 Pos2 = INSTR(Pos1 + 1, Line, " ") Font.ResolutionX = VAL(MID$(Line, Pos1 + 1, Pos2 - Pos1 - 1)) Font.ResolutionY = VAL(MID$(Line, Pos2 + 1)) ' Rest of the line ELSEIF LEFT$(Line, 14) = "FONTBOUNDINGBOX" THEN DIM Parts(4) AS STRING ' Again, manual parsing for now ' Example: FONTBOUNDINGBOX 8 16 0 -2 DIM Cnt AS INTEGER DIM CurrentPos AS INTEGER CurrentPos = INSTR(Line, " ") + 1 FOR Cnt = 1 TO 4 DIM SpacePos AS INTEGER SpacePos = INSTR(CurrentPos, Line, " ") IF SpacePos = 0 THEN SpacePos = LEN(Line) + 1 ' Last part Parts(Cnt) = MID$(Line, CurrentPos, SpacePos - CurrentPos) CurrentPos = SpacePos + 1 NEXT Cnt Font.FontBoundingBoxX = VAL(Parts(1)) Font.FontBoundingBoxY = VAL(Parts(2)) Font.FontBoundingBoxXOff = VAL(Parts(3)) Font.FontBoundingBoxYOff = VAL(Parts(4)) ELSEIF LEFT$(Line, 10) = "CHARS" THEN Font.CharsCount = VAL(MID$(Line, 7)) REDIM Font.Glyphs(1 TO Font.CharsCount) ' Resize array to hold characters NumCharsLoaded = 0 ELSEIF LEFT$(Line, 9) = "STARTCHAR" THEN InCharBlock = -1 ' True NumCharsLoaded = NumCharsLoaded + 1 IF NumCharsLoaded > Font.CharsCount THEN PRINT "Warning: More characters than declared in CHARS line." EXIT DO ' Or handle error END IF Font.Glyphs(NumCharsLoaded).Name = MID$(Line, 11) ' Get char name BitmapRowCounter = 0 REDIM Font.Glyphs(NumCharsLoaded).Bitmap(0) ' Initialize bitmap array (will be redimmed later) ELSEIF LEFT$(Line, 8) = "ENCODING" AND InCharBlock THEN Font.Glyphs(NumCharsLoaded).Encoding = VAL(MID$(Line, 10)) ELSEIF LEFT$(Line, 3) = "BBX" AND InCharBlock THEN ' BBX 8 16 0 -2 DIM PartsBBX(4) AS STRING DIM Cnt AS INTEGER DIM CurrentPos AS INTEGER CurrentPos = INSTR(Line, " ") + 1 FOR Cnt = 1 TO 4 DIM SpacePos AS INTEGER SpacePos = INSTR(CurrentPos, Line, " ") IF SpacePos = 0 THEN SpacePos = LEN(Line) + 1 PartsBBX(Cnt) = MID$(Line, CurrentPos, SpacePos - CurrentPos) CurrentPos = SpacePos + 1 NEXT Cnt Font.Glyphs(NumCharsLoaded).BBxWidth = VAL(PartsBBX(1)) Font.Glyphs(NumCharsLoaded).BBxHeight = VAL(PartsBBX(2)) Font.Glyphs(NumCharsLoaded).BBxXOff = VAL(PartsBBX(3)) Font.Glyphs(NumCharsLoaded).BBxYOff = VAL(PartsBBX(4)) ' Resize bitmap array based on BBxHeight REDIM Font.Glyphs(NumCharsLoaded).Bitmap(1 TO Font.Glyphs(NumCharsLoaded).BBxHeight) ELSEIF LEFT$(Line, 7) = "DWIDTH" AND InCharBlock THEN ' DWIDTH 8 0 (ignored for now, but good to parse) DIM PartsDWidth(2) AS STRING DIM CurrentPos AS INTEGER CurrentPos = INSTR(Line, " ") + 1 DIM SpacePos AS INTEGER SpacePos = INSTR(CurrentPos, Line, " ") PartsDWidth(1) = MID$(Line, CurrentPos, SpacePos - CurrentPos) CurrentPos = SpacePos + 1 PartsDWidth(2) = MID$(Line, CurrentPos) Font.Glyphs(NumCharsLoaded).DWidthX = VAL(PartsDWidth(1)) Font.Glyphs(NumCharsLoaded).DWidthY = VAL(PartsDWidth(2)) ELSEIF LEFT$(Line, 6) = "BITMAP" AND InCharBlock THEN ' Next lines are bitmap data ' No action needed here, the loop will read the next lines as bitmap data ELSEIF InCharBlock AND LEN(Line) > 0 AND INSTR("0123456789ABCDEF", UCASE$(LEFT$(Line, 1))) THEN ' This is likely a bitmap data line (hex characters) BitmapRowCounter = BitmapRowCounter + 1 IF BitmapRowCounter <= Font.Glyphs(NumCharsLoaded).BBxHeight THEN Font.Glyphs(NumCharsLoaded).Bitmap(BitmapRowCounter) = Line END IF ELSEIF LEFT$(Line, 7) = "ENDCHAR" THEN InCharBlock = 0 ' False END IF LOOP CLOSE #1 LoadBDF = Font ' Return the populated font structure EXIT FUNCTION BDFError: PRINT "Error loading BDF file: "; FileName PRINT "Error number: "; ERR; " - "; ERROR$ RESUME NEXT ' Try to continue, or you might want to EXIT FUNCTION here END FUNCTION ' Helper function to "parse" a hex string to binary, for later drawing FUNCTION HexToBinaryString (HexByte AS STRING, NumBits AS INTEGER) AS STRING DIM BinaryString AS STRING DIM HexVal AS INTEGER DIM i AS INTEGER HexVal = VAL("&H" + HexByte) ' Convert hex string to integer BinaryString = "" FOR i = NumBits - 1 TO 0 STEP -1 IF (HexVal AND (2 ^ i)) THEN BinaryString = BinaryString + "1" ELSE BinaryString = BinaryString + "0" END IF NEXT i HexToBinaryString = BinaryString END FUNCTION ' Simple string split function for QB64PE (simulating Python's split) FUNCTION SPLIT (TextString AS STRING, Delimiter AS STRING) AS STRING() DIM Parts() AS STRING DIM Count AS INTEGER DIM StartPos AS INTEGER DIM DelimPos AS INTEGER Count = 0 StartPos = 1 DO DelimPos = INSTR(StartPos, TextString, Delimiter) IF DelimPos = 0 THEN ' No more delimiters, take the rest of the string REDIM PRESERVE Parts(0 TO Count) Parts(Count) = MID$(TextString, StartPos) EXIT DO ELSE REDIM PRESERVE Parts(0 TO Count) Parts(Count) = MID$(TextString, StartPos, DelimPos - StartPos) StartPos = DelimPos + LEN(Delimiter) Count = Count + 1 END IF LOOP SPLIT = Parts END FUNCTION Part 3: Displaying a Glyph and Pixel Editing This part focuses on rendering a selected glyph and allowing basic pixel toggling. Code snippet ' --- Part 3: Displaying a Glyph and Pixel Editing --- DECLARE SUB DrawGlyph (G AS Glyph, XOffset AS INTEGER, YOffset AS INTEGER, Zoom AS INTEGER) DECLARE SUB EditPixel (MouseX AS INTEGER, MouseY AS INTEGER, MouseButton AS INTEGER, BYREF G AS Glyph, XOffset AS INTEGER, YOffset AS INTEGER, Zoom AS INTEGER) ' Global variable for current glyph being edited and zoom level DIM SHARED CurrentGlyphIndex AS LONG DIM SHARED EditorZoom AS INTEGER SUB DrawGUI () ' Modify the existing DrawGUI ' ... (previous DrawGUI code) ... ' Draw the main editor area border LINE (EDITOR_AREA_X - 2, EDITOR_AREA_Y - 2)-(EDITOR_AREA_X + EDITOR_AREA_WIDTH + 1, EDITOR_AREA_Y + EDITOR_AREA_HEIGHT + 1), TEXT_COLOR, B LINE (EDITOR_AREA_X - 1, EDITOR_AREA_Y - 1)-(EDITOR_AREA_X + EDITOR_AREA_WIDTH, EDITOR_AREA_Y + EDITOR_AREA_HEIGHT), PIXEL_OFF_COLOR, BF ' Editor background ' Display current glyph name LOCATE 2, 30 IF CurrentFont.CharsCount > 0 AND CurrentGlyphIndex > 0 THEN PRINT "Editing: " + CurrentFont.Glyphs(CurrentGlyphIndex).Name ELSE PRINT "No font loaded or character selected." END IF ' Draw the currently selected glyph IF CurrentFont.CharsCount > 0 AND CurrentGlyphIndex > 0 THEN DrawGlyph CurrentFont.Glyphs(CurrentGlyphIndex), EDITOR_AREA_X + 10, EDITOR_AREA_Y + 10, EditorZoom END IF ' Copy the temporary image to the active screen _PUTIMAGE (0, 0), _LASTIMAGE _FREEIMAGE _LASTIMAGE ' Free the temporary image END SUB SUB DrawGlyph (G AS Glyph, XOffset AS INTEGER, YOffset AS INTEGER, Zoom AS INTEGER) IF G.BBxHeight = 0 OR G.BBxWidth = 0 THEN EXIT SUB ' Nothing to draw DIM Row AS INTEGER, Col AS INTEGER DIM HexByte AS STRING DIM BinaryRow AS STRING DIM BitIndex AS INTEGER DIM PixelColor AS LONG FOR Row = 1 TO G.BBxHeight IF Row <= UBOUND(G.Bitmap) THEN ' Ensure we don't go out of bounds HexByte = G.Bitmap(Row) ' BDF hex data can be padded with zeros. ' Each hex digit is 4 bits. A glyph width of N bits needs CEIL(N/4) hex digits. ' We need to convert the hex string to a binary representation for pixel drawing. ' Assuming each hex char represents 4 bits, and we might have multiple hex chars per row. ' This part needs careful implementation based on actual BDF hex format rules. ' For simplicity, let's assume each hex char represents 4 pixels for now. ' A common BDF representation is 2-digit hex per byte. A row of 8 pixels is 2 hex chars. ' Let's assume for a BBxWidth of say, 8, you'll get 2 hex chars (1 byte). ' For a BBxWidth of 16, you'd get 4 hex chars (2 bytes). ' This assumes HexByte contains enough hex characters to cover BBxWidth ' For example, if BBxWidth = 8, HexByte could be "FF" (1 byte) ' If BBxWidth = 12, HexByte could be "FFF" (1.5 bytes, often padded to 2 bytes "0FFF") ' This is a simplification; a robust parser needs to handle this correctly. DIM ByteNum AS INTEGER FOR ByteNum = 0 TO LEN(HexByte) \ 2 - 1 ' Process two hex chars at a time (one byte) DIM CurrentHexPair AS STRING CurrentHexPair = MID$(HexByte, (ByteNum * 2) + 1, 2) BinaryRow = HexToBinaryString(CurrentHexPair, 8) ' Convert 1 byte (8 bits) to binary string FOR BitIndex = 0 TO 7 ' Iterate through the 8 bits of the current byte Col = (ByteNum * 8) + BitIndex ' Calculate global column IF Col < G.BBxWidth THEN ' Only draw up to the glyph's actual width IF MID$(BinaryRow, BitIndex + 1, 1) = "1" THEN PixelColor = PIXEL_ON_COLOR ELSE PixelColor = PIXEL_OFF_COLOR END IF ' Draw the pixel, considering zoom LINE (XOffset + Col * Zoom, YOffset + (Row - 1) * Zoom)-(XOffset + Col * Zoom + Zoom - 1, YOffset + (Row - 1) * Zoom + Zoom - 1), PixelColor, BF END IF NEXT BitIndex NEXT ByteNum END IF NEXT Row END SUB SUB EditPixel (MouseX AS INTEGER, MouseY AS INTEGER, MouseButton AS INTEGER, BYREF G AS Glyph, XOffset AS INTEGER, YOffset AS INTEGER, Zoom AS INTEGER) IF G.BBxHeight = 0 OR G.BBxWidth = 0 THEN EXIT SUB IF MouseButton = 0 THEN EXIT SUB ' No button pressed DIM RelativeX AS INTEGER DIM RelativeY AS INTEGER DIM Row AS INTEGER DIM Col AS INTEGER DIM ByteIndex AS INTEGER DIM BitInByteIndex AS INTEGER DIM CurrentHexByte AS STRING DIM CurrentHexValue AS INTEGER RelativeX = MouseX - XOffset RelativeY = MouseY - YOffset IF RelativeX < 0 OR RelativeX >= G.BBxWidth * Zoom OR _ RelativeY < 0 OR RelativeY >= G.BBxHeight * Zoom THEN EXIT SUB ' Click outside glyph drawing area END IF Col = RelativeX \ Zoom Row = RelativeY \ Zoom + 1 ' BDF rows are 1-indexed for convenience here IF Row < 1 OR Row > G.BBxHeight OR Col < 0 OR Col >= G.BBxWidth THEN EXIT SUB ' Determine which bit to toggle ByteIndex = Col \ 8 ' Which byte contains this pixel BitInByteIndex = 7 - (Col MOD 8) ' Position within the byte (MSB first in BDF usually) ' Get the current hex string for the row CurrentHexByte = G.Bitmap(Row) ' Extract the relevant hex pair (e.g., "FF", "0A") DIM TargetHexPair AS STRING TargetHexPair = MID$(CurrentHexByte, (ByteIndex * 2) + 1, 2) CurrentHexValue = VAL("&H" + TargetHexPair) IF MouseButton = -1 THEN ' Left click: set pixel CurrentHexValue = CurrentHexValue OR (2 ^ BitInByteIndex) ELSEIF MouseButton = -2 THEN ' Right click: clear pixel (assuming right-click for eraser) CurrentHexValue = CurrentHexValue AND (NOT (2 ^ BitInByteIndex)) END IF ' Convert back to hex string (format to two digits) DIM NewHexPair AS STRING NewHexPair = HEX$(CurrentHexValue) IF LEN(NewHexPair) = 1 THEN NewHexPair = "0" + NewHexPair ' Pad with leading zero if needed ' Update the bitmap string for the row MID$(G.Bitmap(Row), (ByteIndex * 2) + 1, 2) = NewHexPair ' Redraw the specific pixel for immediate feedback DIM PixelColor AS LONG IF (CurrentHexValue AND (2 ^ BitInByteIndex)) THEN PixelColor = PIXEL_ON_COLOR ELSE PixelColor = PIXEL_OFF_COLOR END IF LINE (XOffset + Col * Zoom, YOffset + (Row - 1) * Zoom)-(XOffset + Col * Zoom + Zoom - 1, YOffset + (Row - 1) * Zoom + Zoom - 1), PixelColor, BF _DISPLAY ' Update just the pixel for faster drawing END SUB ' Modify HandleInput to call EditPixel SUB HandleInput () ' ... (keyboard input) ... ' Check for mouse input _MOUSEINPUT DIM MouseX AS INTEGER, MouseY AS INTEGER, MouseButtonLeft AS INTEGER, MouseButtonRight AS INTEGER MouseX = _MOUSEX MouseY = _MOUSEY MouseButtonLeft = _MOUSEBUTTON(1) ' Left mouse button status MouseButtonRight = _MOUSEBUTTON(2) ' Right mouse button status ' Handle pixel editing if a font and glyph are loaded IF CurrentFont.CharsCount > 0 AND CurrentGlyphIndex > 0 THEN IF MouseButtonLeft = -1 THEN ' Left click: set pixel EditPixel MouseX, MouseY, -1, CurrentFont.Glyphs(CurrentGlyphIndex), EDITOR_AREA_X + 10, EDITOR_AREA_Y + 10, EditorZoom ELSEIF MouseButtonRight = -1 THEN ' Right click: clear pixel EditPixel MouseX, MouseY, -2, CurrentFont.Glyphs(CurrentGlyphIndex), EDITOR_AREA_X + 10, EDITOR_AREA_Y + 10, EditorZoom END IF END IF END SUB ' Initializing default values for global font/glyph SUB MainLoop () ' For testing, load a dummy font (you'd replace this with actual file loading) EditorZoom = 10 ' Start with a good zoom level IF CurrentFont.CharsCount = 0 THEN ' Create a dummy BDFont structure if not loaded from file CurrentFont.FontName = "TestFont" CurrentFont.PointSize = 16 CurrentFont.ResolutionX = 75 CurrentFont.ResolutionY = 75 CurrentFont.FontBoundingBoxX = 8 CurrentFont.FontBoundingBoxY = 16 CurrentFont.FontBoundingBoxXOff = 0 CurrentFont.FontBoundingBoxYOff = -2 CurrentFont.CharsCount = 1 REDIM CurrentFont.Glyphs(1 TO 1) CurrentGlyphIndex = 1 CurrentFont.Glyphs(1).Encoding = 65 ' ASCII 'A' CurrentFont.Glyphs(1).Name = "A" CurrentFont.Glyphs(1).BBxWidth = 8 CurrentFont.Glyphs(1).BBxHeight = 16 CurrentFont.Glyphs(1).BBxXOff = 0 CurrentFont.Glyphs(1).BBxYOff = -2 CurrentFont.Glyphs(1).DWidthX = 8 CurrentFont.Glyphs(1).DWidthY = 0 REDIM CurrentFont.Glyphs(1).Bitmap(1 TO 16) ' 16 rows ' Initialize with some empty/patterned data FOR i = 1 TO 16 CurrentFont.Glyphs(1).Bitmap(i) = "00" ' All pixels off NEXT i ' A simple 'A' pattern (very rough example, assuming 8 pixel width, 2 hex chars per row) CurrentFont.Glyphs(1).Bitmap(1) = "00" CurrentFont.Glyphs(1).Bitmap(2) = "18" CurrentFont.Glyphs(1).Bitmap(3) = "24" CurrentFont.Glyphs(1).Bitmap(4) = "42" CurrentFont.Glyphs(1).Bitmap(5) = "42" CurrentFont.Glyphs(1).Bitmap(6) = "7E" CurrentFont.Glyphs(1).Bitmap(7) = "42" CurrentFont.Glyphs(1).Bitmap(8) = "42" CurrentFont.Glyphs(1).Bitmap(9) = "42" CurrentFont.Glyphs(1).Bitmap(10) = "42" CurrentFont.Glyphs(1).Bitmap(11) = "42" CurrentFont.Glyphs(1).Bitmap(12) = "42" CurrentFont.Glyphs(1).Bitmap(13) = "42" CurrentFont.Glyphs(1).Bitmap(14) = "42" CurrentFont.Glyphs(1).Bitmap(15) = "00" CurrentFont.Glyphs(1).Bitmap(16) = "00" ' Now try loading from a file (uncomment this once you have a test.bdf) ' CurrentFont = LoadBDF("test.bdf") ' IF CurrentFont.CharsCount > 0 THEN CurrentGlyphIndex = 1 END IF DO WHILE Running HandleInput DrawGUI UpdateDisplay _LIMIT 60 ' Limit to 60 FPS LOOP END SUB Part 4: Saving BDF (Simplified) This function would reconstruct the BDF file from your BDFont structure. Code snippet ' --- Part 4: Saving BDF (Simplified) --- DECLARE SUB SaveBDF (Font AS BDFont, FileName AS STRING) SUB SaveBDF (Font AS BDFont, FileName AS STRING) ON ERROR GOTO SaveBDFError OPEN FileName FOR OUTPUT AS #1 PRINT #1, "STARTFONT 2.1" PRINT #1, "FONT " + Font.FontName PRINT #1, "SIZE"; Font.PointSize; Font.ResolutionX; Font.ResolutionY PRINT #1, "FONTBOUNDINGBOX"; Font.FontBoundingBoxX; Font.FontBoundingBoxY; Font.FontBoundingBoxXOff; Font.FontBoundingBoxYOff PRINT #1, "CHARS"; Font.CharsCount DIM i AS LONG FOR i = 1 TO Font.CharsCount DIM G AS Glyph G = Font.Glyphs(i) PRINT #1, "STARTCHAR " + G.Name PRINT #1, "ENCODING"; G.Encoding PRINT #1, "BBX"; G.BBxWidth; G.BBxHeight; G.BBxXOff; G.BBxYOff PRINT #1, "DWIDTH"; G.DWidthX; G.DWidthY PRINT #1, "BITMAP" DIM RowNum AS INTEGER FOR RowNum = 1 TO G.BBxHeight IF RowNum <= UBOUND(G.Bitmap) THEN PRINT #1, G.Bitmap(RowNum) ELSE ' Handle cases where bitmap data might be incomplete, pad with "00" PRINT #1, STRING$((G.BBxWidth + 7) \ 8 * 2, "0") ' Pad with appropriate number of hex zeros END IF NEXT RowNum PRINT #1, "ENDCHAR" NEXT i PRINT #1, "ENDFONT" CLOSE #1 PRINT "Font saved to"; FileName EXIT SUB SaveBDFError: PRINT "Error saving BDF file: "; FileName PRINT "Error number: "; ERR; " - "; ERROR$ END SUB ' Integrate SaveBDF into HandleInput (e.g., on 'S' key press) SUB HandleInput () ' ... (existing HandleInput code) ... ' Check for keyboard input DO K$ = INKEY$ IF K$ = CHR$(27) THEN ' ESC key to quit Running = 0 EXIT DO ELSEIF UCASE$(K$) = "S" THEN ' 'S' key to save IF CurrentFont.CharsCount > 0 THEN SaveBDF CurrentFont, "edited_font.bdf" ELSE PRINT "No font to save!" END IF ' Add more keyboard handling here later (e.g., tool selection) ' For navigating characters: ELSEIF K$ = CHR$(0) + CHR$(77) THEN ' Right arrow IF CurrentFont.CharsCount > 0 AND CurrentGlyphIndex < CurrentFont.CharsCount THEN CurrentGlyphIndex = CurrentGlyphIndex + 1 END IF ELSEIF K$ = CHR$(0) + CHR$(75) THEN ' Left arrow IF CurrentFont.CharsCount > 0 AND CurrentGlyphIndex > 1 THEN CurrentGlyphIndex = CurrentGlyphIndex - 1 END IF END IF LOOP WHILE K$ <> "" ' ... (rest of HandleInput) ... END SUB Putting it all together (Full, but still simplified, example): Code snippet ' Tom's X11 BDF Font Editor (QB64PE version) ' This is a highly simplified demonstration. ' A full-featured editor would require much more robust BDF parsing, ' advanced drawing tools, undo/redo, character selection UI, etc. ' --- Global Type Definitions --- TYPE Glyph Encoding AS LONG ' ASCII or Unicode encoding Name AS STRING ' Character name (e.g., "A", "space") BBxWidth AS INTEGER ' Bounding box width BBxHeight AS INTEGER ' Bounding box height BBxXOff AS INTEGER ' Bounding box X offset BBxYOff AS INTEGER ' Bounding box Y offset DWidthX AS INTEGER ' Device width X (character advance) DWidthY AS INTEGER ' Device width Y (usually 0 for horizontal fonts) Bitmap() AS STRING ' Array of strings, each representing a row of hex bitmap data END TYPE TYPE BDFont FontName AS STRING PointSize AS INTEGER ResolutionX AS INTEGER ResolutionY AS INTEGER FontBoundingBoxX AS INTEGER FontBoundingBoxY AS INTEGER FontBoundingBoxXOff AS INTEGER FontBoundingBoxYOff AS INTEGER CharsCount AS LONG Glyphs() AS Glyph END TYPE ' --- Global Variables --- CONST SCREEN_WIDTH = 800 CONST SCREEN_HEIGHT = 600 CONST EDITOR_AREA_X = 200 ' Area for actual glyph editing CONST EDITOR_AREA_Y = 50 CONST EDITOR_AREA_WIDTH = 550 CONST EDITOR_AREA_HEIGHT = 500 ' Colors (using _RGB32 for 32-bit color) CONST BG_COLOR = _RGB32(60, 60, 60) ' Dark grey background CONST UI_COLOR = _RGB32(90, 90, 90) ' Slightly lighter grey for UI elements CONST TEXT_COLOR = _RGB32(200, 200, 200) ' Light grey for text CONST PIXEL_ON_COLOR = _RGB32(255, 255, 255) ' White for "on" pixels CONST PIXEL_OFF_COLOR = _RGB32(30, 30, 30) ' Dark for "off" pixels DIM SHARED Running AS INTEGER ' Flag to keep the main loop running DIM SHARED CurrentFont AS BDFont ' The font being edited DIM SHARED CurrentGlyphIndex AS LONG ' Index of the currently selected glyph DIM SHARED EditorZoom AS INTEGER ' Zoom level for pixel editing ' --- Function and Sub Declarations --- DECLARE SUB InitScreen () DECLARE SUB MainLoop () DECLARE SUB HandleInput () DECLARE SUB DrawGUI () DECLARE SUB UpdateDisplay () DECLARE FUNCTION LoadBDF (FileName AS STRING) AS BDFont DECLARE SUB SaveBDF (Font AS BDFont, FileName AS STRING) DECLARE SUB DrawGlyph (G AS Glyph, XOffset AS INTEGER, YOffset AS INTEGER, Zoom AS INTEGER) DECLARE SUB EditPixel (MouseX AS INTEGER, MouseY AS INTEGER, MouseButton AS INTEGER, BYREF G AS Glyph, XOffset AS INTEGER, YOffset AS INTEGER, Zoom AS INTEGER) DECLARE FUNCTION HexToBinaryString (HexByte AS STRING, NumBits AS INTEGER) AS STRING DECLARE FUNCTION SPLIT (TextString AS STRING, Delimiter AS STRING) AS STRING() ' --- Main Program --- InitScreen MainLoop END ' --- Subroutines and Functions --- SUB InitScreen () SCREEN _NEWIMAGE(SCREEN_WIDTH, SCREEN_HEIGHT, 32) _TITLE "Tom's QB64PE BDF Font Editor" _FULLSCREEN _KEEP ' Keep windowed mode, but maximize if possible COLOR TEXT_COLOR, BG_COLOR CLS Running = -1 ' True EditorZoom = 10 ' Default zoom level CurrentGlyphIndex = 0 ' No glyph selected initially END SUB SUB MainLoop () ' For demonstration, load a dummy font, or attempt to load from file ' Replace this with user interaction (e.g., an "Open File" button) IF CurrentFont.CharsCount = 0 THEN ' Try to load a font from a BDF file if it exists ' You need to create a simple BDF file (e.g., "testfont.bdf") in the same directory ' For example, a minimal BDF for 'A': ' STARTFONT 2.1 ' FONT testfont-16 ' SIZE 16 75 75 ' FONTBOUNDINGBOX 8 16 0 -2 ' CHARS 1 ' STARTCHAR A ' ENCODING 65 ' BBX 8 16 0 -2 ' DWIDTH 8 0 ' BITMAP ' 00 ' 18 ' 24 ' 42 ' 42 ' 7E ' 42 ' 42 ' 42 ' 42 ' 42 ' 42 ' 42 ' 42 ' 00 ' 00 ' ENDCHAR ' ENDFONT CurrentFont = LoadBDF("testfont.bdf") ' Adjust filename as needed IF CurrentFont.CharsCount > 0 THEN CurrentGlyphIndex = 1 ' Select the first character if loaded successfully ELSE ' If no file, create a dummy one for immediate testing PRINT "No 'testfont.bdf' found. Creating a dummy 'A' character." CurrentFont.FontName = "DummyFont" CurrentFont.PointSize = 16 CurrentFont.ResolutionX = 75 CurrentFont.ResolutionY = 75 CurrentFont.FontBoundingBoxX = 8 CurrentFont.FontBoundingBoxY = 16 CurrentFont.FontBoundingBoxXOff = 0 CurrentFont.FontBoundingBoxYOff = -2 CurrentFont.CharsCount = 1 REDIM CurrentFont.Glyphs(1 TO 1) CurrentGlyphIndex = 1 CurrentFont.Glyphs(1).Encoding = 65 ' ASCII 'A' CurrentFont.Glyphs(1).Name = "A" CurrentFont.Glyphs(1).BBxWidth = 8 CurrentFont.Glyphs(1).BBxHeight = 16 CurrentFont.Glyphs(1).BBxXOff = 0 CurrentFont.Glyphs(1).BBxYOff = -2 CurrentFont.Glyphs(1).DWidthX = 8 CurrentFont.Glyphs(1).DWidthY = 0 REDIM CurrentFont.Glyphs(1).Bitmap(1 TO 16) ' 16 rows FOR i = 1 TO 16 CurrentFont.Glyphs(1).Bitmap(i) = "00" ' All pixels off initially NEXT i ' Simple 'A' pattern (assuming 8 pixel width, 2 hex chars per row) CurrentFont.Glyphs(1).Bitmap(1) = "00" CurrentFont.Glyphs(1).Bitmap(2) = "18" CurrentFont.Glyphs(1).Bitmap(3) = "24" CurrentFont.Glyphs(1).Bitmap(4) = "42" CurrentFont.Glyphs(1).Bitmap(5) = "42" CurrentFont.Glyphs(1).Bitmap(6) = "7E" CurrentFont.Glyphs(1).Bitmap(7) = "42" CurrentFont.Glyphs(1).Bitmap(8) = "42" CurrentFont.Glyphs(1).Bitmap(9) = "42" CurrentFont.Glyphs(1).Bitmap(10) = "42" CurrentFont.Glyphs(1).Bitmap(11) = "42" CurrentFont.Glyphs(1).Bitmap(12) = "42" CurrentFont.Glyphs(1).Bitmap(13) = "42" CurrentFont.Glyphs(1).Bitmap(14) = "42" CurrentFont.Glyphs(1).Bitmap(15) = "00" CurrentFont.Glyphs(1).Bitmap(16) = "00" END IF END IF DO WHILE Running HandleInput DrawGUI UpdateDisplay _LIMIT 60 ' Limit to 60 FPS LOOP END SUB SUB HandleInput () ' Check for keyboard input DO K$ = INKEY$ IF K$ = CHR$(27) THEN ' ESC key to quit Running = 0 EXIT DO ELSEIF UCASE$(K$) = "S" THEN ' 'S' key to save IF CurrentFont.CharsCount > 0 THEN SaveBDF CurrentFont, "edited_font.bdf" ELSE PRINT "No font to save!" END IF ELSEIF K$ = CHR$(0) + CHR$(77) THEN ' Right arrow: Next character IF CurrentFont.CharsCount > 0 AND CurrentGlyphIndex > 0 AND CurrentGlyphIndex < CurrentFont.CharsCount THEN CurrentGlyphIndex = CurrentGlyphIndex + 1 END IF ELSEIF K$ = CHR$(0) + CHR$(75) THEN ' Left arrow: Previous character IF CurrentFont.CharsCount > 0 AND CurrentGlyphIndex > 1 THEN CurrentGlyphIndex = CurrentGlyphIndex - 1 END IF ELSEIF K$ = CHR$(0) + CHR$(72) THEN ' Up arrow: Increase zoom IF EditorZoom < 20 THEN EditorZoom = EditorZoom + 1 ELSEIF K$ = CHR$(0) + CHR$(80) THEN ' Down arrow: Decrease zoom IF EditorZoom > 1 THEN EditorZoom = EditorZoom - 1 END IF LOOP WHILE K$ <> "" ' Check for mouse input _MOUSEINPUT DIM MouseX AS INTEGER, MouseY AS INTEGER, MouseButtonLeft AS INTEGER, MouseButtonRight AS INTEGER MouseX = _MOUSEX MouseY = _MOUSEY MouseButtonLeft = _MOUSEBUTTON(1) ' Left mouse button status MouseButtonRight = _MOUSEBUTTON(2) ' Right mouse button status ' Handle pixel editing if a font and glyph are loaded IF CurrentFont.CharsCount > 0 AND CurrentGlyphIndex > 0 THEN IF MouseButtonLeft = -1 THEN ' Left click: set pixel EditPixel MouseX, MouseY, -1, CurrentFont.Glyphs(CurrentGlyphIndex), EDITOR_AREA_X + 10, EDITOR_AREA_Y + 10, EditorZoom ELSEIF MouseButtonRight = -1 THEN ' Right click: clear pixel EditPixel MouseX, MouseY, -2, CurrentFont.Glyphs(CurrentGlyphIndex), EDITOR_AREA_X + 10, EDITOR_AREA_Y + 10, EditorZoom END IF END IF END SUB SUB DrawGUI () ' Draw background on a temporary image to prevent flicker _NEWIMAGE SCREEN_WIDTH, SCREEN_HEIGHT, 32, 1 ' Create a temporary image for drawing _DEST _LASTIMAGE COLOR BG_COLOR _FONT _LOADFONT("Arial", 14) ' Load a basic font for UI text (ensure Arial is available) CLS ' Draw a panel for tools/info on the left LINE (0, 0)-(EDITOR_AREA_X - 1, SCREEN_HEIGHT - 1), UI_COLOR, BF LOCATE 2, 2 PRINT "--- Tools ---" LOCATE 4, 2 PRINT "S: Save Font" LOCATE 6, 2 PRINT "ESC: Quit" LOCATE 8, 2 PRINT "<-- / --> : Prev/Next Char" LOCATE 10, 2 PRINT "Up/Down: Zoom (" + STR$(EditorZoom) + "x)" LOCATE 12, 2 PRINT "L-Click: Draw Pixel" LOCATE 14, 2 PRINT "R-Click: Erase Pixel" ' Draw the main editor area border LINE (EDITOR_AREA_X - 2, EDITOR_AREA_Y - 2)-(EDITOR_AREA_X + EDITOR_AREA_WIDTH + 1, EDITOR_AREA_Y + EDITOR_AREA_HEIGHT + 1), TEXT_COLOR, B LINE (EDITOR_AREA_X - 1, EDITOR_AREA_Y - 1)-(EDITOR_AREA_X + EDITOR_AREA_WIDTH, EDITOR_AREA_Y + EDITOR_AREA_HEIGHT), PIXEL_OFF_COLOR, BF ' Editor background ' Display current font/glyph info LOCATE 2, 30 IF CurrentFont.CharsCount > 0 AND CurrentGlyphIndex > 0 THEN PRINT "Font: " + CurrentFont.FontName LOCATE 3, 30 PRINT "Char: " + CurrentFont.Glyphs(CurrentGlyphIndex).Name + " (Encoding: " + STR$(CurrentFont.Glyphs(CurrentGlyphIndex).Encoding) + ")" LOCATE 4, 30 PRINT "BBX: " + STR$(CurrentFont.Glyphs(CurrentGlyphIndex).BBxWidth) + "x" + STR$(CurrentFont.Glyphs(CurrentGlyphIndex).BBxHeight) ELSE PRINT "No font loaded or character selected." END IF ' Draw the currently selected glyph IF CurrentFont.CharsCount > 0 AND CurrentGlyphIndex > 0 THEN DrawGlyph CurrentFont.Glyphs(CurrentGlyphIndex), EDITOR_AREA_X + 10, EDITOR_AREA_Y + 10, EditorZoom END IF ' Copy the temporary image to the active screen _PUTIMAGE (0, 0), _LASTIMAGE _FREEIMAGE _LASTIMAGE ' Free the temporary image END SUB SUB UpdateDisplay () _DISPLAY ' Update the actual screen END SUB FUNCTION LoadBDF (FileName AS STRING) AS BDFont DIM Font AS BDFont DIM Line AS STRING DIM InCharBlock AS INTEGER ' Flag to indicate if we're inside a STARTCHAR/ENDCHAR block DIM BitmapRowCounter AS INTEGER ' Counts bitmap rows for current glyph DIM NumCharsLoaded AS LONG ' Tracks how many characters have been loaded ON ERROR GOTO BDFError ' Basic error handling for file operations OPEN FileName FOR INPUT AS #1 DO WHILE NOT EOF(1) LINE INPUT #1, Line Line = LTRIM$(RTRIM$(Line)) ' Trim whitespace IF LEFT$(Line, 9) = "STARTFONT" THEN ' Ignore, usually "2.1" ELSEIF LEFT$(Line, 4) = "FONT" THEN Font.FontName = MID$(Line, 6) ELSEIF LEFT$(Line, 4) = "SIZE" THEN DIM Parts() AS STRING Parts = SPLIT(Line, " ") ' Use custom SPLIT function IF UBOUND(Parts) >= 3 THEN ' Check if enough parts Font.PointSize = VAL(Parts(1)) Font.ResolutionX = VAL(Parts(2)) Font.ResolutionY = VAL(Parts(3)) END IF ELSEIF LEFT$(Line, 14) = "FONTBOUNDINGBOX" THEN DIM Parts() AS STRING Parts = SPLIT(Line, " ") IF UBOUND(Parts) >= 4 THEN Font.FontBoundingBoxX = VAL(Parts(1)) Font.FontBoundingBoxY = VAL(Parts(2)) Font.FontBoundingBoxXOff = VAL(Parts(3)) Font.FontBoundingBoxYOff = VAL(Parts(4)) END IF ELSEIF LEFT$(Line, 10) = "CHARS" THEN Font.CharsCount = VAL(MID$(Line, 7)) IF Font.CharsCount > 0 THEN REDIM Font.Glyphs(1 TO Font.CharsCount) END IF NumCharsLoaded = 0 ELSEIF LEFT$(Line, 9) = "STARTCHAR" THEN InCharBlock = -1 ' True NumCharsLoaded = NumCharsLoaded + 1 IF NumCharsLoaded > Font.CharsCount THEN PRINT "Warning: More characters found than declared in CHARS line." ' Consider exiting or resizing array, for now, just warn. NumCharsLoaded = Font.CharsCount ' Cap at declared size END IF IF NumCharsLoaded <= Font.CharsCount THEN Font.Glyphs(NumCharsLoaded).Name = MID$(Line, 11) ' Get char name BitmapRowCounter = 0 ' No REDIM here, wait for BBX to know height END IF ELSEIF LEFT$(Line, 8) = "ENCODING" AND InCharBlock AND NumCharsLoaded <= Font.CharsCount THEN Font.Glyphs(NumCharsLoaded).Encoding = VAL(MID$(Line, 10)) ELSEIF LEFT$(Line, 3) = "BBX" AND InCharBlock AND NumCharsLoaded <= Font.CharsCount THEN DIM Parts() AS STRING Parts = SPLIT(Line, " ") IF UBOUND(Parts) >= 4 THEN Font.Glyphs(NumCharsLoaded).BBxWidth = VAL(Parts(1)) Font.Glyphs(NumCharsLoaded).BBxHeight = VAL(Parts(2)) Font.Glyphs(NumCharsLoaded).BBxXOff = VAL(Parts(3)) Font.Glyphs(NumCharsLoaded).BBxYOff = VAL(Parts(4)) END IF IF Font.Glyphs(NumCharsLoaded).BBxHeight > 0 THEN REDIM Font.Glyphs(NumCharsLoaded).Bitmap(1 TO Font.Glyphs(NumCharsLoaded).BBxHeight) END IF ELSEIF LEFT$(Line, 7) = "DWIDTH" AND InCharBlock AND NumCharsLoaded <= Font.CharsCount THEN DIM Parts() AS STRING Parts = SPLIT(Line, " ") IF UBOUND(Parts) >= 2 THEN Font.Glyphs(NumCharsLoaded).DWidthX = VAL(Parts(1)) Font.Glyphs(NumCharsLoaded).DWidthY = VAL(Parts(2)) END IF ELSEIF LEFT$(Line, 6) = "BITMAP" AND InCharBlock THEN ' Next lines are bitmap data, handled by the general hex line check below ELSEIF InCharBlock AND NumCharsLoaded <= Font.CharsCount AND LEN(Line) > 0 AND _ (INSTR("0123456789ABCDEF", UCASE$(LEFT$(Line, 1))) > 0) THEN ' This is likely a bitmap data line (hex characters) BitmapRowCounter = BitmapRowCounter + 1 IF BitmapRowCounter <= Font.Glyphs(NumCharsLoaded).BBxHeight THEN Font.Glyphs(NumCharsLoaded).Bitmap(BitmapRowCounter) = Line END IF ELSEIF LEFT$(Line, 7) = "ENDCHAR" THEN InCharBlock = 0 ' False END IF LOOP CLOSE #1 LoadBDF = Font ' Return the populated font structure EXIT FUNCTION BDFError: PRINT "Error loading BDF file: "; FileName PRINT "Error number: "; ERR; " - "; ERROR$ CLOSE #1 ' Ensure file is closed even on error ' Return an empty/default font structure in case of error DIM EmptyFont AS BDFont LoadBDF = EmptyFont RESUME NEXT ' To continue program execution in MainLoop END FUNCTION SUB SaveBDF (Font AS BDFont, FileName AS STRING) ON ERROR GOTO SaveBDFError OPEN FileName FOR OUTPUT AS #1 PRINT #1, "STARTFONT 2.1" PRINT #1, "FONT " + Font.FontName PRINT #1, "SIZE"; Font.PointSize; Font.ResolutionX; Font.ResolutionY PRINT #1, "FONTBOUNDINGBOX"; Font.FontBoundingBoxX; Font.FontBoundingBoxY; Font.FontBoundingBoxXOff; Font.FontBoundingBoxYOff PRINT #1, "CHARS"; Font.CharsCount DIM i AS LONG FOR i = 1 TO Font.CharsCount DIM G AS Glyph G = Font.Glyphs(i) PRINT #1, "STARTCHAR " + G.Name PRINT #1, "ENCODING"; G.Encoding PRINT #1, "BBX"; G.BBxWidth; G.BBxHeight; G.BBxXOff; G.BBxYOff PRINT #1, "DWIDTH"; G.DWidthX; G.DWidthY PRINT #1, "BITMAP" DIM RowNum AS INTEGER FOR RowNum = 1 TO G.BBxHeight IF RowNum <= UBOUND(G.Bitmap) THEN PRINT #1, G.Bitmap(RowNum) ELSE ' If bitmap data is incomplete, pad with "00" PRINT #1, STRING$((G.BBxWidth + 7) \ 8 * 2, "0") ' Calculate hex chars needed for width END IF NEXT RowNum PRINT #1, "ENDCHAR" NEXT i PRINT #1, "ENDFONT" CLOSE #1 LOCATE CSRLIN, 1 PRINT "Font saved to "; FileName; SPACE$(40) ' Clear line EXIT SUB SaveBDFError: LOCATE CSRLIN, 1 PRINT "Error saving BDF file: "; FileName; " - "; ERROR$; SPACE$(40) CLOSE #1 END SUB SUB DrawGlyph (G AS Glyph, XOffset AS INTEGER, YOffset AS INTEGER, Zoom AS INTEGER) IF G.BBxHeight = 0 OR G.BBxWidth = 0 THEN EXIT SUB ' Nothing to draw DIM Row AS INTEGER, Col AS INTEGER DIM HexLine AS STRING DIM BinaryRow AS STRING DIM BitIndex AS INTEGER DIM PixelColor AS LONG FOR Row = 1 TO G.BBxHeight IF Row <= UBOUND(G.Bitmap) THEN HexLine = G.Bitmap(Row) DIM ByteNum AS INTEGER FOR ByteNum = 0 TO (LEN(HexLine) \ 2) - 1 ' Process two hex chars at a time (one byte) DIM CurrentHexPair AS STRING CurrentHexPair = MID$(HexLine, (ByteNum * 2) + 1, 2) BinaryRow = HexToBinaryString(CurrentHexPair, 8) ' Convert 1 byte (8 bits) to binary string FOR BitIndex = 0 TO 7 ' Iterate through the 8 bits of the current byte Col = (ByteNum * 8) + BitIndex ' Calculate global column IF Col < G.BBxWidth THEN ' Only draw up to the glyph's actual width IF MID$(BinaryRow, BitIndex + 1, 1) = "1" THEN PixelColor = PIXEL_ON_COLOR ELSE PixelColor = PIXEL_OFF_COLOR END IF ' Draw the pixel, considering zoom LINE (XOffset + Col * Zoom, YOffset + (Row - 1) * Zoom)-(XOffset + Col * Zoom + Zoom - 1, YOffset + (Row - 1) * Zoom + Zoom - 1), PixelColor, BF END IF NEXT BitIndex NEXT ByteNum END IF NEXT Row END SUB SUB EditPixel (MouseX AS INTEGER, MouseY AS INTEGER, MouseButton AS INTEGER, BYREF G AS Glyph, XOffset AS INTEGER, YOffset AS INTEGER, Zoom AS INTEGER) IF G.BBxHeight = 0 OR G.BBxWidth = 0 THEN EXIT SUB IF MouseButton = 0 THEN EXIT SUB ' No button pressed DIM RelativeX AS INTEGER DIM RelativeY AS INTEGER DIM Row AS INTEGER DIM Col AS INTEGER DIM ByteIndex AS INTEGER DIM BitInByteIndex AS INTEGER ' Bit position within the byte (0-7) DIM CurrentHexByte AS STRING DIM CurrentHexValue AS INTEGER RelativeX = MouseX - XOffset RelativeY = MouseY - YOffset ' Check if click is within the drawing area for this glyph IF RelativeX < 0 OR RelativeX >= G.BBxWidth * Zoom OR _ RelativeY < 0 OR RelativeY >= G.BBxHeight * Zoom THEN EXIT SUB ' Click outside glyph drawing area END IF Col = RelativeX \ Zoom Row = RelativeY \ Zoom + 1 ' BDF rows are 1-indexed for our array IF Row < 1 OR Row > G.BBxHeight OR Col < 0 OR Col >= G.BBxWidth THEN EXIT SUB IF Row > UBOUND(G.Bitmap) THEN EXIT SUB ' Should not happen if BBxHeight matches bitmap array size ' Determine which bit to toggle within the hex data ByteIndex = Col \ 8 ' Which "byte" (pair of hex chars) in the string BitInByteIndex = 7 - (Col MOD 8) ' Position within the 8-bit segment (most significant bit first) ' Get the current hex string for the row CurrentHexByte = G.Bitmap(Row) ' Extract the relevant hex pair (e.g., "FF", "0A") DIM TargetHexPair AS STRING IF (ByteIndex * 2) + 2 <= LEN(CurrentHexByte) THEN ' Ensure string access is valid TargetHexPair = MID$(CurrentHexByte, (ByteIndex * 2) + 1, 2) ELSE TargetHexPair = "00" ' If not enough hex data, assume empty byte END IF CurrentHexValue = VAL("&H" + TargetHexPair) IF MouseButton = -1 THEN ' Left click: set pixel CurrentHexValue = CurrentHexValue OR (2 ^ BitInByteIndex) ELSEIF MouseButton = -2 THEN ' Right click: clear pixel CurrentHexValue = CurrentHexValue AND (NOT (2 ^ BitInByteIndex)) END IF ' Convert back to hex string (format to two digits) DIM NewHexPair AS STRING NewHexPair = HEX$(CurrentHexValue) IF LEN(NewHexPair) = 1 THEN NewHexPair = "0" + NewHexPair ' Pad with leading zero if needed ' Update the bitmap string for the row IF (ByteIndex * 2) + 2 <= LEN(G.Bitmap(Row)) THEN MID$(G.Bitmap(Row), (ByteIndex * 2) + 1, 2) = NewHexPair ELSE ' If the hex string was too short for the current pixel, extend it ' This is a simplified approach; a real editor would ensure the bitmap strings ' are always the correct length for the BBxWidth. G.Bitmap(Row) = G.Bitmap(Row) + STRING$(((ByteIndex * 2) + 2) - LEN(G.Bitmap(Row)), "0") MID$(G.Bitmap(Row), (ByteIndex * 2) + 1, 2) = NewHexPair END IF ' Redraw the specific pixel for immediate feedback DIM PixelColor AS LONG IF (CurrentHexValue AND (2 ^ BitInByteIndex)) THEN PixelColor = PIXEL_ON_COLOR ELSE PixelColor = PIXEL_OFF_COLOR END IF ' Draw directly to screen, not temp image, for responsiveness LINE (XOffset + Col * Zoom, YOffset + (Row - 1) * Zoom)-(XOffset + Col * Zoom + Zoom - 1, YOffset + (Row - 1) * Zoom + Zoom - 1), PixelColor, BF _DISPLAY ' Update just the pixel area for faster drawing END SUB FUNCTION HexToBinaryString (HexByte AS STRING, NumBits AS INTEGER) AS STRING DIM BinaryString AS STRING DIM HexVal AS INTEGER DIM i AS INTEGER HexVal = VAL("&H" + HexByte) ' Convert hex string to integer BinaryString = "" FOR i = NumBits - 1 TO 0 STEP -1 IF (HexVal AND (2 ^ i)) THEN BinaryString = BinaryString + "1" ELSE BinaryString = BinaryString + "0" END IF NEXT i HexToBinaryString = BinaryString END FUNCTION FUNCTION SPLIT (TextString AS STRING, Delimiter AS STRING) AS STRING() DIM Parts() AS STRING DIM Count AS INTEGER DIM StartPos AS INTEGER DIM DelimPos AS INTEGER Count = 0 StartPos = 1 DO DelimPos = INSTR(StartPos, TextString, Delimiter) IF DelimPos = 0 THEN REDIM PRESERVE Parts(0 TO Count) Parts(Count) = MID$(TextString, StartPos) EXIT DO ELSE REDIM PRESERVE Parts(0 TO Count) Parts(Count) = MID$(TextString, StartPos, DelimPos - StartPos) StartPos = DelimPos + LEN(Delimiter) Count = Count + 1 END IF LOOP SPLIT = Parts END FUNCTION How to Use and Expand This Code: Save: Save this entire code block as a .bas file (e.g., fonteditor.bas) in your QB64PE directory. testfont.bdf: Create a simple text file named testfont.bdf in the same directory as your .bas file, and paste the example BDF content for "A" (provided in the MainLoop comments) into it. This will allow the program to load a character initially. Run: Compile and run with QB64PE. Interaction: You'll see a dark grey window with a lighter grey panel on the left and a darker grey drawing area. An 'A' character should appear in the drawing area (if testfont.bdf loaded, or the dummy one was created). Left-click in the white drawing area (where the 'A' is) to turn pixels ON. Right-click to turn pixels OFF. Use the Left/Right Arrow keys to navigate between characters (only one 'A' in the dummy font, so not much to see here yet). Use the Up/Down Arrow keys to change the zoom level. Press 'S' to save the modified font to edited_font.bdf. Press 'ESC' to quit. Further Enhancements (Next Steps for You): Robust BDF Parsing: The current LoadBDF and SaveBDF are highly simplified. Real BDFs have many more properties (e.g., SWIDTH, VPOS, METRICSSET, PROPERTIES), and the bitmap hex data can vary in length. You'll need to accurately parse all relevant sections and properties to build a truly compatible editor. Character Navigation: Implement a more robust character selection mechanism (e.g., a list of characters, search by encoding/name). Drawing Tools: Implement more tools like lines, rectangles, fill, copy/paste, shift/move pixels, invert. Undo/Redo: This is crucial for any editor and would require maintaining a history of bitmap changes. User Interface: Create actual clickable buttons and input fields (using QB64PE's graphics commands and mouse/keyboard input) instead of relying solely on keyboard shortcuts for all actions. You might look into community-made GUI libraries for QB64PE (like InForm-PE mentioned in search results), but that would add another layer of complexity. Error Handling: Improve file I/O error handling and provide more user-friendly messages. New Font/New Character: Add functionality to create new BDF files from scratch or add new characters to an existing font. Performance: For very large fonts or complex operations, optimize drawing routines. Using _SCREENIMAGE and _PUTIMAGE wisely can reduce flicker. X11 Fonts vs. QB64PE Display: Remember, this editor creates/edits BDF files. To use these fonts in an X11 environment, you'd still need to follow the X11 font installation steps (e.g., mkfontdir, xset fp+, xset fp rehash) on your Linux system. QB64PE itself isn't directly interacting with X11 for display. This project is a fantastic way to dive deep into QB64PE's capabilities and low-level programming! Good luck! RE: people using an ai agent, can it translate this TCL app to QB64PE? - madscijr - 07-10-2025 Wow, thanks for giving that a try - I'll see if any of that is usable a little later. The BDF format isn't TOO hard so it shouldn't be that difficult to parse (I got my code working to write the files). Thanks again! |