(***********************************************************************)
(*                                                                     *)
(*                 MLTk, Tcl/Tk interface of Objective Caml            *)
(*                                                                     *)
(*    Francois Rouaix, Francois Pessaux, Jun Furuse and Pierre Weis    *)
(*               projet Cristal, INRIA Rocquencourt                    *)
(*            Jacques Garrigue, Kyoto University RIMS                  *)
(*                                                                     *)
(*  Copyright 2002 Institut National de Recherche en Informatique et   *)
(*  en Automatique and Kyoto University.  All rights reserved.         *)
(*  This file is distributed under the terms of the GNU Library        *)
(*  General Public License, with the special exception on linking      *)
(*  described in file LICENSE found in the Objective Caml source tree. *)
(*                                                                     *)
(***********************************************************************)
(* find font information *)

let debug = ref false
let log s =
  if !debug then try prerr_endline s with _ -> ()

type ('s, 'i) xlfd = {
    (* some of them are currently not interesting for me *)
    mutable foundry: 's;
    mutable family: 's;
    mutable weight: 's;
    mutable slant: 's;
    mutable setWidth: 's;
    mutable addStyle: 's;
    mutable pixelSize: 'i;
    mutable pointSize: 'i;
    mutable resolutionX: 'i;
    mutable resolutionY: 'i;
    mutable spacing: 's;
    mutable averageWidth: 'i;
    mutable registry: 's;
    mutable encoding: 's
  }

let copy xlfd = {xlfd with foundry= xlfd.foundry}

let string_of_xlfd s i xlfd =
  let foundry= s xlfd.foundry
  and family= s xlfd.family
  and weight= s xlfd.weight
  and slant= s xlfd.slant
  and setWidth = s xlfd.setWidth
  and addStyle = s xlfd.addStyle
  and pixelSize= i xlfd.pixelSize
  and pointSize = i xlfd.pointSize
  and resolutionX = i xlfd.resolutionX
  and resolutionY = i xlfd.resolutionY
  and spacing= s xlfd.spacing
  and averageWidth = i xlfd.averageWidth
  and registry= s xlfd.registry
  and encoding = s xlfd.encoding in

  "-"^foundry^
  "-"^family^
  "-"^weight^
  "-"^slant^
  "-"^setWidth ^
  "-"^addStyle ^
  "-"^pixelSize^
  "-"^pointSize ^
  "-"^resolutionX ^
  "-"^resolutionY ^
  "-"^spacing^
  "-"^averageWidth ^
  "-"^registry^
  "-"^encoding

exception Parse_Xlfd_Failure of string

let parse_xlfd xlfd_string =
  (* this must not be a pattern *)
  let split_str char_sep str =
    let len = String.length str in
    let rec split beg cur =
      if cur >= len then [String.sub str beg (len - beg)]
      else if char_sep (String.get str cur)
      then
        let nextw = succ cur in
        (String.sub str beg (cur - beg))
        ::(split nextw nextw)
      else split beg (succ cur) in
    split 0 0
  in
   match split_str (function '-' -> true | _ -> false) xlfd_string with
   | [ _; foundry; family; weight; slant; setWidth; addStyle; pixelSize;
       pointSize; resolutionX; resolutionY; spacing; averageWidth;
       registry; encoding ] ->
       { foundry= foundry;
         family= family;
         weight= weight;
         slant= slant;
         setWidth= setWidth;
         addStyle= addStyle;
         pixelSize= int_of_string pixelSize;
         pointSize= int_of_string pointSize;
         resolutionX= int_of_string resolutionX;
         resolutionY= int_of_string resolutionY;
         spacing= spacing;
         averageWidth= int_of_string averageWidth;
         registry= registry;
         encoding= encoding;
       }
   | _ -> raise (Parse_Xlfd_Failure xlfd_string)

type valid_xlfd = (string, int) xlfd

let string_of_valid_xlfd = string_of_xlfd (fun x -> x) string_of_int

type pattern = (string option, int option) xlfd

let empty_pattern =
  { foundry= None;
    family= None;
    weight= None;
    slant= None;
    setWidth= None;
    addStyle= None;
    pixelSize= None;
    pointSize= None;
    resolutionX= None;
    resolutionY= None;
    spacing= None;
    averageWidth= None;
    registry= None;
    encoding= None;
  }

let string_of_pattern =
  let pat f = function
      Some x -> f x
    | None -> "*"
  in
  let pat_string = pat (fun x -> x) in
  let pat_int = pat string_of_int in
  string_of_xlfd pat_string pat_int

let is_vector_font xlfd =
  (xlfd.pixelSize = 0 && xlfd.resolutionX = 0 && xlfd.resolutionY = 0) ||
  xlfd.spacing <> "c"

let list_fonts dispname pattern =
  let dispopt = match dispname with
    None -> ""
  | Some x -> "-display " ^ x
  in
  let result = List.map parse_xlfd
      (Shell.subshell ("xlsfonts "^dispopt^" -fn "^string_of_pattern pattern))
  in
  if result = [] then raise Not_found
  else result

let available_pixel_size_aux dispname pattern =
  (* return available pixel size without font resizing *)
  (* to obtain good result, *)
  (* the pattern should contain as many information as possible *)
  let pattern = copy pattern in
  pattern.pixelSize <- None;
  let xlfds = list_fonts dispname pattern in
  let pxszs = Hashtbl.create 107 in
  List.iter (fun xlfd ->
    Hashtbl.add pxszs xlfd.pixelSize xlfd) xlfds;
  pxszs

let extract_size_font_hash tbl =
  let keys = ref [] in
  Hashtbl.iter (fun k _ ->
    if not (List.mem k !keys) then keys := k :: !keys) tbl;
  Sort.list (fun (k1,_) (k2,_) -> k1 < k2)
    (List.map (fun k -> k, Hashtbl.find_all tbl k) !keys)

let available_pixel_size dispname pattern =
  let pxszs = available_pixel_size_aux dispname pattern in
  extract_size_font_hash pxszs

let nearest_pixel_size dispname vector_ok pattern =
  (* find the font with the nearest pixel size *)
  log ("\n*** "^string_of_pattern pattern);
  let pxlsz =
    match pattern.pixelSize with
      None -> raise (Failure "invalid pixelSize pattern")
    | Some x -> x
  in
  let tbl = available_pixel_size_aux dispname pattern in
  let newtbl = Hashtbl.create 107 in
  Hashtbl.iter (fun s xlfd ->
    if vector_ok then
      if s = 0 then begin
        if is_vector_font xlfd then begin
          log (Printf.sprintf "%s is vector" (string_of_valid_xlfd xlfd));
          xlfd.pixelSize <- pxlsz;
          Hashtbl.add newtbl pxlsz xlfd
        end
      end else Hashtbl.add newtbl s xlfd
    else if not (is_vector_font xlfd) && s <> 0 then
      Hashtbl.add newtbl s xlfd) tbl;

  let size_font_table = extract_size_font_hash newtbl in

  let diff = ref 10000 in
  let min = ref None in
  List.iter (fun (s,xlfds) ->
    let d = abs(s - pxlsz) in
    if d < !diff then begin
      min := Some (s,xlfds);
      diff := d
    end) size_font_table;
  (* if it contains more than one font, just return the first *)
  match !min with
  | None -> raise Not_found
  | Some(s, xlfds) ->
     log (Printf.sprintf "Size %d is selected" s);
     List.iter (fun xlfd -> log (string_of_valid_xlfd xlfd)) xlfds;
     List.hd xlfds
