2006-10-21 23:59:44 +02:00
|
|
|
#!/usr/bin/perl
|
|
|
|
# Ikiwiki enhanced image handling plugin
|
|
|
|
# Christian Mock cm@tahina.priv.at 20061002
|
|
|
|
package IkiWiki::Plugin::img;
|
|
|
|
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
2008-12-23 22:34:19 +01:00
|
|
|
use IkiWiki 3.00;
|
2006-10-21 23:59:44 +02:00
|
|
|
|
|
|
|
my %imgdefaults;
|
|
|
|
|
2008-12-17 21:22:16 +01:00
|
|
|
sub import {
|
2008-08-03 22:40:12 +02:00
|
|
|
hook(type => "getsetup", id => "img", call => \&getsetup);
|
2007-05-23 18:50:41 +02:00
|
|
|
hook(type => "preprocess", id => "img", call => \&preprocess, scan => 1);
|
2008-12-17 21:22:16 +01:00
|
|
|
}
|
2006-10-21 23:59:44 +02:00
|
|
|
|
2008-12-17 21:22:16 +01:00
|
|
|
sub getsetup () {
|
2008-08-03 22:40:12 +02:00
|
|
|
return
|
|
|
|
plugin => {
|
|
|
|
safe => 1,
|
|
|
|
rebuild => undef,
|
2010-02-12 12:35:52 +01:00
|
|
|
section => "widget",
|
2008-08-03 22:40:12 +02:00
|
|
|
},
|
2016-05-04 09:54:19 +02:00
|
|
|
img_allowed_formats => {
|
|
|
|
type => "string",
|
2016-05-06 07:57:12 +02:00
|
|
|
default => [qw(jpeg png gif svg)],
|
|
|
|
description => "Image formats to process (jpeg, png, gif, svg, pdf or 'everything' to accept all)",
|
2016-05-04 09:54:19 +02:00
|
|
|
# ImageMagick has had arbitrary code execution flaws,
|
|
|
|
# and the whole delegates mechanism is scary from
|
|
|
|
# that perspective
|
|
|
|
safe => 0,
|
|
|
|
rebuild => 0,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
sub allowed {
|
|
|
|
my $format = shift;
|
|
|
|
my $allowed = $config{img_allowed_formats};
|
2016-05-06 07:57:12 +02:00
|
|
|
$allowed = ['jpeg', 'png', 'gif', 'svg'] unless defined $allowed && @$allowed;
|
2016-05-04 09:54:19 +02:00
|
|
|
|
|
|
|
foreach my $a (@$allowed) {
|
2016-05-06 08:32:17 +02:00
|
|
|
return 1 if lc($a) eq $format || lc($a) eq 'everything';
|
2016-05-04 09:54:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2008-12-17 21:22:16 +01:00
|
|
|
}
|
2008-08-03 22:40:12 +02:00
|
|
|
|
2008-12-17 21:22:16 +01:00
|
|
|
sub preprocess (@) {
|
2006-10-21 23:59:44 +02:00
|
|
|
my ($image) = $_[0] =~ /$config{wiki_file_regexp}/; # untaint
|
|
|
|
my %params=@_;
|
|
|
|
|
2010-01-29 03:07:23 +01:00
|
|
|
if (! defined $image) {
|
|
|
|
error("bad image filename");
|
|
|
|
}
|
|
|
|
|
2008-06-08 06:02:33 +02:00
|
|
|
if (exists $imgdefaults{$params{page}}) {
|
|
|
|
foreach my $key (keys %{$imgdefaults{$params{page}}}) {
|
|
|
|
if (! exists $params{$key}) {
|
|
|
|
$params{$key}=$imgdefaults{$params{page}}->{$key};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-06-13 04:59:46 +02:00
|
|
|
if (! exists $params{size} || ! length $params{size}) {
|
2008-06-08 06:02:33 +02:00
|
|
|
$params{size}='full';
|
2006-10-21 23:59:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($image eq 'defaults') {
|
2008-06-08 06:02:33 +02:00
|
|
|
$imgdefaults{$params{page}} = \%params;
|
2006-10-21 23:59:44 +02:00
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
Avoid %links accumulating duplicates. (For TOVA)
This is sorta an optimisation, and sorta a bug fix. In one
test case I have available, it can speed a page build up from 3
minutes to 3 seconds.
The root of the problem is that $links{$page} contains arrays of
links, rather than hashes of links. And when a link is found,
it is just pushed onto the array, without checking for dups.
Now, the array is emptied before scanning a page, so there
should not be a lot of opportunity for lots of duplicate links
to pile up in it. But, in some cases, they can, and if there
are hundreds of duplicate links in the array, then scanning it
for matching links, as match_link and some other code does,
becomes much more expensive than it needs to be.
Perhaps the real right fix would be to change the data structure
to a hash. But, the list of links is never accessed like that,
you always want to iterate through it.
I also looked at deduping the list in saveindex, but that does
a lot of unnecessary work, and doesn't completly solve the problem.
So, finally, I decided to add an add_link function that handles deduping,
and make ikiwiki-transition remove the old dup links.
2009-05-06 05:40:09 +02:00
|
|
|
add_link($params{page}, $image);
|
2009-09-28 02:57:27 +02:00
|
|
|
add_depends($params{page}, $image);
|
2009-07-27 22:22:26 +02:00
|
|
|
|
2008-01-09 08:31:11 +01:00
|
|
|
# optimisation: detect scan mode, and avoid generating the image
|
|
|
|
if (! defined wantarray) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2007-05-23 03:44:11 +02:00
|
|
|
my $file = bestlink($params{page}, $image);
|
2008-07-21 22:38:40 +02:00
|
|
|
my $srcfile = srcfile($file, 1);
|
2008-07-21 22:53:52 +02:00
|
|
|
if (! length $file || ! defined $srcfile) {
|
|
|
|
return htmllink($params{page}, $params{destpage}, $image);
|
2008-07-21 22:38:40 +02:00
|
|
|
}
|
2006-10-21 23:59:44 +02:00
|
|
|
|
2008-03-24 01:01:26 +01:00
|
|
|
my $dir = $params{page};
|
2006-10-21 23:59:44 +02:00
|
|
|
my $base = IkiWiki::basename($file);
|
2016-05-04 09:52:40 +02:00
|
|
|
my $extension;
|
|
|
|
my $format;
|
|
|
|
|
2016-05-09 00:26:15 +02:00
|
|
|
if ($base =~ m/\.([a-z0-9]+)$/is) {
|
2016-05-04 09:52:40 +02:00
|
|
|
$extension = $1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
error gettext("Unable to detect image type from extension");
|
|
|
|
}
|
|
|
|
|
|
|
|
# Never interpret well-known file extensions as any other format,
|
|
|
|
# in case the wiki configuration unwisely allows attaching
|
|
|
|
# arbitrary files named *.jpg, etc.
|
2016-05-06 00:17:45 +02:00
|
|
|
my $magic;
|
|
|
|
my $offset = 0;
|
|
|
|
open(my $in, '<', $srcfile) or error sprintf(gettext("failed to read %s: %s"), $file, $!);
|
|
|
|
binmode($in);
|
|
|
|
|
2016-05-04 09:52:40 +02:00
|
|
|
if ($extension =~ m/^(jpeg|jpg)$/is) {
|
|
|
|
$format = 'jpeg';
|
2016-05-06 00:17:45 +02:00
|
|
|
$magic = "\377\330\377";
|
2016-05-04 09:52:40 +02:00
|
|
|
}
|
|
|
|
elsif ($extension =~ m/^(png)$/is) {
|
|
|
|
$format = 'png';
|
2016-05-06 00:17:45 +02:00
|
|
|
$magic = "\211PNG\r\n\032\n";
|
2016-05-04 09:52:40 +02:00
|
|
|
}
|
|
|
|
elsif ($extension =~ m/^(gif)$/is) {
|
|
|
|
$format = 'gif';
|
2016-05-06 00:17:45 +02:00
|
|
|
$magic = "GIF8";
|
2016-05-04 09:52:40 +02:00
|
|
|
}
|
|
|
|
elsif ($extension =~ m/^(svg)$/is) {
|
|
|
|
$format = 'svg';
|
|
|
|
}
|
|
|
|
elsif ($extension =~ m/^(pdf)$/is) {
|
|
|
|
$format = 'pdf';
|
2016-05-06 00:17:45 +02:00
|
|
|
$magic = "%PDF-";
|
2016-05-04 09:52:40 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
# allow ImageMagick to auto-detect (potentially dangerous)
|
2018-02-28 10:51:13 +01:00
|
|
|
my $im = Image::Magick->new();
|
|
|
|
my $r = $im->Ping(file => $in);
|
|
|
|
if ($r) {
|
|
|
|
$format = lc $r;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
error sprintf(gettext("failed to determine format of %s"), $file);
|
|
|
|
}
|
2016-05-04 09:52:40 +02:00
|
|
|
}
|
|
|
|
|
2016-05-04 09:54:19 +02:00
|
|
|
error sprintf(gettext("%s image processing disabled in img_allowed_formats configuration"), $format ? $format : "\"$extension\"") unless allowed($format ? $format : "everything");
|
|
|
|
|
2016-05-06 00:17:45 +02:00
|
|
|
# Try harder to protect ImageMagick from itself
|
2016-05-06 07:57:12 +02:00
|
|
|
if (defined $magic) {
|
2016-05-06 00:17:45 +02:00
|
|
|
my $content;
|
|
|
|
read($in, $content, length $magic) or error sprintf(gettext("failed to read %s: %s"), $file, $!);
|
|
|
|
if ($magic ne $content) {
|
|
|
|
error sprintf(gettext("\"%s\" does not seem to be a valid %s file"), $file, $format);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-07 11:19:04 +02:00
|
|
|
my $ispdf = $base=~s/\.pdf$/.png/i;
|
2014-04-07 11:32:25 +02:00
|
|
|
my $pagenumber = exists($params{pagenumber}) ? int($params{pagenumber}) : 0;
|
|
|
|
if ($pagenumber != 0) {
|
|
|
|
$base = "p$pagenumber-$base";
|
|
|
|
}
|
2007-02-20 04:59:35 +01:00
|
|
|
|
2006-10-21 23:59:44 +02:00
|
|
|
my $imglink;
|
2014-07-15 11:57:57 +02:00
|
|
|
my $imgdatalink;
|
2016-05-06 07:57:12 +02:00
|
|
|
my ($dwidth, $dheight);
|
2014-09-16 10:37:41 +02:00
|
|
|
|
2016-05-06 07:57:12 +02:00
|
|
|
my ($w, $h);
|
|
|
|
if ($params{size} ne 'full') {
|
|
|
|
($w, $h) = ($params{size} =~ /^(\d*)x(\d*)$/);
|
2014-09-16 10:37:41 +02:00
|
|
|
}
|
|
|
|
|
2016-05-06 07:57:12 +02:00
|
|
|
if ($format eq 'svg') {
|
|
|
|
# svg images are not scaled using ImageMagick because the
|
|
|
|
# pipeline is complex. Instead, the image size is simply
|
|
|
|
# set to the provided values.
|
|
|
|
#
|
|
|
|
# Aspect ratio will be preserved automatically when
|
|
|
|
# only a width or only a height is specified.
|
|
|
|
# When both are specified, aspect ratio will not be
|
|
|
|
# preserved.
|
|
|
|
$imglink = $file;
|
|
|
|
$dwidth = $w if length $w;
|
|
|
|
$dheight = $h if length $h;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
eval q{use Image::Magick};
|
|
|
|
error gettext("Image::Magick is not installed") if $@;
|
|
|
|
my $im = Image::Magick->new();
|
|
|
|
my $r = $im->Read("$format:$srcfile\[$pagenumber]");
|
|
|
|
error sprintf(gettext("failed to read %s: %s"), $file, $r) if $r;
|
|
|
|
|
2017-09-01 21:26:26 +02:00
|
|
|
if ($config{deterministic}) {
|
|
|
|
$im->Set('date:create' => 0);
|
|
|
|
$im->Set('date:modify' => 0);
|
|
|
|
$im->Set('option' => 'png:exclude-chunk=time');
|
|
|
|
}
|
|
|
|
|
2016-05-06 07:57:12 +02:00
|
|
|
if (! defined $im->Get("width") || ! defined $im->Get("height")) {
|
|
|
|
error sprintf(gettext("failed to get dimensions of %s"), $file);
|
|
|
|
}
|
2009-08-29 05:23:06 +02:00
|
|
|
|
2016-05-06 07:57:12 +02:00
|
|
|
if (! length $w && ! length $h) {
|
|
|
|
$dwidth = $im->Get("width");
|
|
|
|
$dheight = $im->Get("height");
|
|
|
|
} else {
|
|
|
|
error sprintf(gettext('wrong size format "%s" (should be WxH)'), $params{size})
|
|
|
|
unless (defined $w && defined $h &&
|
|
|
|
(length $w || length $h));
|
|
|
|
|
|
|
|
if ($im->Get("width") == 0 || $im->Get("height") == 0) {
|
|
|
|
($dwidth, $dheight)=(0, 0);
|
|
|
|
} elsif (! length $w || (length $h && $im->Get("height")*$w > $h * $im->Get("width"))) {
|
|
|
|
# using height because only height is given or ...
|
|
|
|
# because original image is more portrait than $w/$h
|
|
|
|
# ... slimness of $im > $h/w
|
|
|
|
# ... $im->Get("height")/$im->Get("width") > $h/$w
|
|
|
|
# ... $im->Get("height")*$w > $h * $im->Get("width")
|
|
|
|
|
|
|
|
$dheight=$h;
|
|
|
|
$dwidth=$h / $im->Get("height") * $im->Get("width");
|
|
|
|
} else { # (! length $h) or $w is what determines the resized size
|
|
|
|
$dwidth=$w;
|
|
|
|
$dheight=$w / $im->Get("width") * $im->Get("height");
|
|
|
|
}
|
2014-07-15 00:23:56 +02:00
|
|
|
}
|
|
|
|
|
2016-05-06 07:57:12 +02:00
|
|
|
if ($dwidth < $im->Get("width") || $ispdf) {
|
|
|
|
# resize down, or resize to pixels at all
|
2014-07-15 00:23:56 +02:00
|
|
|
|
2016-05-06 07:57:12 +02:00
|
|
|
my $outfile = "$config{destdir}/$dir/$params{size}-$base";
|
|
|
|
$imglink = "$dir/$params{size}-$base";
|
2014-07-15 00:23:56 +02:00
|
|
|
|
2016-05-06 07:57:12 +02:00
|
|
|
will_render($params{page}, $imglink);
|
2014-07-15 00:23:56 +02:00
|
|
|
|
2016-05-06 07:57:12 +02:00
|
|
|
if (-e $outfile && (-M $srcfile >= -M $outfile)) {
|
|
|
|
$im = Image::Magick->new;
|
|
|
|
$r = $im->Read($outfile);
|
|
|
|
error sprintf(gettext("failed to read %s: %s"), $outfile, $r) if $r;
|
2007-03-06 23:37:05 +01:00
|
|
|
}
|
|
|
|
else {
|
2016-05-06 07:57:12 +02:00
|
|
|
$r = $im->Resize(geometry => "${dwidth}x${dheight}");
|
|
|
|
error sprintf(gettext("failed to resize: %s"), $r) if $r;
|
|
|
|
|
|
|
|
$im->set($ispdf ? (magick => 'png') : ());
|
|
|
|
my @blob = $im->ImageToBlob();
|
|
|
|
# don't actually write resized file in preview mode;
|
|
|
|
# rely on width and height settings
|
|
|
|
if (! $params{preview}) {
|
|
|
|
writefile($imglink, $config{destdir}, $blob[0], 1);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
eval q{use MIME::Base64};
|
|
|
|
error($@) if $@;
|
|
|
|
$imgdatalink = "data:image/".$im->Get("magick").";base64,".encode_base64($blob[0]);
|
|
|
|
}
|
2007-03-06 23:37:05 +01:00
|
|
|
}
|
2016-05-06 07:57:12 +02:00
|
|
|
|
|
|
|
# always get the true size of the resized image (it could be
|
|
|
|
# that imagemagick did its calculations differently)
|
|
|
|
$dwidth = $im->Get("width");
|
|
|
|
$dheight = $im->Get("height");
|
|
|
|
} else {
|
|
|
|
$imglink = $file;
|
2006-10-21 23:59:44 +02:00
|
|
|
}
|
2014-07-15 00:23:56 +02:00
|
|
|
|
2016-05-06 07:57:12 +02:00
|
|
|
if (! defined($dwidth) || ! defined($dheight)) {
|
|
|
|
error sprintf(gettext("failed to determine size of image %s"), $file)
|
|
|
|
}
|
2009-09-28 02:53:02 +02:00
|
|
|
}
|
2006-10-21 23:59:44 +02:00
|
|
|
|
2007-03-06 23:37:05 +01:00
|
|
|
my ($fileurl, $imgurl);
|
2014-07-15 11:57:57 +02:00
|
|
|
my $urltobase = $params{preview} ? undef : $params{destpage};
|
|
|
|
$fileurl=urlto($file, $urltobase);
|
|
|
|
$imgurl=$imgdatalink ? $imgdatalink : urlto($imglink, $urltobase);
|
2007-03-06 23:37:05 +01:00
|
|
|
|
2010-10-13 18:57:16 +02:00
|
|
|
if (! exists $params{class}) {
|
2010-07-05 20:04:49 +02:00
|
|
|
$params{class}="img";
|
|
|
|
}
|
|
|
|
|
2010-06-12 22:16:24 +02:00
|
|
|
my $attrs='';
|
|
|
|
foreach my $attr (qw{alt title class id hspace vspace}) {
|
|
|
|
if (exists $params{$attr}) {
|
|
|
|
$attrs.=" $attr=\"$params{$attr}\"";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-06 07:57:12 +02:00
|
|
|
my $imgtag='<img src="'.$imgurl.'"';
|
|
|
|
$imgtag.=' width="'.$dwidth.'"' if defined $dwidth;
|
|
|
|
$imgtag.=' height="'.$dheight.'"' if defined $dheight;
|
|
|
|
$imgtag.= $attrs.
|
2010-01-07 22:09:34 +01:00
|
|
|
(exists $params{align} && ! exists $params{caption} ? ' align="'.$params{align}.'"' : '').
|
2007-07-15 21:00:07 +02:00
|
|
|
' />';
|
|
|
|
|
2010-01-07 21:36:49 +01:00
|
|
|
my $link;
|
2010-01-07 21:41:16 +01:00
|
|
|
if (! defined $params{link}) {
|
2010-01-07 21:36:49 +01:00
|
|
|
$link=$fileurl;
|
2007-07-15 21:00:07 +02:00
|
|
|
}
|
2007-12-28 22:14:43 +01:00
|
|
|
elsif ($params{link} =~ /^\w+:\/\//) {
|
2010-01-07 21:36:49 +01:00
|
|
|
$link=$params{link};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (defined $link) {
|
2010-01-07 22:09:34 +01:00
|
|
|
$imgtag='<a href="'.$link.'">'.$imgtag.'</a>';
|
2007-12-28 22:14:43 +01:00
|
|
|
}
|
2009-06-18 16:34:48 +02:00
|
|
|
else {
|
|
|
|
my $b = bestlink($params{page}, $params{link});
|
|
|
|
|
|
|
|
if (length $b) {
|
2009-10-09 19:37:06 +02:00
|
|
|
add_depends($params{page}, $b, deptype("presence"));
|
2009-06-18 16:34:48 +02:00
|
|
|
$imgtag=htmllink($params{page}, $params{destpage},
|
|
|
|
$params{link}, linktext => $imgtag,
|
2010-01-05 15:23:22 +01:00
|
|
|
noimageinline => 1,
|
2010-01-07 21:36:49 +01:00
|
|
|
);
|
2009-06-18 16:34:48 +02:00
|
|
|
}
|
2007-09-22 18:46:27 +02:00
|
|
|
}
|
2008-06-08 05:45:40 +02:00
|
|
|
|
2008-06-08 06:02:33 +02:00
|
|
|
if (exists $params{caption}) {
|
2010-01-05 15:23:22 +01:00
|
|
|
return '<table class="img'.
|
2010-01-07 22:09:34 +01:00
|
|
|
(exists $params{align} ? " align-$params{align}" : "").
|
2010-01-05 15:23:22 +01:00
|
|
|
'">'.
|
2008-06-08 06:02:33 +02:00
|
|
|
'<caption>'.$params{caption}.'</caption>'.
|
2008-06-08 05:45:40 +02:00
|
|
|
'<tr><td>'.$imgtag.'</td></tr>'.
|
|
|
|
'</table>';
|
|
|
|
}
|
2007-07-15 21:00:07 +02:00
|
|
|
else {
|
|
|
|
return $imgtag;
|
|
|
|
}
|
2008-12-17 21:22:16 +01:00
|
|
|
}
|
2006-10-21 23:59:44 +02:00
|
|
|
|
2007-03-06 23:37:05 +01:00
|
|
|
1
|