Bivio::UI::FacadeComponent::Icon
# Copyright (c) 1999-2007 bivio Software, Inc. All rights reserved.
# $Id$
package Bivio::UI::FacadeComponent::Icon;
use strict;
use Bivio::Base 'UI.FacadeComponent';
use Image::Size ();
b_use('IO.ClassLoaderAUTOLOAD');
my($_HTML) = b_use('Bivio.HTML');
my($_URI) = '/i';
my($_CLEAR_DOT) = {
uri => '/i/dot.gif',
height => 1,
width => 1,
};
my($_FILE_SUFFIX_REGEXP);
my($_MISSING) = '/missing-image';
my($_FILE_SUFFIX_SEARCH_LIST) = ['.gif', '.jpg', '.jpeg', '.png'];
$_FILE_SUFFIX_REGEXP = qr{
\.(?:@{[join('|', map(substr($_, 1), @$_FILE_SUFFIX_SEARCH_LIST))]})$
}ix;
Bivio::IO::Config->register({
uri => $_URI,
missing_uri => $_MISSING,
clear_dot_uri => $_CLEAR_DOT->{uri},
file_suffix_search_list => $_FILE_SUFFIX_SEARCH_LIST,
});
# We keep a cache of all values if Facade.want_local_file_cache is true. In
# this case, we cache to avoid repeating not-found errors for each icon and for
# performance (avoids (N-1)xM file reads).
my($_CACHE) = {};
sub FILE_SUFFIX_REGEXP {
# : regexp_ref
# Returns regular expression used for file suffixes
return $_FILE_SUFFIX_REGEXP;
}
sub UNDEF_VALUE {
return {
file_name => $_MISSING,
uri => $_MISSING,
width => 1,
height => 1,
mtime => 0,
};
}
sub format_css {
my($proto, $name, $attr, $req) = @_;
($req, $attr) = ($attr, undef)
if !$req && ref($attr);
my($v) = _find($proto, $name, $req)->{value};
$attr ||= 'uri';
return $attr eq 'uri' ? 'url(' . $v->{uri} . ')'
: defined($v->{$attr}) ? $v->{$attr}
: $proto->die($v, $attr, ': no such attribute');
}
sub format_html {
# (proto, string, Collection.Attributes) : string
# Returns the image formated for an an C<IMG> tag, e.g.
#
# src="uri" width="W" height="H"
#
# Value contains a I<leading space>.
return _find(@_)->{html};
}
sub format_html_attribute {
# (proto, string, string, Collection.Attributes) : string
# Formats tag, e.g. background as in:
#
# background="uri"
#
# Value contains a I<leading space>.
my($proto, $name, $attribute, $req_or_facade) = @_;
return qq{ $attribute=}
. _html_attr(uri => $proto, $name, $req_or_facade);
}
sub get_clear_dot {
# (proto) : hash_ref
# Please use L<Bivio::UI::HTML::Widget::ClearDot|Bivio::UI::HTML::Widget::ClearDot>.
#
# Returns single pixel transparent gif. Value should be treated as
# read-only and is constant.
# Make a copy just in case
return {%$_CLEAR_DOT};
}
sub get_favicon_uri {
my($proto, $req) = @_;
my($uri) = FacadeComponent_Text()->get_value('favicon_uri', $req);
return Type_CacheTagFilePath()->from_local_path(
$req->req('UI.Facade')->get_local_plain_file_name($uri), $uri);
}
sub get_height {
# (proto, string, Collection.Attributes) : int
# Returns the height of the icon.
return _find(@_)->{value}->{height};
}
sub get_height_as_html {
# (proto, string, Collection.Attributes) : string
# Returns the height of the icon in the form of an " height=N" attribute
# to an HTML tag.
return ' height=' . _html_attr(height => @_);
}
sub get_icon_dir {
my($self) = @_;
return _facade_name($self, '');
}
sub get_uri {
return shift->internal_uri(@_);
}
sub get_value {
# (proto, string, Collection.Attributes) : hash_ref
# (self, string) : hash_ref
# The return value should be treated as read-only. The result contains
# the following keys:
#
#
# height : int
#
# The height of the image.
#
# uri : string
#
# The absolute uri (/i/...) of the image.
#
# width : int
#
# The width of the image.
# Make a copy for safety reasons
return {%{_find(@_)->{value}}};
}
sub get_width {
# (proto, string, Collection.Attributes) : int
# Returns the width of the icon.
return _find(@_)->{value}->{width};
}
sub get_width_as_html {
# (proto, string, Collection.Attributes) : string
# Returns the width of the icon in the form of an " width=N" attribute
# to an HTML tag.
return ' width=' . _html_attr(width => @_);
}
sub handle_config {
# (proto, hash) : undef
# clear_dot_uri : string [/i/dot.gif]
#
# URI of single pixel transparent gif.
# See L<get_clear_dot|"get_clear_dot">.
#
# file_suffix_search_list : array_ref [['.gif', '.jpg', '.jpeg', '.png']]
#
# Ordered list of file suffices to search for when trying to find an icon.
# Two icons cannot share the same base name, e.g. only one of
# my_icon.gif and my_icon.jpg will be found when looking for I<my_icon>.
#
# missing_uri : string [/missing-image]
#
# URI to be used when an icon could not be found.
#
# uri : string [/i]
#
# URI prefix for icons. The uniquely short name allows for simple
# configuration of URI-based front-end icon serving.
my(undef, $cfg) = @_;
$_URI = $cfg->{uri};
Bivio::IO::Alert->warn("$_URI: is not absolute") unless $_URI =~ m!^/!;
$_URI =~ s!([^/])$!$1/!;
$_MISSING = $cfg->{missing_uri};
$_CLEAR_DOT->{uri} = $cfg->{clear_dot_uri};
$_FILE_SUFFIX_SEARCH_LIST = [map(
$_ =~ /^\w+$/ ? ".$_"
: $_ =~ /^\.\w+$/
? $_
: b_die($_, ': bad file_suffix_search_list value (not a word)'),
@{$cfg->{file_suffix_search_list}},
)];
return;
}
sub internal_cache_key {
return _facade_name(@_);
}
sub internal_file_name {
my($name) = _facade_name(@_);
foreach my $suffix (@$_FILE_SUFFIX_SEARCH_LIST) {
my($f) = $name . $suffix;
return $f
if -r $f;
}
return undef;
}
sub internal_initialize_value {
my($self, $value) = @_;
my($v) = $value->{config};
$value->{value} = defined($v)
? b_die($value, ': configuration not allowed')
: $self->UNDEF_VALUE;
return;
}
sub internal_uri {
my($file_name) = shift->internal_file_name(@_);
$file_name = Type_CacheTagFilePath()->from_local_path($file_name);
return $file_name =~ m{([^/]+)$} ? "$_URI$1" : $_MISSING;
}
sub _facade_name {
my($self, $name) = @_;
return $self->get_facade->get_local_plain_file_name("$_URI$name");
}
sub _find {
# (proto, string, Collection.Attributes) : hash_ref
# Returns the value hash_ref
my($proto, $name, $req_or_facade) = @_;
my($self) = $proto->internal_get_self($req_or_facade);
my($facade) = $self->get_facade;
my($key) = $self->internal_cache_key($name);
my($cache) = $facade->get('want_local_file_cache');
if ($cache &&= $_CACHE->{$key}) {
return $cache
if (-M $cache->{file_name} || 0) == $cache->{mtime};
delete($_CACHE->{$key});
$cache = 1;
}
my($file) = $self->internal_file_name($name);
my($w, $h, $err) = $file ? Image::Size::imgsize($file) : ();
my($u);
unless (defined($w)) {
Bivio::IO::Alert->warn(
$facade, '.Icon.', $name,
($err ? (': Image::Size error: ', $err) : ': not found'),
);
$w = $h = 1;
$file = $_MISSING;
$u = $_MISSING;
}
my($v) = {
file_name => $file,
uri => $u || $self->internal_uri($name),
width => $w,
height => $h,
mtime => -M $file || 0,
};
my($value) = {
value => $v,
html => qq{ src="@{[$_HTML->escape_attr_value($v->{uri})]}" width="$w" height="$h"},
};
$_CACHE->{$key} = $value
if $cache;
return $value;
}
sub _html_attr {
my($which) = shift;
return '"' . $_HTML->escape_attr_value(_find(@_)->{value}->{$which}) . '"';
}
1;