diff --git a/fonts/BOLD.CHR b/fonts/BOLD.CHR new file mode 100644 index 0000000..8af2bc2 Binary files /dev/null and b/fonts/BOLD.CHR differ diff --git a/fonts/EURO.CHR b/fonts/EURO.CHR new file mode 100644 index 0000000..bb1f9ec Binary files /dev/null and b/fonts/EURO.CHR differ diff --git a/fonts/GOTH.CHR b/fonts/GOTH.CHR new file mode 100644 index 0000000..75f9a7e Binary files /dev/null and b/fonts/GOTH.CHR differ diff --git a/fonts/LCOM.CHR b/fonts/LCOM.CHR new file mode 100644 index 0000000..0916763 Binary files /dev/null and b/fonts/LCOM.CHR differ diff --git a/fonts/LITT.CHR b/fonts/LITT.CHR new file mode 100644 index 0000000..08c3067 Binary files /dev/null and b/fonts/LITT.CHR differ diff --git a/fonts/SCRI.CHR b/fonts/SCRI.CHR new file mode 100644 index 0000000..92fb8a9 Binary files /dev/null and b/fonts/SCRI.CHR differ diff --git a/fonts/SIMP.CHR b/fonts/SIMP.CHR new file mode 100644 index 0000000..00ee8d7 Binary files /dev/null and b/fonts/SIMP.CHR differ diff --git a/fonts/TSCR.CHR b/fonts/TSCR.CHR new file mode 100644 index 0000000..31900c9 Binary files /dev/null and b/fonts/TSCR.CHR differ diff --git a/include/bgifnt.bm b/include/bgifnt.bm new file mode 100644 index 0000000..905f3b9 --- /dev/null +++ b/include/bgifnt.bm @@ -0,0 +1,238 @@ + +FUNCTION LoadBGIFont (filename AS STRING) + LoadBGIFont = __BGI_Internal(1, 0, 0, 0, 0, filename, 0) +END FUNCTION + +SUB UnloadBGIFont (fontHandle AS INTEGER) + DIM discard AS LONG + discard = __BGI_Internal(2, fontHandle, 0, 0, 0, "", 0) +END SUB + +SUB DisplayBGIText (fontHandle AS INTEGER, startX AS SINGLE, startY AS SINGLE, text AS STRING, fontSize AS SINGLE, col AS LONG) + DIM discard AS LONG + discard = __BGI_Internal(3, fontHandle, startX, startY, fontSize, text, col) +END SUB + +FUNCTION __BGI_Internal (mode AS INTEGER, handle AS INTEGER, arg1 AS SINGLE, arg2 AS SINGLE, arg3 AS SINGLE, textData AS STRING, col AS LONG) + DIM filename AS STRING + DIM fNum AS INTEGER + DIM slot AS INTEGER + DIM i AS LONG + DIM c AS INTEGER + DIM d AS STRING + DIM header_size_pos AS LONG + DIM header_size AS INTEGER + DIM stroke_header_start AS LONG + DIM strokes_offset AS INTEGER + DIM asc_temp AS INTEGER + DIM base_temp AS INTEGER + DIM desc_temp AS INTEGER + DIM char_offsets_pos AS LONG + DIM widths_pos AS LONG + + DIM startX AS SINGLE + DIM startY AS SINGLE + DIM fontSize AS SINGLE + DIM text AS STRING + DIM font_height AS INTEGER + DIM scale AS SINGLE + DIM x AS SINGLE + DIM ch AS INTEGER + DIM stroke_pos AS LONG + DIM cur_x AS SINGLE + DIM cur_y AS SINGLE + DIM byte1 AS INTEGER + DIM byte2 AS INTEGER + DIM op1 AS INTEGER + DIM op2 AS INTEGER + DIM m AS INTEGER + DIM dx_raw AS INTEGER + DIM dx AS INTEGER + DIM dy_raw AS INTEGER + DIM dy AS INTEGER + DIM new_x AS SINGLE + DIM new_y AS SINGLE + + + STATIC init AS INTEGER + STATIC __bgi_fontdata() AS STRING + STATIC __bgi_metrics() AS LONG + + CONST MAX_FONTS = 10 + CONST M_INUSE = 1 + CONST M_FIRSTCHAR = 2 + CONST M_NUMCHARS = 3 + CONST M_ASCENDER = 4 + CONST M_BASELINE = 5 + CONST M_DESCENDER = 6 + CONST M_STROKESSTART = 7 + CONST METRICS_SIZE = 521 + + IF NOT init THEN + REDIM __bgi_fontdata(1 TO MAX_FONTS) AS STRING + REDIM __bgi_metrics(1 TO MAX_FONTS, 1 TO METRICS_SIZE) AS LONG + init = -1 + END IF + + SELECT CASE mode + CASE 1 ' --- LOAD FONT --- + filename = textData + IF _FILEEXISTS(filename) = 0 THEN + PRINT "File not found: "; filename + __BGI_Internal = 0 + EXIT FUNCTION + END IF + slot = 0 + FOR i = 1 TO MAX_FONTS + IF __bgi_metrics(i, M_INUSE) = 0 THEN + slot = i + EXIT FOR + END IF + NEXT i + + IF slot = 0 THEN + PRINT "BGI Font Error: Maximum loaded font limit reached." + __BGI_Internal = 0 + EXIT FUNCTION + END IF + + ' Extract stream data safely + fNum = FREEFILE + OPEN filename FOR BINARY AS #fNum + __bgi_fontdata(slot) = SPACE$(LOF(fNum)) + GET #fNum, , __bgi_fontdata(slot) + CLOSE #fNum + + d = __bgi_fontdata(slot) + + ' Validate signature header identity + IF MID$(d, 1, 2) <> "PK" OR ASC(MID$(d, 3, 1)) <> 8 OR ASC(MID$(d, 4, 1)) <> 8 OR MID$(d, 5, 4) <> "BGI " THEN + PRINT "Not a valid Borland BGI .CHR font file: "; filename + __bgi_fontdata(slot) = "" + __BGI_Internal = 0 + EXIT FUNCTION + END IF + + ' Track string end parsing marker + i = 9 + WHILE i <= LEN(d) AND ASC(MID$(d, i, 1)) <> 26 + i = i + 1 + WEND + IF i > LEN(d) THEN + PRINT "Invalid font format: Missing description terminator." + __bgi_fontdata(slot) = "" + __BGI_Internal = 0 + EXIT FUNCTION + END IF + + header_size_pos = i + 1 + header_size = CVI(MID$(d, header_size_pos, 2)) + stroke_header_start = header_size + 1 + + IF ASC(MID$(d, stroke_header_start, 1)) <> ASC("+") THEN + PRINT "Not a stroked font." + __bgi_fontdata(slot) = "" + __BGI_Internal = 0 + EXIT FUNCTION + END IF + + __bgi_metrics(slot, M_NUMCHARS) = CVI(MID$(d, stroke_header_start + 1, 2)) + __bgi_metrics(slot, M_FIRSTCHAR) = ASC(MID$(d, stroke_header_start + 4, 1)) + strokes_offset = CVI(MID$(d, stroke_header_start + 5, 2)) + + ' Signed Int8 layout mappings + asc_temp = ASC(MID$(d, stroke_header_start + 8, 1)) + IF asc_temp > 127 THEN __bgi_metrics(slot, M_ASCENDER) = asc_temp - 256 ELSE __bgi_metrics(slot, M_ASCENDER) = asc_temp + base_temp = ASC(MID$(d, stroke_header_start + 9, 1)) + IF base_temp > 127 THEN __bgi_metrics(slot, M_BASELINE) = base_temp - 256 ELSE __bgi_metrics(slot, M_BASELINE) = base_temp + desc_temp = ASC(MID$(d, stroke_header_start + 10, 1)) + IF desc_temp > 127 THEN __bgi_metrics(slot, M_DESCENDER) = desc_temp - 256 ELSE __bgi_metrics(slot, M_DESCENDER) = desc_temp + + ' Assign vector stroke references + char_offsets_pos = stroke_header_start + 16 + FOR c = 0 TO __bgi_metrics(slot, M_NUMCHARS) - 1 + __bgi_metrics(slot, 8 + __bgi_metrics(slot, M_FIRSTCHAR) + c) = CVI(MID$(d, char_offsets_pos + c * 2, 2)) + NEXT c + + ' Map character layout metrics width boundaries + widths_pos = char_offsets_pos + __bgi_metrics(slot, M_NUMCHARS) * 2 + FOR c = 0 TO __bgi_metrics(slot, M_NUMCHARS) - 1 + __bgi_metrics(slot, 265 + __bgi_metrics(slot, M_FIRSTCHAR) + c) = ASC(MID$(d, widths_pos + c, 1)) + NEXT c + + __bgi_metrics(slot, M_STROKESSTART) = stroke_header_start + strokes_offset + __bgi_metrics(slot, M_INUSE) = -1 + + __BGI_Internal = slot + + CASE 2 ' --- UNLOAD FONT --- + IF handle >= 1 AND handle <= MAX_FONTS THEN + __bgi_metrics(handle, M_INUSE) = 0 + __bgi_fontdata(handle) = "" + END IF + + CASE 3 ' --- DISPLAY TEXT --- + IF handle < 1 OR handle > MAX_FONTS THEN EXIT FUNCTION + IF __bgi_metrics(handle, M_INUSE) = 0 THEN EXIT FUNCTION + + startX = arg1 + startY = arg2 + fontSize = arg3 + text = textData + + font_height = __bgi_metrics(handle, M_ASCENDER) - __bgi_metrics(handle, M_DESCENDER) + IF font_height = 0 THEN font_height = 1 + + scale = fontSize / font_height + x = startX + + FOR ch = 1 TO LEN(text) + c = ASC(MID$(text, ch, 1)) + + IF c < __bgi_metrics(handle, M_FIRSTCHAR) OR c >= __bgi_metrics(handle, M_FIRSTCHAR) + __bgi_metrics(handle, M_NUMCHARS) OR __bgi_metrics(handle, 8 + c) = 0 THEN + x = x + 8 * scale + _CONTINUE + END IF + + stroke_pos = __bgi_metrics(handle, M_STROKESSTART) + __bgi_metrics(handle, 8 + c) + cur_x = x + cur_y = startY + + DO + byte1 = ASC(MID$(__bgi_fontdata(handle), stroke_pos, 1)) + byte2 = ASC(MID$(__bgi_fontdata(handle), stroke_pos + 1, 1)) + stroke_pos = stroke_pos + 2 + + op1 = (byte1 AND 128) \ 128 + op2 = (byte2 AND 128) \ 128 + m = op1 + op1 + op2 + + IF m = 0 THEN EXIT DO + IF m = 1 THEN _CONTINUE + + dx_raw = byte1 AND 127 + dx = dx_raw + IF dx > 63 THEN dx = dx - 128 + + dy_raw = byte2 AND 127 + dy = dy_raw + IF dy > 63 THEN dy = dy - 128 + dy = -dy + + new_x = x + dx * scale + new_y = startY + dy * scale + + IF m = 2 THEN + cur_x = new_x + cur_y = new_y + ELSEIF m = 3 THEN + LINE (cur_x, cur_y)-(new_x, new_y), col + cur_x = new_x + cur_y = new_y + END IF + LOOP + + x = x + __bgi_metrics(handle, 265 + c) * scale + NEXT ch + END SELECT +END FUNCTION diff --git a/pixler.bas b/pixler.bas index 3fed931..0c15dff 100644 --- a/pixler.bas +++ b/pixler.bas @@ -64,6 +64,22 @@ state.bcolor=closestcolor(_rgb32(255,255,255),pal()) addcommand"fcolor ("+hex$(state.fcolor)+")" addcommand"bcolor ("+hex$(state.bcolor)+")" +DIM SHARED fontHandles(1 TO 10) AS INTEGER +DIM SHARED activeFontIndex AS INTEGER: activeFontIndex = 1 +DIM SHARED activeFontSize AS SINGLE: activeFontSize = 16 +DIM SHARED textToolString AS STRING + +fontHandles(1) = LoadBGIFont("./fonts/SIMP.CHR") +fontHandles(2) = LoadBGIFont("./fonts/BOLD.CHR") +fontHandles(3) = LoadBGIFont("./fonts/EURO.CHR") +fontHandles(4) = LoadBGIFont("./fonts/GOTH.CHR") +fontHandles(5) = LoadBGIFont("./fonts/LCOM.CHR") +fontHandles(6) = LoadBGIFont("./fonts/LITT.CHR") +fontHandles(7) = LoadBGIFont("./fonts/SANS.CHR") +fontHandles(8) = LoadBGIFont("./fonts/SCRI.CHR") +fontHandles(9) = LoadBGIFont("./fonts/TRIP.CHR") +fontHandles(10) = LoadBGIFont("./fonts/TSCR.CHR") + dim lastmx,lastmy dim keyin as string dim mouseworldy as long @@ -117,6 +133,7 @@ do end if ' Keyboarding + if not state.tool=16 and not state.isdrawing then keyin=inkey$ select case keyin case chr$(27) @@ -165,12 +182,13 @@ do redraw end if end select - + end if + canvas if showtoolbox then toolbox if showcolorpicker then colorpicker if showcommands then commandlist - + if state.tool = 12 then drawTextToolPanel _limit 30 _display loop @@ -198,6 +216,76 @@ sub commandlist if button(x,_height-25,60,23,"redraw") then redraw end sub +sub drawTextToolPanel + dim panelWidth as integer: panelWidth = 160 + dim x as integer: x = _width - panelWidth + static showFontList as integer ' Tracks the expanding drop-down list state + + ' Draw the side panel background card block + line (x, 0)-(_width - 1, _height - 1), backgroundcolor1, bf + line (x, 0)-(x, _height - 1), backgroundcolor2 + + _printmode _keepbackground + _printstring (x + 10, 15), "TEXT TOOL OPTIONS" + line (x + 10, 30)-(x + 140, 30), backgroundcolor2 + + ' --- Font Size Controls --- + _printstring (x + 10, 45), "Size: " + tst(int(activeFontSize)) + if button(x + 85, 42, 24, 20, "-") then + if activeFontSize > 4 then activeFontSize = activeFontSize - 2 + end if + if button(x + 115, 42, 24, 20, "+") then + if activeFontSize < 120 then activeFontSize = activeFontSize + 2 + end if + + ' --- Expandable Font Selection Links --- + dim currentFontName as string + select case activeFontIndex + case 1: currentFontName = "SIMP.CHR" + case 2: currentFontName = "BOLD.CHR" + case 3: currentFontName = "EURO.CHR" + case 4: currentFontName = "GOTH.CHR" + case 5: currentFontName = "LCOM.CHR" + case 6: currentFontName = "LITT.CHR" + case 7: currentFontName = "SANS.CHR" + case 8: currentFontName = "SCRI.CHR" + case 9: currentFontName = "TRIP.CHR" + case 10: currentFontName = "TSCR.CHR" + end select + + _printstring (x + 10, 80), "Font: " + if link(x + 55, 80, "[" + currentFontName + " ]") then + showFontList = not showFontList ' Toggle expansion list visibility + end if + + ' Render the drop-down links when expanded + if showFontList then + dim fontNames(1 to 10) as string + fontNames(1) = "Simplex": fontNames(2) = "Bold": fontNames(3) = "Euro" + fontNames(4) = "Gothic": fontNames(5) = "Complex": fontNames(6) = "Little" + fontNames(7) = "Sans": fontNames(8) = "Script": fontNames(9) = "Triplex" + fontNames(10) = "TScript" + + dim ly as integer, idx as integer + for idx = 1 to 10 + ly = 80 + (idx * 20) + ' Highlight the currently active font selection with an asterisk + dim itemPrefix as string + if idx = activeFontIndex then itemPrefix = "* " else itemPrefix = " " + + if link(x + 20, ly, itemPrefix + fontNames(idx)) then + activeFontIndex = idx + showFontList = 0 ' Auto-collapse list upon selection + end if + next idx + end if + + ' Guard the UI boundaries so clicks on this panel do not draw on the canvas below it + if _mousex >= x then + if _mousebutton(1) or _mousebutton(2) then mouseclicked = 0: rmouseclicked = 0 + end if +end sub + sub redraw redim numarr(0) as long dim i as integer @@ -257,6 +345,32 @@ sub redraw boundaryfill numarr(0),numarr(1),numarr(2),numarr(3) case "gradient" ditheredgradient numarr(0),numarr(1),numarr(2),numarr(3),state.fcolor,state.bcolor + case "gradient" + ditheredgradient numarr(0),numarr(1),numarr(2),numarr(3),state.fcolor,state.bcolor + + case "text" + ' Extract the string parameter from the command structure manually + ' e.g., text (X, Y, FontHandleIndex, FontSize, FColor, Your String Content) + dim txtStart as integer: txtStart = instr(commands(i), ",") + ' Advance past the first 5 commas to isolate the text string + dim commaCount as integer: commaCount = 0 + dim searchPos as integer: searchPos = 1 + while commaCount < 5 + searchPos = instr(searchPos, commands(i), ",") + if searchPos > 0 then + commaCount = commaCount + 1 + searchPos = searchPos + 1 + else + exit while + end if + wend + if commaCount = 5 then + dim textMsg as string + textMsg = mid$(commands(i), searchPos, instr(searchPos, commands(i), ")") - searchPos) + ' Draw text to the drawing layer directly + DisplayBGIText fontHandles(numarr(2)), numarr(0), numarr(1), textMsg, numarr(3), numarr(4) + end if + case "" ' blank line do nothing case else @@ -401,64 +515,64 @@ sub canvas end if next _dest layers(2).ihandle:cls,0:_dest 0 - ' 2.5 if the mouse is in ui thats all we need - if showtoolbox then - if _mousex>=0 and _mousex<=70 then - exit sub + ' 2.5 Check if the mouse pointer is hitting the UI boundaries + dim mouseInUI as _byte + mouseInUI = 0 + + if showtoolbox and (_mousex>=0 and _mousex<=70) then mouseInUI = -1 + if showcolorpicker and (_mousey>=_height-20) then mouseInUI = -1 + if showcommands and (_mousex>=drawx2) then mouseInUI = -1 + + ' 3. Calculate Canvas Coordinates (Center-aligned to the zoom block) + dim canx as long + dim cany as long + + canx=int((_mousex-state.offsetx+(state.zoom \ 2))/state.zoom) + cany=int((_mousey-state.offsety+(state.zoom \ 2))/state.zoom) + + static drawcol + if _mousebutton(1) then drawcol=state.fcolor + if _mousebutton(2) then drawcol=state.bcolor + + ' ONLY initiate drawing actions if the mouse is NOT in the UI + if mouseInUI = 0 then + if (mousedown or rmousedown) and state.isdrawing=0 then + state.startx=canx + state.starty=cany + state.isdrawing=-1 + end if end if - end if - if showcolorpicker then - if _mousey>=_height-20 then - exit sub + + ' Bypass tool execution for regular click-and-drag shapes, + ' but ALWAYS allow text tool (12) to pass through so it draws the live overlay + if mouseInUI = 0 or state.tool = 12 then + select case state.tool + case 1 + do.pencil canx,cany,drawcol + case 2 + do.line state.startx,state.starty,canx,cany,drawcol + case 3 + do.circle state.startx,state.starty,canx,cany,drawcol + case 4 + do.fcircle state.startx,state.starty,canx,cany,drawcol + case 5 + do.box state.startx,state.starty,canx,cany,drawcol + case 6 + do.fbox state.startx,state.starty,canx,cany,drawcol + case 7 + do.polygon canx,cany + case 8 + do.fpolygon canx,cany + case 9 + do.floodfill canx,cany,drawcol + case 10 + do.eyedropper canx,cany + case 11 + do.gradient state.startx,state.starty,canx,cany + case 12 + do.text canx, cany, drawcol + end select end if - end if - if showcommands then - if _mousex>=drawx2 then - exit sub - end if - end if - - ' 3. Calculate Canvas Coordinates (Center-aligned to the zoom block) - dim canx as long - dim cany as long - - canx=int((_mousex-state.offsetx+(state.zoom \ 2))/state.zoom) - cany=int((_mousey-state.offsety+(state.zoom \ 2))/state.zoom) - - static drawcol - if _mousebutton(1) then drawcol=state.fcolor - if _mousebutton(2) then drawcol=state.bcolor - - if (mousedown or rmousedown) and state.isdrawing=0 then - state.startx=canx - state.starty=cany - state.isdrawing=-1 - end if - - select case state.tool - case 1 - do.pencil canx,cany,drawcol - case 2 - do.line state.startx,state.starty,canx,cany,drawcol - case 3 - do.circle state.startx,state.starty,canx,cany,drawcol - case 4 - do.fcircle state.startx,state.starty,canx,cany,drawcol - case 5 - do.box state.startx,state.starty,canx,cany,drawcol - case 6 - do.fbox state.startx,state.starty,canx,cany,drawcol - case 7 - do.polygon canx,cany - case 8 - do.fpolygon canx,cany - case 9 - do.floodfill canx,cany,drawcol - case 10 - do.eyedropper canx,cany - case 11 - do.gradient state.startx,state.starty,canx,cany - end select end sub sub do.pencil(x as long,y as long,col as long) @@ -836,6 +950,21 @@ sub do.gradient(sx as long,sy as long,ex as long,ey as long) if state.isdrawing then osource=_source odest=_dest + + if _keydown(100303) or _keydown(100304) then + dim dx as single:dx=ex-sx + dim dy as single:dy=ey-sy + if dx<>0 or dy<>0 then + dim linelen as single:linelen=sqr(dx*dx+dy*dy) + dim angle as single:angle=_atan2(dy,dx) + dim degrees as single:degrees=angle*(180.0/_pi) + dim snappeddegrees as single:snappeddegrees=int((degrees+22.5)/45.0)*45.0 + dim snappedangle as single:snappedangle=snappeddegrees*(_pi/180.0) + ex=sx+_round(linelen*cos(snappedangle)) + ey=sy+_round(linelen*sin(snappedangle)) + end if + end if + if mouseclicked or rmouseclicked then _dest layers(1).ihandle addcommand"gradient ("+tst(sx)+","+tst(sy)+","+tst(ex)+","+tst(ey)+")" @@ -850,6 +979,54 @@ sub do.gradient(sx as long,sy as long,ex as long,ey as long) end if end sub +sub do.text (x as long, y as long, col as long) + dim osource as long + dim odest as long + + ' 1. Mouse Click on Canvas initiates the Typing Focus Lock + if (mouseclicked) and state.isdrawing = 0 then + state.startx = x + state.starty = y + state.isdrawing = -1 + textToolString = "" ' Clear typing input buffer + _keyclear ' Flush background buffer lines + end if + + if state.isdrawing then + osource = _source + odest = _dest + ' 2. Intercept keyboard streams directly inside the active lock state + dim k as long + do + k = _keyhit + if k = 0 then exit do + ' Character keys range check + if k >= 32 and k <= 126 then + textToolString = textToolString + chr$(k) + elseif k = 8 and len(textToolString) > 0 then ' Backspace behavior + textToolString = left$(textToolString, len(textToolString) - 1) + elseif k = 13 then + _dest layers(1).ihandle + DisplayBGIText fontHandles(activeFontIndex), state.startx, state.starty, textToolString, activeFontSize, col + addcommand "text (" + tst(state.startx) + "," + tst(state.starty) + "," + tst(activeFontIndex) + "," + tst(int(activeFontSize)) + "," + hex$(col) + "," + textToolString + ")" + state.isdrawing = 0 + _dest odest + _source osource + while inkey$<>"":wend + exit sub + elseif k = 27 then + state.isdrawing = 0 + exit sub + end if + loop + ' 3. Render dynamic typing string overlay to active live preview layer (layer 2) + _dest layers(2).ihandle + DisplayBGIText fontHandles(activeFontIndex), state.startx, state.starty, textToolString + "_", activeFontSize, col + _source osource + _dest odest + end if +end sub + function icon (index as long) static init as integer static icons() as long @@ -927,9 +1104,18 @@ function icon (index as long) line (22,10)-(26,6),c ' Squeeze bulb cap _dest 0 + icons(10)=_newimage(32,32,32):_dest icons(10) + ditheredgradient 6, 6, 26, 26, highlightcolor, backgroundcolor1 + + icons(11) = _NEWIMAGE(32, 32, 32): _DEST icons(11) + line (8, 8)-(24, 8), c + line (16, 8)-(16, 24), c + line (12, 24)-(20, 24), c + _DEST 0 + ' Fill remaining fallback slots (10-19) with clean blank images dim j as integer - for j=10 to 19 + for j=11 to 19 if icons(j)=0 then icons(j)=_newimage(32,32,32) next @@ -947,6 +1133,7 @@ end function '$include: 'include/imgout.bm' '$include: 'include/palette.bm' '$include: 'include/tools.bm' +'$include: 'include/bgifnt.bm' ''$include: 'include/effects.bm' function adduiicon(imagehandle as long)