{
    This file is part of Chentrah,
    Copyright (C) 2004-2008 Anton Rzheshevski (chebmaster@mail.ru).

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see http://www.gnu.org/licenses/

 **********************************************************************

    This file contains the built-in fixed font handling routines.

 **********************************************************************}

  {$include cl_builtin_font.inc} // the imbedded low-res font texture (1-bit  .png)

  const
    MIN_FACE_TO_USE = 2;
//    ffMipThreshold = 0.3; //manual "mipmap" selection based on the pixel to texel ratio

  var
    fftAlreadyLoaded: boolean = false;

  const
    ffcs: array[0..17] of WideString = (
      'АаБбВвГгДдЕеЁёЖж',
      'ЗзИиЙйКкЛлМмНнОо',
      'ПпРрСсТтУуФфХхЦц',
      'ЧчШшЩщЬьЫыЪъЭэЮю',
      'Яя!"#$%&''()*+,-.',
      '/0123456789:;<=>',
      '?@ABCDEFGHIJKLMN',
      'OPQRSTUVWXYZ[\]^',
      '_`abcdefghijklmn',
      'opqrstuvwxyz{|}~',
      '‘’‚“”„…‰«»§®°–—‘',
      'ƒˆŠŒŽ•˜™šœžŸ¡¢£¤',
      '¥¦§©ª¬°±²³´µ¶·¹ÿ',
      '¼½¾¿ÀÁÂ∞ÃÄÅÆÇÈÉÊ',
      'ËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚ',
      'ÛÜÝÞßàáâãäåæçèéê',
      'ëìíîïðñòóôõö÷øùú',
      'û√üýþ™№∂∆∏∑♯≈≠≤≥');
(*
    sfcs: array[0..13] of WideString = (
      ' !"#$%&''()*+,-./',
      '0123456789:;<=>?',
      '@ABCDEFGHIJKLMNO',
      'PQRSTUVWXYZ[\]^_',
      '`abcdefghijklmno',
      'pqrstuvwxyz{|}~', //Do NOT edit! Lazarus may not render the aposthroph at the end of this line -- but it is there!
      'ЂЃ‚ѓ„…†‡ ‰Љ‹ЊЌЋЏ', //no Euro symbol. I drew that raster font ages ago, before there was Euro.
      'ђ‘’“”•–— ™љ›њќћџ',
      ' ЎўЈ¤Ґ¦§Ё©Є«¬­®Ї', //Do NOT edit! Lazarus displays this line with quirks because it has a strange symbol between ¬ and @!
      '°±Ііґµ¶·ё№є»јЅѕї',
      'АБВГДЕЖЗИЙКЛМНОП',
      'РСТУФХЦЧШЩЪЫЬЭЮЯ',
      'абвгдежзийклмноп',
      'рстуфхцчшщъыьэюя');
*)
    ffMaxChar = 287;
    ffMaxFace = 16;

  type
    ffEnum = (ffSize, ffLeft, ffTop, ffGridWidth, ffGridHeight,
                     ffGlyphWidth, ffGlyphHeight, ffHspacing, ffSsize, ffTsize);
    TffSet = array [ffEnum] of integer;
    Pffset = ^Tffset;

  const
    ffm_builtin: Tffset =
      (13, 14, 9,  14,  24,  10,  22,  10,  256,  512);
    ffm_normal: Tffset =
      (31, 22, 7,  36,  55,  24,  54,  18, 1024, 1024);
    ffzoomx: array[0..1] of float = (1.0, 0.85);
  var
    ffm: array[0..1] of PffSet = (@ffm_normal, @ffm_builtin);


  var
    //                              s     t     0hq, 1shitty
    fftexcoord: array[0..ffMaxChar, 0..1, 0..1, 0..1] of GLfloat;
    ffmap: array [WideChar] of integer;

    FixedFontTexture: array[0..1] of GLint;
    
    //  FixedFontTexture: array[0..1, 0..1] of GLint; //horz and vert MIP maps

  // the font image itself (1024x1024) is split into two 4-bit png files,
  //   this gives the best compression without any visual quality loss
  //   (more than 2x compared to the joint gray+alpha png)

  procedure LoadFixedFontTexture;
  var
    luma, alpha, combi: TImageData;
    x, y, mx, my, cx, cy: integer;
    mtz: GLsizei;
    lumaname, alphaname, filename: string;
