
(********************************************************************)
(*                                                                  *)
(*  pixmapfont.s7i  Defines pixmapFontType and the font cache.      *)
(*  Copyright (C) 2010  Thomas Mertes                               *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library 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 Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


const type: pixmapHashType is hash [char] PRIMITIVE_WINDOW;

const type: pixmapFontType is new struct
    var pixmapHashType: pixmap is pixmapHashType.value;
    var font: baseFont is emptyFont.value;
    var integer: fontSize is 0;
    var integer: scale is 1;
    var color: foreground is white;
    var color: background is black;
    var integer: baseLineDelta is 0;
    var integer: line_delta is 0;
    var integer: column_delta is 0;
    var integer: characterSpacing is 0;
  end struct;


const func pixmapFontType: genPixmapFont (in font: aFont,
    in integer: fontSize, in integer: scale,
    in color: foreground, in color: background)                     is DYNAMIC;
const func PRIMITIVE_WINDOW: getFontCharPixmap (in font: aFont,
    inout pixmapFontType: pixmapFont, in char: ch)                  is DYNAMIC;


#
# Font cache
#


const type: fontKeyType is new struct
    var font: baseFont is emptyFont.value;
    var integer: fontSize is 0;
    var integer: scale is 1;
    var color: foreground is white;
    var color: background is black;
  end struct;

const func integer: hashCode (in fontKeyType: fontKey) is
  return hashCode(fontKey.baseFont) + hashCode(fontKey.fontSize) + hashCode(fontKey.scale) +
      hashCode(fontKey.foreground) + hashCode(fontKey.background);

const func integer: compare (in fontKeyType: fontKey1, in fontKeyType: fontKey2) is func
  result
    var integer: result is 0;
  begin
    result := compare(fontKey1.baseFont, fontKey2.baseFont);
    if result = 0 then
      result := compare(fontKey1.fontSize, fontKey2.fontSize);
      if result = 0 then
        result := compare(fontKey1.scale, fontKey2.scale);
        if result = 0 then
          result := compare(fontKey1.foreground, fontKey2.foreground);
          if result = 0 then
            result := compare(fontKey1.background, fontKey2.background);
          end if;
        end if;
      end if;
    end if;
  end func;

const type: fontCacheType is hash [fontKeyType] pixmapFontType;

var fontCacheType: fontCache is fontCacheType.value;


const func pixmapFontType: genPixmapFont (in fontProperties: properties,
    in integer: fontSize, in integer: scale,
    in color: foreground, in color: background) is func
  result
    var pixmapFontType: result is pixmapFontType.value;
  local
    var char: ch is ' ';
  begin
    incl(result.pixmap, ' ', PRIMITIVE_WINDOW.value);
    result.baseFont := properties;
    result.fontSize := fontSize;
    result.scale := scale;
    result.foreground := foreground;
    result.background := background;
    result.baseLineDelta := baseLineDelta(properties) * scale;
    result.line_delta := lineHeight(properties) * scale;
    result.column_delta := columnWidth(properties) * scale;
    result.characterSpacing := characterSpacing(properties) * scale;
  end func;


const func pixmapFontType: getFont (in font: baseFont, in integer: fontSize,
    in integer: scale, in color: foreground, in color: background) is func
  result
    var pixmapFontType: result is pixmapFontType.value;
  local
    var fontKeyType: fontKey is fontKeyType.value;
  begin
    # writeln("getFont(bmpFont, " <& fontSize <& ", " <& scale <& ")");
    fontKey.baseFont := baseFont;
    fontKey.fontSize := fontSize;
    fontKey.scale := scale;
    fontKey.foreground := foreground;
    fontKey.background := background;
    if fontKey in fontCache then
      result := fontCache[fontKey];
    else
      result := genPixmapFont(baseFont, fontSize, scale, foreground, background);
      fontCache @:= [fontKey] result;
    end if;
  end func;


const func PRIMITIVE_WINDOW: getFontCharPixmap (
    inout pixmapFontType: pixmapFont, in char: ch) is
  return getFontCharPixmap(pixmapFont.baseFont, pixmapFont, ch);


#
# pixmapFontFile
#


const type: pixmapFontFile is sub null_file struct
    var PRIMITIVE_WINDOW: win is PRIMITIVE_WINDOW.value;
    var pixmapFontType: font is pixmapFontType.value;
    var integer: height is 0;
    var integer: width is 0;
    var integer: line is 0;
    var integer: column is 0;
    var integer: min_x is 0;
    var integer: min_y is 0;
    var integer: curr_x is 0;
    var integer: curr_y is 0;
    var color: foreground is white;
    var color: background is black;
  end struct;


type_implements_interface(pixmapFontFile, text);


const func pixmapFontFile: malloc (ref pixmapFontFile: ext_file) is func
  result
    var pixmapFontFile: result is pixmapFontFile.value;
  begin
    result := ext_file;
  end func;


const func text: openBitmapFontFile (in PRIMITIVE_WINDOW: win) is func
  result
    var text: result is STD_NULL;
  local
    var pixmapFontFile: new_file is pixmapFontFile.value;
  begin
    new_file.win := win;
    new_file.font.baseFont := fontProperties.value;
    result := malloc(new_file);
  end func;


const func text: openBitmapFontFile (in PRIMITIVE_WINDOW: win,
    in integer: min_x, in integer: min_y) is func
  result
    var text: result is STD_NULL;
  local
    var pixmapFontFile: new_file is pixmapFontFile.value;
  begin
    new_file.win := win;
    new_file.font.baseFont := fontProperties.value;
    new_file.min_x := min_x;
    new_file.min_y := min_y;
    result := malloc(new_file);
  end func;


const proc: setFont (inout text param, in font param) is DYNAMIC;


const proc: cursor (ref pixmapFontFile: fontFile, ref boolean: active) is noop;
const func integer: height (in pixmapFontFile: fontFile) is return fontFile.height;
const func integer: width (in pixmapFontFile: fontFile) is return fontFile.width;


const proc: clear (in pixmapFontFile: fontFile,
    in integer: upper, in integer: left, in integer: lower, in integer: right) is func
  begin
    rectTo(curr_win,
        fontFile.min_x + fontFile.font.column_delta * pred(left),
        fontFile.min_y + fontFile.font.line_delta * pred(upper),
        fontFile.min_x + pred(fontFile.font.column_delta * right),
        fontFile.min_y + pred(fontFile.font.line_delta * lower),
        fontFile.background);
  end func;


const proc: setPos (inout pixmapFontFile: fontFile,
    in integer: line, in integer: column) is func
  begin
    fontFile.curr_y := fontFile.min_y + fontFile.font.line_delta * line;
    fontFile.curr_x := fontFile.min_x + fontFile.font.column_delta * column;
  end func;


const proc: setPosXY (inout pixmapFontFile: fontFile, in integer: xPos, in integer: yPos) is func
  begin
    fontFile.curr_x := fontFile.min_x + xPos;
    fontFile.curr_y := fontFile.min_y + yPos;
    fontFile.line := (fontFile.curr_y + 2) div fontFile.font.line_delta;
    fontFile.column := (fontFile.curr_x + 6) div fontFile.font.column_delta;
  end func;


const proc: color (inout pixmapFontFile: fontFile, in color: col) is func
  begin
    fontFile.font := getFont(fontFile.font.baseFont, fontFile.font.fontSize,
        fontFile.font.scale, col, fontFile.font.background);
  end func;


const proc: color (inout pixmapFontFile: fontFile, in color: col, in color: backgr) is func
  begin
    fontFile.font := getFont(fontFile.font.baseFont, fontFile.font.fontSize,
        fontFile.font.scale, col, backgr);
  end func;


const proc: scale (inout text param, in integer: scale) is DYNAMIC;


const proc: scale (inout pixmapFontFile: fontFile, in integer: scale) is func
  begin
    fontFile.font := getFont(fontFile.font.baseFont, fontFile.font.fontSize,
        scale, fontFile.font.foreground, fontFile.font.background);
  end func;


const proc: write (inout pixmapFontFile: fontFile, in string: stri) is func
  local
    var char: ch is ' ';
    var PRIMITIVE_WINDOW: charPixmap is PRIMITIVE_WINDOW.value;
  begin
    for ch range stri do
      if ch in fontFile.font.pixmap then
        charPixmap := fontFile.font.pixmap[ch];
      else
        charPixmap := getFontCharPixmap(fontFile.font, ch);
      end if;
      put(fontFile.curr_x, fontFile.curr_y - fontFile.font.baseLineDelta, charPixmap, PSET);
      fontFile.curr_x +:= width(charPixmap);
      if fontFile.font.characterSpacing <> 0 then
        rect(fontFile.curr_x, fontFile.curr_y - fontFile.font.baseLineDelta,
            fontFile.font.characterSpacing, height(charPixmap), fontFile.font.background);
        fontFile.curr_x +:= fontFile.font.characterSpacing;
      end if;
    end for;
    fontFile.column +:= length(stri);
  end func;


const proc: write (inout pixmapFontFile: fontFile, in char: ch) is func
  begin
    write(fontFile, str(ch));
  end func;


const proc: writeln (inout pixmapFontFile: fontFile) is func
  begin
    incr(fontFile.line);
    fontFile.column := 1;
    fontFile.curr_x := fontFile.min_x + fontFile.font.column_delta - 6;
    fontFile.curr_y +:= fontFile.font.line_delta;
  end func;
