aboutsummaryrefslogtreecommitdiff
path: root/doc/plugins/contrib/asymptote.mdwn
blob: a85c60efc2096cd5743fe0f5eb2ea2f996bfe62d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
[[!template id=plugin name=asymptote author="[Peter Simons](http://cryp.to/)"]]
[[!tag type/widget]]

This plugin provides the [[ikiwiki/directive/asymptote]]
[[ikiwiki/directive]] which allows embedding
[asymptote](http://asymptote.sourceforge.net/) diagrams in a page.

Security implications: asymptote has functions for reading files and
other dangerous stuff, so enabling this plugin means that everyone who
can edit your Wiki can also read any file from your hard drive thats
accessible to the user running Ikiwiki. 

[[!if test="enabled(asymptote)" then="""
An example diagram:

[[!asymptote src="""
import geometry;
unitsize(1cm);
triangle t = triangle((0,0), (4,0), (0.5,2));
show(La="$D$", Lb="$E$", Lc="", t);
dot(t.A^^t.B^^t.C);
point pD = midpoint(t.BC); dot(pD);
point pE = midpoint(t.AC); dot(pE);
draw(pD--pE);

point A_ = (pD-t.A)*2+t.A; dot("$A'$", A_, NE);
draw(t.B--A_--t.C, dashed);
draw(t.A--A_, dashed);

point E_ = midpoint(line(t.B,A_)); dot(Label("$E'$", E_, E));
draw(E_--pD, dashed);
"""]]
"""]]

This plugin uses the [[!cpan Digest::SHA]] perl module.

The full source code is:

        #! /usr/bin/perl

        package IkiWiki::Plugin::asymptote;
        use warnings;
        use strict;
        use Digest::MD5 qw(md5_hex);
        use File::Temp qw(tempdir);
        use HTML::Entities;
        use Encode;
        use IkiWiki 3.00;

        sub import {
                hook(type => "getsetup", id => "asymptote", call => \&getsetup);
                hook(type => "preprocess", id => "asymptote", call => \&preprocess);
        }

        sub getsetup () {
                return
                        plugin => {
                                safe => 1,
                                rebuild => undef,
                                section => "widget",
                        },
        }

        sub preprocess (@) {
                my %params = @_;

                my $code = $params{src};
                if (! defined $code && ! length $code) {
                        error gettext("missing src attribute");
                }
                return create($code, \%params);
        }

        sub create ($$$) {
                # This function calls the image generating function and returns
                # the <img .. /> for the generated image.
                my $code = shift;
                my $params = shift;

                my $digest = md5_hex(Encode::encode_utf8($code));

                my $imglink= $params->{page} . "/$digest.png";
                my $imglog =  $params->{page} .  "/$digest.log";
                will_render($params->{page}, $imglink);
                will_render($params->{page}, $imglog);

                my $imgurl=urlto($imglink, $params->{destpage});
                my $logurl=urlto($imglog, $params->{destpage});

                if (-e "$config{destdir}/$imglink" ||
                    gen_image($code, $digest, $params->{page})) {
                        return qq{<img src="$imgurl}
                                .(exists $params->{alt} ? qq{" alt="} . $params->{alt} : qq{})
                                .qq{" class="asymptote" />};
                }
                else {
                        error qq{<a href="$logurl">}.gettext("failed to generate image from code")."</a>";
                }
        }

        sub gen_image ($$$$) {
                # Actually creates the image.
                my $code = shift;
                my $digest = shift;
                my $imagedir = shift;

                my $tmp = eval { create_tmp_dir($digest) };
                if (! $@ &&
                    writefile("$digest.asy", $tmp, $code) &&
                    writefile("$imagedir/$digest.png", $config{destdir}, "") &&
                    system("asy -render=2 -offscreen -f png -o $config{destdir}/$imagedir/$digest.png $tmp/$digest.asy &>$tmp/$digest.log") == 0
                   ) {
                        return 1;
                }
                else {
                        # store failure log
                        my $log="";
                        {
                                if (open(my $f, '<', "$tmp/$digest.log")) {
                                        local $/=undef;
                                        $log = <$f>;
                                        close($f);
                                }
                        }
                        writefile("$digest.log", "$config{destdir}/$imagedir", $log);

                        return 0;
                }
        }

        sub create_tmp_dir ($) {
                # Create a temp directory, it will be removed when ikiwiki exits.
                my $base = shift;

                my $template = $base.".XXXXXXXXXX";
                my $tmpdir = tempdir($template, TMPDIR => 1, CLEANUP => 1);
                return $tmpdir;
        }

        1