Subversion Repositories DIN Is Noise

Rev

Rev 2107 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
* font.cc
* DIN Is Noise is copyright (c) 2006-2025 Jagannathan Sampath
* DIN Is Noise is released under GNU Public License 2.0
* For more information, please visit https://dinisnoise.org/
*/



#include <fstream>
#include <sstream>
#include <cmath>
#include "font.h"
#include "log.h"
#include "random.h"
#include "chrono.h"

using namespace std;

extern string user_data_dir;
static const char eol = '\n';

font::font (const string& filename) : mod(0)

#ifdef __SVG_FNT__
  ,svg ((user_data_dir + "text.svg").c_str(), ios::out)
#endif

#ifdef __PLOTTER_FNT__
  ,hpgl ((user_data_dir + "text.hpgl").c_str(), ios::out)
#endif

{
  cellsize.x = 2;
  cellsize.y = 2;
  spacing.ch = 2 * cellsize.x;
  headroom = 2 * cellsize.y;
  fname = filename;
  load (fname);

#ifdef __SVG_FNT__
  svg << "<?xml version=\"1.0\"?>" << eol;
  svg << "<svg xmlns=\"https://www.w3.org/2000/svg\">" << eol;
#endif

#ifdef __PLOTTER_FNT__
  hpgl << "IN;" << eol;
#endif

}

font::~font () {
  if (mod) save ();
#ifdef __SVG_FNT__
  svg << "</svg>" << eol;
#endif
  dlog << "--- destroyed font ---" << endl;
}

void font::load (const string& fn) {
  string ffname (user_data_dir + fname);
  ifstream file (ffname.c_str(), ios::in);
  if (file) {
    dlog << "<<< loading font from: " << ffname;
    load (file);
    dlog << ", done. >>>" << eol;
  } else {
    dlog << "!!! bad font file: " << ffname << " !!!" << endl;
  }
}

void font::load (ifstream& file) {

  string ignore;

  characters.clear ();
  kern.clear ();

  file >> ignore >> name;
  file >> ignore >> ignore; // copyright
  file >> ignore >> nchars;

  MAKETYPE (sumt, int, width, height)
  sumt sum;

  charwidth.max = charheight.max = 0;
  sum.width = sum.height = 0;

  for (int i = 0; i < nchars; i++) {
      char c; file >> c;
      glyph g;
      file >> g.nln;
      for (int l = 0; l < g.nln; ++l) {
        line ln;
        file >> ln.npts;
        for (int s = 0; s < ln.npts; ++s) {
          int x, y; file >> x >> y;
          ln.points.push_back (point<int>(x * cellsize.x, y * cellsize.y));
        }
        g.lines.push_back (ln);
      }

      g.find_width_height ();
      if (g.width > charwidth.max) charwidth.max = g.width;
      if (g.height > charheight.max) charheight.max = g.height;
      sum.width += g.width;
      sum.height += g.height;

      characters[c] = g;

  }

  charwidth.avg = (int) (sum.width * 1.0f / nchars + 0.5);
  charheight.avg = (int)(sum.height * 1.0f / nchars + 0.5);

  lift = 2 * cellsize.y;

  spacing.word = 2 * spacing.ch;

  calc_line_height ();

  int nkerns = 0;
  file >> ignore >> nkerns;
  for (int i = 0; i < nkerns; ++i) {
    char a, b;
    int k;
    file >> a >> b >> k;
    kern [a][b] = k * cellsize.x;
  }

}

void font::calc_line_height () {
  extern int line_height;
  line_height = fnt.charheight.max + fnt.headroom;
}

void font::save () {

  ofstream file ((user_data_dir + fname).c_str(), ios::out);
  if (!file) { dlog << "cannot save font in: " << fname << eol; return;} else { dlog << "saving font in: " << fname << eol;}

  // build chars
  int nc = 0;
  stringstream ss1;
  for (map<char, glyph>::iterator i = characters.begin(), j = characters.end(); i != j; ++i) {
    glyph& g = (*i).second;
    vector<line>& lines = g.lines; // poly lines
    int nlines = lines.size ();
    if (nlines) {
      ss1 << (*i).first << ' ';
      ss1 << nlines << ' ';
      for (int s = 0; s < nlines; ++s) {
        vector < point<int> >& points = lines[s].points;
        int npts = points.size ();
        ss1 << npts << ' ';
        for (int m = 0; m < npts; ++m) {
          point<int>& pt = points[m];
          ss1 << (int) (pt.x / cellsize.x) << ' ' << (int) (pt.y / cellsize.y) << ' ';
        }
      }
      ss1 << eol;
      ++nc;
    }
  }

  // write chars
  file << "name " << name << eol;
  file << "copyright jagannathan_sampath_(c)_2006-2024_all_rights_reserved." << eol;
  file << "num_chars " << nc << eol;
  file << ss1.str();

  // build kerns
  int nk = 0;
  stringstream ss2;
  for (map<char, map<char, int> >::iterator i = kern.begin(), j = kern.end(); i != j; ++i) {
    char a = (*i).first;
    map<char, int>& m = (*i).second;
    for (map<char, int>::iterator p = m.begin(), q = m.end(); p != q; ++p) {
      char b = (*p).first;
      int k = (*p).second;
      if (k != 0) {
        ss2 << a << ' ' << b << ' ' << k/cellsize.x << '\n';
        ++nk;
      }
    }
  }

  // write kerns
  file << "num_kerns " << nk << eol;
  file << ss2.str();

}

