/* * Copyright (c) 2024 Ross Cunniff * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "libio" #include "libglut" #include "libgl1" const TXF_FORMAT_BYTE = 0; const TXF_FORMAT_BITMAP = 1; // Forward declaration of TxfGlyph class class TxfGlyph; public char; class TexFont { protected var tex_width; protected var tex_height; protected var max_ascent; protected var max_descent; protected var min_glyph, max_glyph; protected var glyphs; protected var image; protected var vertices; protected var texCoords; var texInit = false; public proc draw() { using namespace gl1; if (!texInit) { TexImage2D(TEXTURE_2D, 0, LUMINANCE, tex_width, tex_height, 0, LUMINANCE, UNSIGNED_BYTE, image); TexEnvi(TEXTURE_2D, TEXTURE_ENV_MODE, REPLACE); TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, LINEAR); TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, LINEAR); TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, REPEAT); TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, REPEAT); texInit = true; } Enable(TEXTURE_2D); VertexPointer(3, FLOAT, 0, vertices); TexCoordPointer(2, FLOAT, 0, texCoords); EnableClientState( VERTEX_ARRAY ); EnableClientState( TEXTURE_COORD_ARRAY ); DrawArrays(TRIANGLE_FAN, 0, 4); } public proc create(filename) { var f; f = new File(filename, "rb"); if( f == nil ) { "Error: can't open ", filename, "\n"; } try { var s; // Read the magic number s = f.read(PackUbyte[4]); if( !(s #= [0xFF,'t','x','f']).reduce(`&) ) { throw("bad magic "); } // Read the byte swap indicator switch( f.read(Uint) ) { case 0x12345678U : f.setswab(false); case 0x78563412U : f.setswab(true); default : throw("bad byte order"); } // Read the header var fmt; var num_glyphs; var a; a = f.read(PackInt[6]); fmt = a[0]; tex_width = a[1]; tex_height = a[2]; max_ascent = a[3]; max_descent = a[4]; num_glyphs = a[5]; glyphs = new Array(num_glyphs); // Read the glyphs min_glyph = 0x3FFFFFFF; max_glyph = -1; for( var i = 0; i < num_glyphs; i++ ) { glyphs[i] = new TxfGlyph(f, self) {}; if( glyphs[i].char < min_glyph ) { min_glyph = glyphs[i].char; } if( glyphs[i].char > max_glyph ) { max_glyph = glyphs[i].char; } } // Sort the glyphs var lut; lut = new Array(max_glyph - min_glyph + 1); for( var i = 0; i < num_glyphs; i++ ) { lut[glyphs[i].char - min_glyph] = glyphs[i]; } glyphs = lut; // Read the texture switch( fmt ) { case TXF_FORMAT_BYTE : image = f.read(PackChar[tex_width*tex_height]); case TXF_FORMAT_BITMAP : var byte_width; image = new PackChar(tex_width * tex_height); byte_width = (tex_width + 7) / 8; for( var i = 0; i < tex_height; i++ ) { s = f.read(PackUbyte[byte_width]); for( var j = 0; j < tex_width; j++ ) { image[i*tex_width + j] = s[j/8] & (1<<(j%8)) ? 255 : 0; } } default : throw("bad texture format"); } } catch (msg, fil, lin) { "Error: ", msg, ' ', fil, ' ', lin, '\n'; } f.close(); vertices = oadl::perm([-1.,-1,0, 1,-1,0, 1,1,0, -1,1,0]); texCoords = oadl::perm([0.,0, 1,0, 1,1, 0,1]); } } class TxfGlyph { protected var vertices; protected var texCoords; protected var char; public proc draw() { gl1::VertexPointer(3, gl1::FLOAT, 0, vertices); gl1::TexCoordPointer(2, gl1::FLOAT, 0, texCoords); gl1::EnableClientState( gl1::VERTEX_ARRAY ); gl1::EnableClientState( gl1::TEXTURE_COORD_ARRAY ); gl1::DrawArrays(gl1::TRIANGLE_FAN, 0, 4); } public proc create(f, font) { var width; var height; var xoffset; var yoffset; var advance; var posX; var posY; var a; char = f.read(Ushort); a = f.read(PackUbyte[6]); width = a[0]; height = a[1]; xoffset = a[2]; yoffset = a[3]; advance = a[4]; posX = f.read(Ushort); posY = f.read(Ushort); vertices = oadl::perm(new PackFloat(3*4)); texCoords = oadl::perm(new PackFloat(2*4)); var tw, th, xstep, ystep; tw = Float(font.tex_width); th = Float(font.tex_height); xstep = 0.5 / tw; ystep = 0.5 / th; texCoords[ 0] = posX / tw + xstep; texCoords[ 1] = posY / th + ystep; vertices [ 0] = Float(xoffset); vertices [ 1] = Float(yoffset); vertices [ 2] = 0.; texCoords[2+0] = (posX + width) / tw + xstep; texCoords[2+1] = posY / th + ystep; vertices [3+0] = Float(xoffset + width); vertices [3+1] = Float(yoffset); vertices [3+2] = 0.; texCoords[4+0] = (posX + width) / tw + xstep; texCoords[4+1] = (posY + height) / th + ystep; vertices [6+0] = Float(xoffset + width); vertices [6+1] = Float(yoffset + height); vertices [6+1] = 0.; texCoords[6+0] = posX / tw + xstep; texCoords[6+1] = (posY + height) / th + ystep; vertices [9+0] = Float(xoffset); vertices [9+1] = Float(yoffset + height); vertices [9+2] = 0.; } } var txf, txfName; proc winshape(w, h) { var x = 0, y = 0; if (w < h) { y = (h - w) / 2; h = w; } else { x = (w - h) / 2; w = h; } gl1::Viewport(x, y, w, h); } proc display() { if (!txf) { txf = new TexFont(txfName); } gl1::ClearColor(0., 0., 0., 1.); gl1::Clear(gl1::COLOR_BUFFER_BIT|gl1::DEPTH_BUFFER_BIT); txf.draw(); glut::SwapBuffers(); glut::PostRedisplay(); } proc main(args) { txfName = args[1]; glut::Init(args); glut::InitDisplayMode(glut::RGBA | glut::DOUBLE | glut::DEPTH); glut::CreateWindow("DEMO\0"); glut::DisplayFunc(display); glut::ReshapeFunc(winshape); glut::MainLoop(); }