/*
* 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();
}