int font::char_width (char c) { return characters[c].width;}
int font::char_height (char c) { return characters[c].height;}

void font::draw_char (char c, int x, int y, int z) {
  static const int MAX_LINE_POINTS = 32; // cant exceed or crash!
  static int pts [3 * MAX_LINE_POINTS] = {0};
  glyph& gl = characters[c];
  vector<line>& lines = gl.lines;
  glVertexPointer (3, GL_INT, 0, pts);
  for (int i = 0; i < gl.nln; ++i) {
    line& li = lines[i];
    vector< point<int> >& points = li.points;
    int q = 0, npts = li.npts;
    for (int j = 0; j < npts; ++j) {
      const point<int>& p = points[j];
      pts[q++] = x + p.x;
      pts[q++] = y + p.y;
      pts[q++] = z;
    }
    glDrawArrays (GL_LINE_STRIP, 0, npts);
  }
}

int draw_string (const string& s, int x, int y, int z) {
  char prevc = ' ';
  for (int p = 0, q = s.length(); p < q; ++p) {
      char c = s[p];
      if (c == ' ') x += fnt.spacing.word; else {
        x += fnt.kern[prevc][c];
        fnt.draw_char (c, x, y, z);
        x += (fnt.char_width (c) + fnt.spacing.ch);
      }
      prevc = c;
  }
  return x;
}

const map<char, glyph>& font::get_chars () {
  return characters;
}

void font::set_chars (const map<char, glyph>& chars) {
  characters = chars;
  mod = 1;
}

void get_char_width_height (const string& s, int& x, int& y) {
  x = 0; y = 0;
  int q = s.length ();
  if (q) {
    char prevc = ' ';
    for (int p = 0; p < q; ++p) {
      char c = s[p];
      if (c == ' ')
        x += fnt.spacing.word;
      else
        x = x + fnt.kern[prevc][c] + fnt.char_width (c) + fnt.spacing.ch;
      int ch = fnt.char_height (c);
      if (ch > y) y = ch;
      prevc = c;
    }
  }
}

int get_char_width (const string& s) {
  int x = 0;
  int q = s.length ();
  if (q) {
    char prevc = ' ';
    for (int p = 0; p < q; ++p) {
      char c = s[p];
      if (c == ' ')
        x += fnt.spacing.word;
      else
        x = x + fnt.kern[prevc][c] + fnt.char_width (c) + fnt.spacing.ch;
      prevc = c;
    }
  }
  return x;
}

int get_char_height (const string& s) {
  int h = 0;
  for (int p = 0, q = s.length(); p < q; ++p) {
    char c = s[p];
    int ch = fnt.char_height (c);
    if (ch > h) h = ch;
  }
  return h;
}

#ifdef __PLOTTER_FNT__

void font::plot_char (char c, int x, int y, int z) {
  const glyph& gl = characters[c];
  const vector<line>& lines = gl.lines;
  for (unsigned int i = 0, nlines = lines.size(); i < nlines; ++i) {
    const line& li = lines[i];
    const vector< point<int> >& points = li.points;
    unsigned int npts = points.size();
    const point<int>& p0 = points[0];
    hpgl << "PU " << (x + p0.x) << ',' << (y + p0.y) << ';' << eol;
    hpgl << "PD;" << eol;
    for (unsigned int j = 0; j < npts; ++j) {
      const point<int>& p = points[j];
      int xp = x + p.x, yp = y + p.y;
      hpgl << "PA " << xp << ',' << yp << ';' << eol;
    }
  }
}

int plot_string (const string& s, int x, int y, int z) {
  char prevc = ' ';
  for (int p = 0, q = s.length(); p < q; ++p) {
      char c = s[p];
      if (c == ' ') x += fnt.spacing.word; else {
        x += fnt.kern[prevc][c];
        fnt.plot_char (c, x, y, z);
        x += (fnt.char_width (c) + fnt.spacing.ch);
      }
      prevc = c;
  }
  return x;
}
#endif

#ifdef __SVG_FNT__
void font::write_char (char c, int x, int y, int z) {
  const glyph& gl = characters[c];
  const vector<line>& lines = gl.lines;
  for (unsigned int i = 0, nlines = lines.size(); i < nlines; ++i) {
    const line& li = lines[i];
    const vector< point<int> >& points = li.points;
    unsigned int npts = points.size();
    svg << "<g>" << eol;
    svg << "<polyline fill=\"none\" stroke=\"black\" points=\"";
      for (unsigned int j = 0; j < npts; ++j) {
        const point<int>& p = points[j];
        glVertex3i(x + p.x, y + p.y, z);
        int xp = x + p.x, yp = y - p.y;
        svg << xp << "," << yp << " ";
      }
    svg << "\"/>" << eol;
    svg << "</g>" << eol;
  }
}

int write_string (const string& s, int x, int y, int z) {
  char prevc = ' ';
  for (int p = 0, q = s.length(); p < q; ++p) {
    char c = s[p];
    if (c == ' ') x += fnt.spacing.word; else {
      x += fnt.kern[prevc][c];
      fnt.write_char (c, x, y, z);
      x += (fnt.char_width (c) + fnt.spacing.ch);
    }
    prevc = c;
  }
  return x;
}
#endif