%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
%%% Purpose : The font model
%% ###########################################################################
%%
%% This module handle fonts. It was changed for Tcl 8.2 but it could
%% probably be simplified more.
%%
%% In Tcl 8.2 we can use named fonts. So the whe get a font request we
%% first check if it already exists and if not we name it and insert it
%% into the database.
%%
%% The font naming is also changedin Tcl 8.2.
%%
%% In Tcl 8.2 there is a way to find out the width of a string in
%% a specified font.
%%
%% ###########################################################################
-module(gstk_font).
%-compile(export_all).
-export([init/0,choose_ascii/2,choose/2,width_height/3]).
-ifndef(NEW_WIDTH_HEIGHT).
init() ->
%% hack. the only way to find the size of a text seems to be to put
%% it into a label in an unmappen window (DummyFontWindow)
gstk:exec("toplevel .dfw;wm withdraw .dfw;" %deiconify
"label .dfw.l -text dummyinittxt -padx 0 -pady 0 -borderwidth 0;"
"pack .dfw.l").
-else.
init() -> true.
-endif.
%%----------------------------------------------------------------------
%% Returns: undefined if font doesn't exist
%% {WidthPixels, HeightPixels}
%%----------------------------------------------------------------------
-ifndef(NEW_WIDTH_HEIGHT).
width_height(_DB, FontSpec, Txt) ->
FontSpecStr = tk_font_spec(norm_font_spec(FontSpec)),
case gstk:call([".dfw.l co -font {", FontSpecStr,"}",
" -text ", gstk:to_ascii(Txt)]) of
{result, _} ->
Width = tcl2erl:ret_int("update idletasks;winfo w .dfw.l"),
Height = tcl2erl:ret_int("winfo h .dfw.l"),
% io:format("width_height(~p,~p) =>\n~p\n\n",[FontSpec,Txt,{Width,Height}]),
{Width,Height};
_Bad_Result ->
% io:format("width_height(~p,~p) =>\nundefined\n\n",[FontSpec,Txt]),
undefined
end.
-else.
%% This code should work but does't. Tk gives incorrect
%% values if asking to fast or something /kent
width_height(DB, FontSpec, Txt) when tuple(FontSpec) ->
NormFontSpec = norm_font_spec(FontSpec),
FontSpecStr = tk_font_spec(NormFontSpec),
{Family,_,Size} = NormFontSpec,
LineHeight =
case cached_line_height(DB, {Family,Size}) of
undefined ->
LineH = tcl2erl:ret_int(
["font metrics {",FontSpecStr,"} -linespace"]),
cache_line_height(DB, {Family,Size}, LineH),
LineH;
LineH ->
LineH
end,
EscapedText = gstk:to_ascii(Txt),
Width = tcl2erl:ret_int(
["font measure {",FontSpecStr,"} ",EscapedText]),
Height = LineHeight * line_count(Txt),
{Width,Height};
width_height(_DB, FontSpec, Txt) when list(FontSpec) ->
EscapedText = gstk:to_ascii(Txt),
Width =
tcl2erl:ret_int(["font measure {",FontSpec,"} ",EscapedText]),
LineHeight =
tcl2erl:ret_int(["font metrics {",FontSpec,"} -linespace"]),
Height = LineHeight * line_count(Txt),
{Width,Height}.
cached_line_height(DB,FontSpec) ->
gstk_db:lookup(DB, {cached_line_height,FontSpec}).
cache_line_height(DB,FontSpec,Size) ->
gstk_db:insert(DB, {cached_line_height,FontSpec}, Size).
line_count(Line) ->
line_count(Line, 1).
line_count([H | T], Count) ->
Count + line_count(H, 0) + line_count(T, 0);
line_count($\n, Count) -> Count + 1;
line_count(Char, Count) when integer(Char) -> Count;
line_count([], Count) -> Count.
-endif.
% "expr [font metrics ",FSpec," -linespace] * \
% [regsub -all \\n ",Txt," {} ignore]"
%%----------------------------------------------------------------------
%% Returns: Font specification string in Tk format
%%
%% The input is {Family,Size} or {Family,Style,Size} where Family and
%% Style are atoms ?! FIXME true???
%%----------------------------------------------------------------------
choose_ascii(DB, Font) ->
{Fam,Styl,Siz} = choose(DB, Font),
{variable,V} =gstk_db:lookup(DB,{font,Fam,Styl,Siz}),
% io:format("choose_ascii(~p) =>\n~p\n\n",[Font,V]),
V.
%% DB contains: {font,Fam,Style,Size} -> {replaced_by,{font,Fam,Style,Size}} or
%% {variable, TkVariableStrInclDollar}
%% ###########################################################################
%%
%% We create a new font name on the other side and store the name in the
%% database. We reorder the options so that they have a predefined order.
%%
%% ###########################################################################
choose(DB, FontSpec) ->
choose_font(DB, norm_font_spec(FontSpec)).
choose_font(DB, {Fam,Styl,Siz}) ->
Fam0 = map_family(Fam),
case gstk_db:lookup(DB,{font,Fam0,Styl,Siz}) of
{variable,_OwnFontName} -> true;
undefined ->
N = gstk_db:counter(DB,font), % FIXME: Can use "font create"
% without name to get unique name
NewName=["f",gstk:to_ascii(N)],
% io:format("~s\n\n",
% [lists:flatten(["font create ",NewName," ",
% tk_font_spec({Fam0,Styl,Siz})])]),
gstk:exec(["font create ",NewName," ",
tk_font_spec({Fam0,Styl,Siz})]),
%% should us variable syntax gs(f1) instead
%% have to recompile erlcall to define this global gs var
V2 = {variable,NewName},
gstk_db:insert(DB,{font,Fam0,Styl,Siz},V2),
true
end,
% io:format("choose(~p,~p,~p) =>\n~p\n\n",[Fam,Styl,Siz,{Fam0,Styl,Siz}]),
{Fam0,Styl,Siz}.
%% ----- The Font Model -----
%% Guaranteed system fonts to exists in Tk 8.2 are:
%%
%% Windows : system systemfixed ansi ansifixed device oemfixed
%% Unix : fixed
%%
%% Times, Courier and Helvetica always exists. Tk try to substitute
%% others with the best matchin font.
%% We map GS font style and names to something we know Tk 8 have.
%% We know Tk have 'times', 'courier', 'helvetica' and 'fixed'.
%%
%% GS style specification is 'bold' or 'italic'.
%% GS family is a typeface of type 'times', 'courier', 'helvetica',
%% 'symbol', 'new_century_schoolbook', or 'screen' (which is a suitable
%% screen font).
%%
%% Note that 'symbol' may not be present and this is not handled.
%%
%% The X/Tk8 font handling don't work very well. The fonts are
%% scaled "tk scaling", we can display a 9 and 10 point helvetica
%% but "font actual {helvetica 9}" will return 10 points....
map_family(new_century_schoolbook) ->
times;
map_family(Fam) ->
Fam.
% Normalize so can make the coding easier and compare font
% specifications stored in database with new ones. We ignore invalid
% entries in the list.
norm_font_spec({Family,Size}) ->
{Family,[],Size};
norm_font_spec({Family,Style,Size}) ->
{Family,norm_style(Style),Size}.
norm_style(bold) ->
[bold];
norm_style(italic) ->
[italic];
norm_style([italic]) ->
[italic];
norm_style([bold]) ->
[bold];
norm_style([bold,italic] = Style) ->
Style;
norm_style([italic,bold]) ->
[bold,italic];
norm_style(List) when is_list(List) -> % not well formed list, ignore garbage
case {lists:member(bold, List),lists:member(italic, List)} of
{true,true} ->
[bold,italic];
{true,_} ->
[bold];
{_,true} ->
[italic];
_ ->
[] % ignore garbage
end;
norm_style(_Any) -> % ignore garbage
[].
% Create a tcl string from a normalized font specification
% The style list is normalized.
tk_font_spec({Fam,Style,Size}) ->
["-family ",gstk:to_ascii(Fam),
" -size ",gstk:to_ascii(-Size),
tk_font_spec_style(Style)].
tk_font_spec_style([]) ->
"";
tk_font_spec_style([bold]) ->
" -weight bold";
tk_font_spec_style([italic]) ->
" -slant italic";
tk_font_spec_style([bold,italic]) ->
" -weight bold -slant italic".