//t: integer;
  begin
    if fftAlreadyLoaded then exit;
    try
//t:=GetTickCount;
      glGetIntegerv(GL_MAX_TEXTURE_SIZE, @mtz);

      FillChar(luma, sizeof(TImageData), 0);
      FillChar(alpha, sizeof(TImageData), 0);

      if (MotherState.InstallPath='') or (MotherState.HomePath='')
        or (mtz < 2048) //usually indicates that there's no support for the luminance+alpha format
        or not FileExists(MotherState.InstallPath + ImagesDir + 'fixed_luma.png')
        or not FileExists(MotherState.InstallPath + ImagesDir + 'fixed_alpha.png')
      then begin
        //the installation path not found,
        // OR it's the GDI render which doesn't support the GL_LUMINANCE format
        MotherState.FixedFontEmergencyDownsample:= true;
        MotherState.FixedFontQuality:= 1;
        if MotherState.VerboseLog then AddLog('Using the built-in low-res font texture: install path'#10#13'  not found or OpenGL doesn''t support the GL_LUMINANCE_ALPHA format.');
      end;

      //the shitty built-in 256x512 w/o shadow
      glGenTextures(1, @FixedFontTexture[1]);
      glBindTexture(GL_TEXTURE_2D, FixedFontTexture[1]);
      glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
      LoadImageFromMemory(@builtin_font, sizeof(builtin_font), luma);
      if MotherState.FixedFontEmergencyDownsample and MotherState.VerboseLog
        then AddLog('Using the built-in %0x%1 font texture: install path'#10#13+
                    '  not found or OpenGL doesn''t support GL_LUMINANCE_ALPHA.', [luma.width, luma.height]);
      ConvertImage(luma, ifGray8);
      glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, luma.width, luma.height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, luma.Bits);
      FreeImage(luma);

      //the quality texture (Liberation Mono Bold) w/shadow
      if not MotherState.FixedFontEmergencyDownsample then begin
        lumaname:= MotherState.InstallPath + ImagesDir + 'fixed_luma.png';
        alphaname:= MotherState.InstallPath + ImagesDir + 'fixed_alpha.png';
        glGenTextures(1, @FixedFontTexture[0]);
        glBindTexture(GL_TEXTURE_2D, FixedFontTexture[0]);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 4);
        filename:= MotherState.CacheDir + 'fixedfont.png';
        if FileExists(filename)
        then
          LoadImageFromFile(filename, combi)
        else begin
          LoadImageFromFile(lumaname, luma);
          if (luma.width <> 1024) or (luma.height <> 1024)
            then Die('%0 is not a 1024x1024.', [lumaname]);
          ConvertImage(luma, ifGray8);

          LoadImageFromFile(alphaname, alpha);
          if (alpha.width <> 1024) or (alpha.height <> 1024)
            then Die('%0 is not a 1024x1024.', [alphaname]);
          ConvertImage(alpha, ifGray8);

          NewImage(1024, 1024, ifA8Gray8, combi);

          // combine the separately packed luma and alpha into one A8G8 image:
          for y:=0 to 1023 do
            for x:=0 to 1023 do begin
              pbyte(combi.bits + y*2048 + x*2)^:= pbyte(luma.bits + x + y*1024)^;
              pbyte(combi.bits + y*2048 + x*2 + 1)^:= pbyte(alpha.bits + x + y*1024)^;
            end;
          if MotherState.CacheDir <> ''
            then SaveImageToFile(filename, combi);
        end;
        glTexImage2D (GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, 1024, 1024, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, combi.bits);
        FreeImage(combi);
        FreeImage(alpha);
        FreeImage(luma);
      end;
//AddLog('---------- %0', [GetTickCount - t]);
    except
      Die(MI_ERROR_PROGRAMMER_NO_BAKA, ['failed to load the built-in fixed font']);
    end;
    fftAlreadyLoaded:= true;
  end;



  procedure InitFixedFont();
  var
    w: WideChar;
    x, y, z, f, rf: integer;
  begin
    for w:= low(WideChar) to high(WideChar) do ffmap[w]:= 11*16 + 5;
      // fill the entire map with the index of  "•", sixt of the twelfth row.

    for y:=0 to 17 do
      for x:=0 to 15 do
        ffmap[ffcs[y][x + 1]]:= x + y*16;
        
    LoadFixedFontTexture;

    for z:=0 to 1 do
      for y:=0 to 17 do
        for x:=0 to 15 do begin
          //from top left clockwise.
          fftexcoord[x + y*16, 0, 0, z]:= (ffm[z]^[ffLeft] + ffm[z]^[ffGridWidth] * x) / ffm[z]^[ffSsize];
          fftexcoord[x + y*16, 0, 1, z]:= (ffm[z]^[ffTop] + ffm[z]^[ffGridHeight] * y) / ffm[z]^[ffTsize];

          fftexcoord[x + y*16, 1, 0, z]:= fftexcoord[x + y*16, 0, 0, z] + ffm[z]^[ffGlyphWidth] / ffm[z]^[ffSsize];
          fftexcoord[x + y*16, 1, 1, z]:= fftexcoord[x + y*16, 0, 1, z] + ffm[z]^[ffGlyphHeight] / ffm[z]^[ffTsize];
        end;
  end;


  procedure _cgeffSizeToFace(size: integer; face: Pinteger; zoom: PGLfloat );
  begin
    zoom^:= size / ffm[0]^[ffSize];
  end;


  function _cgeffGetStringWidth(Size, numChars: integer): GLfloat;
  var
    face: integer;
    zoom: GLfloat;
  begin
    _cgeffSizeToFace(Size, @face, @zoom);
    Result:=max(1, numChars) * round(zoom * ffm[0]^[ffHspacing]);
  end;
  
  function _cgeffGetStringHeight(Size, numLines: integer): GLfloat;
  var
    face: integer;
    zoom: GLfloat;
  begin
    _cgeffSizeToFace(max(1, Size), @face, @zoom);
    Result:= max(1, numLines) * round(zoom * ffm[0]^[ffGlyphHeight]);
  end;
  


  procedure _cgeffFitStringIntoRectangle
                  (Size, numChars, numLines, Width, Height: integer;
                                                  fit: PStringFitRec);
  var
    w, h: float;//integer;
  begin
    Width:=max(1, Width);
    Height:=max(1, Height);
    numChars:=max(1, numChars);
    numLines:=max(1, numLines);
    
    _cgeffSizeToFace(Size, @fit^.face, @fit^.zoomX);
    fit^.zoomY:= fit^.zoomX;
    w:={round}(fit^.zoomX * ffm[0]^[ffHspacing]);
    h:={round}(fit^.zoomY * ffm[0]^[ffGlyphHeight]);
    if (w * numChars <= Width) and (h * numLines <= Height ) then begin
      fit^.ActualWidth:= round(w * numChars);
      fit^.ActualHeight:= round(h * numLines);
    end
    else begin
//    if face^ = ffMaxFace then begin
      //zoom it down until it fits or zoom falls below 19/40
      if w * numChars > Width then begin
        w:= Width / {div} numChars;
        fit^.zoomX:= w / ffm[0]^[ffHspacing];
      end;
      if h * numLines > Height then begin
        h:= Height / {div} numLines;
        fit^.zoomY:= h / ffm[0]^[ffGlyphHeight];
      end;

      fit^.ActualWidth:= round(numChars * {round}(fit^.zoomX * ffm[0]^[ffHSpacing]));
      fit^.ActualHeight:= round(numLines * {round}(fit^.zoomY * ffm[0]^[ffGlyphHeight]));
    end;
  end;


  var
    prevMipX: integer = 0;
    prevMipY: integer = 0;

  procedure _cgeffSetRenderState (noshadow: boolean);
  begin
    noshadow:= noshadow or (MotherState.FixedFontQuality = 1);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, FixedFontTexture[MotherState.FixedFontQuality]);
    glEnable(GL_BLEND);
    glDisable(GL_ALPHA_TEST);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    if noshadow
      then glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR)
      else glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
  end;
  
  procedure _cgeffRenderString (fit: PStringFitRec; Left, Top: glFloat; w: PWideChar);
  var
    cn, i: integer;
    sw, gw, gh: float;//integer;
  begin
    if not Assigned(w) then exit;
    
    gh:={round}(ffm[0]^[ffGlyphHeight] * fit^.zoomY);
    gw:={round}(ffm[0]^[ffGlyphWidth] * fit^.zoomX) * ffzoomx[MotherState.FixedFontQuality];
    sw:={round}(ffm[0]^[ffHspacing] * fit^.zoomX);
    
    glBindTexture(GL_TEXTURE_2D, FixedFontTexture[MotherState.FixedFontQuality]);

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 4);

    glBegin(GL_QUADS);
    
    while w^ <> #0 do begin
      if w^ <> ' ' then begin //no rendering spaces!
        cn:= ffmap[w^];

        glTexCoord2f(fftexcoord[cn, 0, 0, MotherState.FixedFontQuality], fftexcoord[cn, 0, 1, MotherState.FixedFontQuality]);
        glVertex2f(Left, Top);

        glTexCoord2f(fftexcoord[cn, 1, 0, MotherState.FixedFontQuality], fftexcoord[cn, 0, 1, MotherState.FixedFontQuality]);
        glVertex2f(Left + gw, Top);

        glTexCoord2f(fftexcoord[cn, 1, 0, MotherState.FixedFontQuality], fftexcoord[cn, 1, 1, MotherState.FixedFontQuality]);
        glVertex2f(Left + gw, Top + gh);

        glTexCoord2f(fftexcoord[cn, 0, 0, MotherState.FixedFontQuality], fftexcoord[cn, 1, 1, MotherState.FixedFontQuality]);
        glVertex2f(Left, Top + gh);
      end;
      Left += sw;
      inc(w);
    end;

    glEnd();
  end;



// *************** EXPORTED VERSIONS ******************************************
  procedure cgeffSizeToFace(size: integer; face: Pinteger; zoom: PGLfloat ); cdecl;
  begin
    try
      _cgeffSizeToFace(size, face, zoom);
    except
      ProcessGuardedException();
    end;
  end;

  procedure cgeffFitStringIntoRectangle
      (Size, numChars, numLines, Width, Height: integer; fit: PStringFitRec); cdecl;
  begin
    try
      _cgeffFitStringIntoRectangle(Size, numChars, numLines, Width, Height, fit);
    except
      ProcessGuardedException();
    end;
  end;


  function cgeffGetStringWidth(Size, numChars: integer): GLfloat; cdecl;
  begin
    try
      _cgeffGetStringWidth(Size, numChars);
    except
      ProcessGuardedException();
    end;
  end;

  function cgeffGetStringHeight(Size, numLines: integer): GLfloat; cdecl;
  begin
    try
      _cgeffGetStringHeight(Size, numLines);
    except
      ProcessGuardedException();
    end;
  end;

  procedure cgeffSetRenderState (noshadow: boolean); cdecl;
  begin
    try
      _cgeffSetRenderState (noshadow);
    except
      ProcessGuardedException();
    end;
  end;

  procedure cgeffRenderString (fit: PStringFitRec; Left, Top: glFloat; w: PWideChar); cdecl;
  begin
    try
      _cgeffRenderString (fit, Left, Top, w);
    except
      ProcessGuardedException();
    end;
  end;

