| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # Copyright (c) 2009, 2010 Oleksandr Tymoshenko | 
| 2 |  |  |  |  |  |  | # All rights reserved. | 
| 3 |  |  |  |  |  |  |  | 
| 4 |  |  |  |  |  |  | # Redistribution and use in source and binary forms, with or without | 
| 5 |  |  |  |  |  |  | # modification, are permitted provided that the following conditions | 
| 6 |  |  |  |  |  |  | # are met: | 
| 7 |  |  |  |  |  |  | # 1. Redistributions of source code must retain the above copyright | 
| 8 |  |  |  |  |  |  | #    notice, this list of conditions and the following disclaimer. | 
| 9 |  |  |  |  |  |  | # 2. Redistributions in binary form must reproduce the above copyright | 
| 10 |  |  |  |  |  |  | #    notice, this list of conditions and the following disclaimer in the | 
| 11 |  |  |  |  |  |  | #    documentation and/or other materials provided with the distribution. | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | 
| 14 |  |  |  |  |  |  | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
| 15 |  |  |  |  |  |  | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
| 16 |  |  |  |  |  |  | # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | 
| 17 |  |  |  |  |  |  | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
| 18 |  |  |  |  |  |  | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | 
| 19 |  |  |  |  |  |  | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | 
| 20 |  |  |  |  |  |  | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | 
| 21 |  |  |  |  |  |  | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | 
| 22 |  |  |  |  |  |  | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | 
| 23 |  |  |  |  |  |  | # SUCH DAMAGE. | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | package EBook::EPUB::Lite; | 
| 26 |  |  |  |  |  |  |  | 
| 27 | 4 |  |  | 4 |  | 88215 | use strict; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 119 |  | 
| 28 | 4 |  |  | 4 |  | 16 | use warnings; | 
|  | 4 |  |  |  |  | 6 |  | 
|  | 4 |  |  |  |  | 119 |  | 
| 29 | 4 |  |  | 4 |  | 1949 | use version; | 
|  | 4 |  |  |  |  | 6519 |  | 
|  | 4 |  |  |  |  | 25 |  | 
| 30 |  |  |  |  |  |  | our $VERSION = 0.7; | 
| 31 |  |  |  |  |  |  |  | 
| 32 | 4 |  |  | 4 |  | 2608 | use Moo; | 
|  | 4 |  |  |  |  | 49609 |  | 
|  | 4 |  |  |  |  | 23 |  | 
| 33 | 4 |  |  | 4 |  | 9149 | use Types::Standard qw/ArrayRef HashRef Object Str/; | 
|  | 4 |  |  |  |  | 286647 |  | 
|  | 4 |  |  |  |  | 56 |  | 
| 34 | 4 |  |  | 4 |  | 6941 | use EBook::EPUB::Lite::Metadata; # done | 
|  | 4 |  |  |  |  | 19 |  | 
|  | 4 |  |  |  |  | 188 |  | 
| 35 | 4 |  |  | 4 |  | 2495 | use EBook::EPUB::Lite::Manifest; # done | 
|  | 4 |  |  |  |  | 14 |  | 
|  | 4 |  |  |  |  | 141 |  | 
| 36 | 4 |  |  | 4 |  | 2105 | use EBook::EPUB::Lite::Guide; # done | 
|  | 4 |  |  |  |  | 11 |  | 
|  | 4 |  |  |  |  | 148 |  | 
| 37 | 4 |  |  | 4 |  | 1897 | use EBook::EPUB::Lite::Spine; # done | 
|  | 4 |  |  |  |  | 11 |  | 
|  | 4 |  |  |  |  | 153 |  | 
| 38 | 4 |  |  | 4 |  | 1913 | use EBook::EPUB::Lite::NCX; # done | 
|  | 4 |  |  |  |  | 16 |  | 
|  | 4 |  |  |  |  | 160 |  | 
| 39 |  |  |  |  |  |  |  | 
| 40 | 4 |  |  | 4 |  | 1984 | use EBook::EPUB::Lite::Container::Zip; # not moose | 
|  | 4 |  |  |  |  | 12 |  | 
|  | 4 |  |  |  |  | 125 |  | 
| 41 |  |  |  |  |  |  |  | 
| 42 | 4 |  |  | 4 |  | 2508 | use UUID::Tiny qw(); | 
|  | 4 |  |  |  |  | 88699 |  | 
|  | 4 |  |  |  |  | 141 |  | 
| 43 | 4 |  |  | 4 |  | 37 | use File::Temp; | 
|  | 4 |  |  |  |  | 10 |  | 
|  | 4 |  |  |  |  | 438 |  | 
| 44 | 4 |  |  | 4 |  | 27 | use File::Basename qw/dirname/; | 
|  | 4 |  |  |  |  | 7 |  | 
|  | 4 |  |  |  |  | 294 |  | 
| 45 | 4 |  |  | 4 |  | 24 | use File::Copy; | 
|  | 4 |  |  |  |  | 7 |  | 
|  | 4 |  |  |  |  | 262 |  | 
| 46 | 4 |  |  | 4 |  | 21 | use File::Path; | 
|  | 4 |  |  |  |  | 6 |  | 
|  | 4 |  |  |  |  | 216 |  | 
| 47 | 4 |  |  | 4 |  | 24 | use File::Spec; | 
|  | 4 |  |  |  |  | 116 |  | 
|  | 4 |  |  |  |  | 116 |  | 
| 48 | 4 |  |  | 4 |  | 23 | use Carp; | 
|  | 4 |  |  |  |  | 6 |  | 
|  | 4 |  |  |  |  | 10978 |  | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | has metadata    => ( | 
| 51 |  |  |  |  |  |  | isa     => Object, | 
| 52 |  |  |  |  |  |  | is      => 'ro', | 
| 53 |  |  |  |  |  |  | default => sub { EBook::EPUB::Lite::Metadata->new() }, | 
| 54 |  |  |  |  |  |  | handles => [ qw/add_contributor | 
| 55 |  |  |  |  |  |  | add_creator | 
| 56 |  |  |  |  |  |  | add_coverage | 
| 57 |  |  |  |  |  |  | add_date | 
| 58 |  |  |  |  |  |  | add_meta_dcitem | 
| 59 |  |  |  |  |  |  | add_description | 
| 60 |  |  |  |  |  |  | add_format | 
| 61 |  |  |  |  |  |  | add_meta_item | 
| 62 |  |  |  |  |  |  | add_language | 
| 63 |  |  |  |  |  |  | add_publisher | 
| 64 |  |  |  |  |  |  | add_relation | 
| 65 |  |  |  |  |  |  | add_rights | 
| 66 |  |  |  |  |  |  | add_source | 
| 67 |  |  |  |  |  |  | add_subject | 
| 68 |  |  |  |  |  |  | add_translator | 
| 69 |  |  |  |  |  |  | add_type | 
| 70 |  |  |  |  |  |  | /], | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | ); | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | has manifest    => ( | 
| 75 |  |  |  |  |  |  | isa     => Object, | 
| 76 |  |  |  |  |  |  | is      => 'ro', | 
| 77 |  |  |  |  |  |  | default => sub { EBook::EPUB::Lite::Manifest->new() }, | 
| 78 |  |  |  |  |  |  | ); | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | has spine       => ( | 
| 81 |  |  |  |  |  |  | isa     => Object, | 
| 82 |  |  |  |  |  |  | is      => 'ro', | 
| 83 |  |  |  |  |  |  | default => sub { EBook::EPUB::Lite::Spine->new() }, | 
| 84 |  |  |  |  |  |  | ); | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | has guide       => ( | 
| 87 |  |  |  |  |  |  | isa     => Object, | 
| 88 |  |  |  |  |  |  | is      => 'ro', | 
| 89 |  |  |  |  |  |  | default => sub { EBook::EPUB::Lite::Guide->new() }, | 
| 90 |  |  |  |  |  |  | ); | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | has ncx     => ( | 
| 93 |  |  |  |  |  |  | isa     => Object, | 
| 94 |  |  |  |  |  |  | is      => 'ro', | 
| 95 |  |  |  |  |  |  | default => sub { EBook::EPUB::Lite::NCX->new() }, | 
| 96 |  |  |  |  |  |  | handles => [ qw/add_navpoint/ ], | 
| 97 |  |  |  |  |  |  | ); | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | has _uuid  => ( | 
| 100 |  |  |  |  |  |  | isa     => Str, | 
| 101 |  |  |  |  |  |  | is      => 'rw', | 
| 102 |  |  |  |  |  |  | ); | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | has _encryption_key  => ( | 
| 105 |  |  |  |  |  |  | isa     => Str, | 
| 106 |  |  |  |  |  |  | is      => 'rw', | 
| 107 |  |  |  |  |  |  | ); | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | # Array of filenames that should be encrypted | 
| 110 |  |  |  |  |  |  | has _encrypted_filerefs => ( | 
| 111 |  |  |  |  |  |  | is         => 'ro', | 
| 112 |  |  |  |  |  |  | isa        => ArrayRef[Object], | 
| 113 |  |  |  |  |  |  | default    => sub { [] }, | 
| 114 |  |  |  |  |  |  | ); | 
| 115 |  |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  | sub add_encrypted_fileref { | 
| 117 | 1 |  |  | 1 | 0 | 3 | my ($self, @args) = @_; | 
| 118 | 1 |  |  |  |  | 3 | push @{ shift->_encrypted_filerefs }, @args; | 
|  | 1 |  |  |  |  | 7 |  | 
| 119 |  |  |  |  |  |  | } | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | sub encrypted_filerefs { | 
| 122 | 2 |  |  | 2 | 0 | 2 | return @{ shift->_encrypted_filerefs }; | 
|  | 2 |  |  |  |  | 11 |  | 
| 123 |  |  |  |  |  |  | } | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | has id_counters => ( isa => HashRef, is => 'ro', default =>  sub { {} }); | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | has _temporary_dir_handle => (isa => Object, | 
| 128 |  |  |  |  |  |  | is => 'ro', | 
| 129 |  |  |  |  |  |  | default => sub { | 
| 130 |  |  |  |  |  |  | # defaults to CLEANUP => 1 as per doc | 
| 131 |  |  |  |  |  |  | return File::Temp->newdir; | 
| 132 |  |  |  |  |  |  | }); | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | sub tmpdir { | 
| 135 |  |  |  |  |  |  | # return the path, not the object. | 
| 136 | 18 |  |  | 18 | 0 | 161 | return shift->_temporary_dir_handle->dirname; | 
| 137 |  |  |  |  |  |  | } | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | sub BUILD | 
| 140 |  |  |  |  |  |  | { | 
| 141 | 2 |  |  | 2 | 0 | 144 | my ($self) = @_; | 
| 142 | 2 |  |  |  |  | 19 | $self->manifest->add_item( | 
| 143 |  |  |  |  |  |  | id          => 'ncx', | 
| 144 |  |  |  |  |  |  | href        => 'toc.ncx', | 
| 145 |  |  |  |  |  |  | media_type  => 'application/x-dtbncx+xml' | 
| 146 |  |  |  |  |  |  | ); | 
| 147 |  |  |  |  |  |  |  | 
| 148 | 2 |  |  |  |  | 13 | $self->spine->toc('ncx'); | 
| 149 | 2 | 50 |  |  |  | 1267 | mkdir ($self->tmpdir . "/OPS") or die "Can't make OPS dir in " . $self->tmpdir; | 
| 150 |  |  |  |  |  |  | # Implicitly generate UUID for book | 
| 151 | 2 |  |  |  |  | 351 | $self->_set_uuid(UUID::Tiny::uuid_to_string(UUID::Tiny::create_uuid())); | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | sub to_xml | 
| 155 |  |  |  |  |  |  | { | 
| 156 | 2 |  |  | 2 | 0 | 5 | my ($self) = @_; | 
| 157 | 2 |  |  |  |  | 4 | my $xml; | 
| 158 |  |  |  |  |  |  |  | 
| 159 | 2 |  |  |  |  | 21 | my $writer = XML::Writer->new( | 
| 160 |  |  |  |  |  |  | OUTPUT      => \$xml, | 
| 161 |  |  |  |  |  |  | DATA_MODE   => 1, | 
| 162 |  |  |  |  |  |  | DATA_INDENT => 2, | 
| 163 |  |  |  |  |  |  | ); | 
| 164 |  |  |  |  |  |  |  | 
| 165 | 2 |  |  |  |  | 356 | $writer->xmlDecl("utf-8"); | 
| 166 | 2 |  |  |  |  | 56 | $writer->startTag('package', | 
| 167 |  |  |  |  |  |  | xmlns               => 'http://www.idpf.org/2007/opf', | 
| 168 |  |  |  |  |  |  | version             => '2.0', | 
| 169 |  |  |  |  |  |  | 'unique-identifier' => 'BookId', | 
| 170 |  |  |  |  |  |  | ); | 
| 171 | 2 |  |  |  |  | 335 | $self->metadata->encode($writer); | 
| 172 | 2 |  |  |  |  | 57 | $self->manifest->encode($writer); | 
| 173 | 2 |  |  |  |  | 60 | $self->spine->encode($writer); | 
| 174 | 2 |  |  |  |  | 53 | $self->guide->encode($writer); | 
| 175 | 2 |  |  |  |  | 5 | $writer->endTag('package'); | 
| 176 | 2 |  |  |  |  | 40 | $writer->end(); | 
| 177 |  |  |  |  |  |  |  | 
| 178 | 2 |  |  |  |  | 319 | return $xml; | 
| 179 |  |  |  |  |  |  | } | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  | sub add_author | 
| 182 |  |  |  |  |  |  | { | 
| 183 | 1 |  |  | 1 | 1 | 9 | my ($self, $author, $formal) = @_; | 
| 184 | 1 |  |  |  |  | 9 | $self->metadata->add_author($author, $formal); | 
| 185 | 1 |  |  |  |  | 9 | $self->ncx->add_author($author); | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | sub add_title | 
| 189 |  |  |  |  |  |  | { | 
| 190 | 1 |  |  | 1 | 1 | 6 | my ($self, $title) = @_; | 
| 191 | 1 |  |  |  |  | 6 | $self->metadata->add_title($title); | 
| 192 | 1 |  |  |  |  | 7 | my $ncx_title =  $self->ncx->title; | 
| 193 |  |  |  |  |  |  | # Collect all titles in a row for NCX | 
| 194 | 1 | 50 |  |  |  | 577 | $title = "$ncx_title $title" if (defined($ncx_title)); | 
| 195 | 1 |  |  |  |  | 19 | $self->ncx->title($title); | 
| 196 |  |  |  |  |  |  | } | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | sub _set_uuid | 
| 199 |  |  |  |  |  |  | { | 
| 200 | 2 |  |  | 2 |  | 642 | my ($self, $uuid) = @_; | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | # Just some naive check for key to be UUID | 
| 203 | 2 | 50 |  |  |  | 19 | if ($uuid !~ /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i) { | 
| 204 | 0 |  |  |  |  | 0 | carp "$uuid - is not valid UUID"; | 
| 205 | 0 |  |  |  |  | 0 | return; | 
| 206 |  |  |  |  |  |  | } | 
| 207 | 2 |  |  |  |  | 4 | my $key = $uuid; | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 2 |  |  |  |  | 11 | $key =~ s/-//g; | 
| 210 | 2 |  |  |  |  | 10 | $key =~ s/([a-f0-9]{2})/chr(hex($1))/egi; | 
|  | 32 |  |  |  |  | 68 |  | 
| 211 | 2 |  |  |  |  | 13 | $self->_encryption_key($key); | 
| 212 | 2 | 50 |  |  |  | 1261 | if (defined($self->_uuid)) { | 
| 213 | 0 |  |  |  |  | 0 | warn "Overriding existing uuid " . $self->_uuid; | 
| 214 | 0 |  |  |  |  | 0 | $self->_uuid($uuid); | 
| 215 |  |  |  |  |  |  | } | 
| 216 |  |  |  |  |  |  |  | 
| 217 | 2 |  |  |  |  | 1031 | $self->ncx->uid("urn:uuid:$uuid"); | 
| 218 | 2 |  |  |  |  | 949 | $self->metadata->set_book_id("urn:uuid:$uuid"); | 
| 219 | 2 |  |  |  |  | 978 | $self->_uuid($uuid); | 
| 220 |  |  |  |  |  |  | } | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  | sub add_identifier | 
| 223 |  |  |  |  |  |  | { | 
| 224 | 0 |  |  | 0 | 1 | 0 | my ($self, $ident, $scheme) = @_; | 
| 225 | 0 | 0 |  |  |  | 0 | if ($ident =~ /^urn:uuid:(.*)/i) { | 
| 226 | 0 |  |  |  |  | 0 | my $uuid = $1; | 
| 227 | 0 |  |  |  |  | 0 | $self->_set_uuid($uuid); | 
| 228 |  |  |  |  |  |  | } | 
| 229 |  |  |  |  |  |  | else { | 
| 230 | 0 |  |  |  |  | 0 | $self->metadata->add_identifier($ident, $scheme); | 
| 231 |  |  |  |  |  |  | } | 
| 232 |  |  |  |  |  |  | } | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | sub add_xhtml_entry | 
| 235 |  |  |  |  |  |  | { | 
| 236 | 4 |  |  | 4 | 0 | 11 | my ($self, $filename, %opts) = @_; | 
| 237 | 4 |  |  |  |  | 6 | my $linear = 1; | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | $linear = 0 if (defined ($opts{'linear'}) && | 
| 240 | 4 | 50 | 33 |  |  | 21 | $opts{'linear'} eq 'no'); | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  |  | 
| 243 | 4 |  |  |  |  | 9 | my $id = $self->nextid('ch'); | 
| 244 | 4 |  |  |  |  | 23 | $self->manifest->add_item( | 
| 245 |  |  |  |  |  |  | id          => $id, | 
| 246 |  |  |  |  |  |  | href        => $filename, | 
| 247 |  |  |  |  |  |  | media_type  => 'application/xhtml+xml', | 
| 248 |  |  |  |  |  |  | ); | 
| 249 |  |  |  |  |  |  |  | 
| 250 | 4 |  |  |  |  | 28 | $self->spine->add_itemref( | 
| 251 |  |  |  |  |  |  | idref       => $id, | 
| 252 |  |  |  |  |  |  | linear      => $linear, | 
| 253 |  |  |  |  |  |  | ); | 
| 254 |  |  |  |  |  |  |  | 
| 255 | 4 |  |  |  |  | 11 | return $id; | 
| 256 |  |  |  |  |  |  | } | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | sub add_stylesheet_entry | 
| 259 |  |  |  |  |  |  | { | 
| 260 | 2 |  |  | 2 | 0 | 4 | my ($self, $filename) = @_; | 
| 261 | 2 |  |  |  |  | 9 | my $id = $self->nextid('css'); | 
| 262 | 2 |  |  |  |  | 19 | $self->manifest->add_item( | 
| 263 |  |  |  |  |  |  | id          => $id, | 
| 264 |  |  |  |  |  |  | href        => $filename, | 
| 265 |  |  |  |  |  |  | media_type  => 'text/css', | 
| 266 |  |  |  |  |  |  | ); | 
| 267 |  |  |  |  |  |  |  | 
| 268 | 2 |  |  |  |  | 7 | return $id; | 
| 269 |  |  |  |  |  |  | } | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | sub add_image_entry | 
| 272 |  |  |  |  |  |  | { | 
| 273 | 1 |  |  | 1 | 0 | 2 | my ($self, $filename, $type) = @_; | 
| 274 |  |  |  |  |  |  | # trying to guess | 
| 275 | 1 | 50 |  |  |  | 5 | if (!defined($type)) { | 
| 276 | 0 | 0 | 0 |  |  | 0 | if (($filename =~ /\.jpg$/i) || ($filename =~ /\.jpeg$/i)) { | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 277 | 0 |  |  |  |  | 0 | $type = 'image/jpeg'; | 
| 278 |  |  |  |  |  |  | } | 
| 279 |  |  |  |  |  |  | elsif ($filename =~ /\.gif$/i) { | 
| 280 | 0 |  |  |  |  | 0 | $type = 'image/gif'; | 
| 281 |  |  |  |  |  |  | } | 
| 282 |  |  |  |  |  |  | elsif ($filename =~ /\.png$/i) { | 
| 283 | 0 |  |  |  |  | 0 | $type = 'image/png'; | 
| 284 |  |  |  |  |  |  | } | 
| 285 |  |  |  |  |  |  | elsif ($filename =~ /\.svg$/i) { | 
| 286 | 0 |  |  |  |  | 0 | $type = 'image/svg+xml'; | 
| 287 |  |  |  |  |  |  | } | 
| 288 |  |  |  |  |  |  | else { | 
| 289 | 0 |  |  |  |  | 0 | croak ("Unknown image type for file $filename"); | 
| 290 | 0 |  |  |  |  | 0 | return; | 
| 291 |  |  |  |  |  |  | } | 
| 292 |  |  |  |  |  |  | } | 
| 293 |  |  |  |  |  |  |  | 
| 294 | 1 |  |  |  |  | 3 | my $id = $self->nextid('img'); | 
| 295 | 1 |  |  |  |  | 7 | $self->manifest->add_item( | 
| 296 |  |  |  |  |  |  | id          => $id, | 
| 297 |  |  |  |  |  |  | href        => $filename, | 
| 298 |  |  |  |  |  |  | media_type  => $type, | 
| 299 |  |  |  |  |  |  | ); | 
| 300 |  |  |  |  |  |  |  | 
| 301 | 1 |  |  |  |  | 3 | return $id; | 
| 302 |  |  |  |  |  |  | } | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | sub add_entry | 
| 305 |  |  |  |  |  |  | { | 
| 306 | 1 |  |  | 1 | 0 | 4 | my ($self, $filename, $type) = @_; | 
| 307 | 1 |  |  |  |  | 5 | my $id = $self->nextid('item'); | 
| 308 | 1 |  |  |  |  | 9 | $self->manifest->add_item( | 
| 309 |  |  |  |  |  |  | id          => $id, | 
| 310 |  |  |  |  |  |  | href        => $filename, | 
| 311 |  |  |  |  |  |  | media_type  => $type, | 
| 312 |  |  |  |  |  |  | ); | 
| 313 |  |  |  |  |  |  |  | 
| 314 | 1 |  |  |  |  | 4 | return $id; | 
| 315 |  |  |  |  |  |  | } | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | sub add_xhtml { | 
| 318 | 4 |  |  | 4 | 1 | 56 | my ($self, $filename, $data, %opts) = @_; | 
| 319 | 4 |  |  |  |  | 17 | $self->_write_text([OPS => $filename], $data); | 
| 320 | 4 |  |  |  |  | 24 | return $self->add_xhtml_entry($filename, %opts); | 
| 321 |  |  |  |  |  |  | } | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | sub add_stylesheet { | 
| 324 | 2 |  |  | 2 | 1 | 383 | my ($self, $filename, $data) = @_; | 
| 325 | 2 |  |  |  |  | 11 | $self->_write_text([OPS => $filename], $data); | 
| 326 | 2 |  |  |  |  | 13 | return $self->add_stylesheet_entry($filename); | 
| 327 |  |  |  |  |  |  | } | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | sub add_image | 
| 330 |  |  |  |  |  |  | { | 
| 331 | 1 |  |  | 1 | 1 | 10 | my ($self, $filename, $data, $type) = @_; | 
| 332 | 1 |  |  |  |  | 5 | $self->_write_data([OPS => $filename], $data); | 
| 333 | 1 |  |  |  |  | 6 | return $self->add_image_entry($filename, $type); | 
| 334 |  |  |  |  |  |  | } | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | sub add_data | 
| 337 |  |  |  |  |  |  | { | 
| 338 | 1 |  |  | 1 | 0 | 7 | my ($self, $filename, $data, $type) = @_; | 
| 339 | 1 |  |  |  |  | 5 | $self->_write_data([OPS => $filename], $data); | 
| 340 | 1 |  |  |  |  | 6 | return $self->add_entry($filename, $type); | 
| 341 |  |  |  |  |  |  | } | 
| 342 |  |  |  |  |  |  |  | 
| 343 |  |  |  |  |  |  | sub copy_xhtml | 
| 344 |  |  |  |  |  |  | { | 
| 345 | 0 |  |  | 0 | 1 | 0 | my ($self, $src_filename, $filename, %opts) = @_; | 
| 346 | 0 |  |  |  |  | 0 | my $tmpdir = $self->tmpdir; | 
| 347 | 0 | 0 |  |  |  | 0 | if (mkdir_and_copy($src_filename, "$tmpdir/OPS/$filename")) { | 
| 348 | 0 |  |  |  |  | 0 | return $self->add_xhtml_entry($filename, %opts); | 
| 349 |  |  |  |  |  |  | } | 
| 350 |  |  |  |  |  |  | else { | 
| 351 | 0 |  |  |  |  | 0 | carp ("Failed to copy $src_filename to $tmpdir/OPS/$filename"); | 
| 352 |  |  |  |  |  |  | } | 
| 353 |  |  |  |  |  |  |  | 
| 354 | 0 |  |  |  |  | 0 | return; | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | sub copy_stylesheet | 
| 358 |  |  |  |  |  |  | { | 
| 359 | 0 |  |  | 0 | 1 | 0 | my ($self, $src_filename, $filename) = @_; | 
| 360 | 0 |  |  |  |  | 0 | my $tmpdir = $self->tmpdir; | 
| 361 | 0 | 0 |  |  |  | 0 | if (mkdir_and_copy($src_filename, "$tmpdir/OPS/$filename")) { | 
| 362 | 0 |  |  |  |  | 0 | return $self->add_stylesheet_entry("$filename"); | 
| 363 |  |  |  |  |  |  | } | 
| 364 |  |  |  |  |  |  | else { | 
| 365 | 0 |  |  |  |  | 0 | carp ("Failed to copy $src_filename to $tmpdir/OPS/$filename"); | 
| 366 |  |  |  |  |  |  | } | 
| 367 |  |  |  |  |  |  |  | 
| 368 | 0 |  |  |  |  | 0 | return; | 
| 369 |  |  |  |  |  |  | } | 
| 370 |  |  |  |  |  |  |  | 
| 371 |  |  |  |  |  |  | sub copy_image | 
| 372 |  |  |  |  |  |  | { | 
| 373 | 0 |  |  | 0 | 1 | 0 | my ($self, $src_filename, $filename, $type) = @_; | 
| 374 | 0 |  |  |  |  | 0 | my $tmpdir = $self->tmpdir; | 
| 375 | 0 | 0 |  |  |  | 0 | if (mkdir_and_copy($src_filename, "$tmpdir/OPS/$filename")) { | 
| 376 | 0 |  |  |  |  | 0 | return $self->add_image_entry("$filename"); | 
| 377 |  |  |  |  |  |  | } | 
| 378 |  |  |  |  |  |  | else { | 
| 379 | 0 |  |  |  |  | 0 | carp ("Failed to copy $src_filename to $tmpdir/OPS/$filename"); | 
| 380 |  |  |  |  |  |  | } | 
| 381 |  |  |  |  |  |  |  | 
| 382 | 0 |  |  |  |  | 0 | return; | 
| 383 |  |  |  |  |  |  | } | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | sub copy_file | 
| 386 |  |  |  |  |  |  | { | 
| 387 | 0 |  |  | 0 | 1 | 0 | my ($self, $src_filename, $filename, $type) = @_; | 
| 388 | 0 |  |  |  |  | 0 | my $tmpdir = $self->tmpdir; | 
| 389 | 0 | 0 |  |  |  | 0 | if (mkdir_and_copy($src_filename, "$tmpdir/OPS/$filename")) { | 
| 390 | 0 |  |  |  |  | 0 | my $id = $self->nextid('id'); | 
| 391 | 0 |  |  |  |  | 0 | $self->manifest->add_item( | 
| 392 |  |  |  |  |  |  | id          => $id, | 
| 393 |  |  |  |  |  |  | href        => "$filename", | 
| 394 |  |  |  |  |  |  | media_type  => $type, | 
| 395 |  |  |  |  |  |  | ); | 
| 396 | 0 |  |  |  |  | 0 | return $id; | 
| 397 |  |  |  |  |  |  | } | 
| 398 |  |  |  |  |  |  | else { | 
| 399 | 0 |  |  |  |  | 0 | carp ("Failed to copy $src_filename to $tmpdir/OPS/$filename"); | 
| 400 |  |  |  |  |  |  | } | 
| 401 |  |  |  |  |  |  |  | 
| 402 | 0 |  |  |  |  | 0 | return; | 
| 403 |  |  |  |  |  |  | } | 
| 404 |  |  |  |  |  |  |  | 
| 405 |  |  |  |  |  |  | sub encrypt_file | 
| 406 |  |  |  |  |  |  | { | 
| 407 | 1 |  |  | 1 | 1 | 13 | my ($self, $src_filename, $filename, $type) = @_; | 
| 408 | 1 |  |  |  |  | 3 | my $tmpdir = $self->tmpdir; | 
| 409 | 1 | 50 |  |  |  | 28 | if (!defined($self->_encryption_key)) { | 
| 410 | 0 |  |  |  |  | 0 | croak "Can't encrypt without a key: no urn:uuid: indetifier has been provided"; | 
| 411 |  |  |  |  |  |  | } | 
| 412 |  |  |  |  |  |  |  | 
| 413 | 1 |  |  |  |  | 22 | my $key = $self->_encryption_key; | 
| 414 | 1 | 50 |  |  |  | 8 | if (adobe_encrypt($src_filename, "$tmpdir/OPS/$filename", $key)) { | 
| 415 | 1 |  |  |  |  | 8 | my $id = $self->nextid('id'); | 
| 416 | 1 |  |  |  |  | 15 | $self->manifest->add_item( | 
| 417 |  |  |  |  |  |  | id          => $id, | 
| 418 |  |  |  |  |  |  | href        => "$filename", | 
| 419 |  |  |  |  |  |  | media_type  => $type, | 
| 420 |  |  |  |  |  |  | ); | 
| 421 | 1 |  |  |  |  | 7 | $self->add_encrypted_fileref("OPS/$filename"); | 
| 422 | 1 |  |  |  |  | 5 | return $id; | 
| 423 |  |  |  |  |  |  | } | 
| 424 |  |  |  |  |  |  | else { | 
| 425 | 0 |  |  |  |  | 0 | carp ("Failed to copy $src_filename to $tmpdir/OPS/$filename"); | 
| 426 |  |  |  |  |  |  | } | 
| 427 |  |  |  |  |  |  |  | 
| 428 | 0 |  |  |  |  | 0 | return; | 
| 429 |  |  |  |  |  |  | } | 
| 430 |  |  |  |  |  |  |  | 
| 431 |  |  |  |  |  |  |  | 
| 432 |  |  |  |  |  |  | sub nextid | 
| 433 |  |  |  |  |  |  | { | 
| 434 | 9 |  |  | 9 | 0 | 18 | my ($self, $prefix) = @_; | 
| 435 | 9 |  |  |  |  | 12 | my $id; | 
| 436 |  |  |  |  |  |  |  | 
| 437 | 9 | 50 |  |  |  | 20 | $prefix = 'id' unless(defined($prefix)); | 
| 438 | 9 | 100 |  |  |  | 10 | if (defined(${$self->id_counters}{$prefix})) { | 
|  | 9 |  |  |  |  | 46 |  | 
| 439 | 2 |  |  |  |  | 3 | $id = "$prefix" . ${$self->id_counters}{$prefix}; | 
|  | 2 |  |  |  |  | 8 |  | 
| 440 | 2 |  |  |  |  | 2 | ${$self->id_counters}{$prefix}++; | 
|  | 2 |  |  |  |  | 6 |  | 
| 441 |  |  |  |  |  |  | } | 
| 442 |  |  |  |  |  |  | else | 
| 443 |  |  |  |  |  |  | { | 
| 444 |  |  |  |  |  |  | # First usage of prefix | 
| 445 | 7 |  |  |  |  | 11 | $id = "${prefix}1"; | 
| 446 | 7 |  |  |  |  | 10 | ${$self->id_counters}{$prefix} = 2; | 
|  | 7 |  |  |  |  | 19 |  | 
| 447 |  |  |  |  |  |  | } | 
| 448 |  |  |  |  |  |  |  | 
| 449 | 9 |  |  |  |  | 22 | return $id; | 
| 450 |  |  |  |  |  |  | } | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  | sub pack_zip | 
| 453 |  |  |  |  |  |  | { | 
| 454 | 2 |  |  | 2 | 1 | 22 | my ($self, $filename) = @_; | 
| 455 | 2 |  |  |  |  | 8 | my $tmpdir = $self->tmpdir; | 
| 456 | 2 |  |  |  |  | 16 | $self->write_ncx; | 
| 457 | 2 |  |  |  |  | 14 | $self->write_opf; | 
| 458 | 2 |  |  |  |  | 23 | my $container = EBook::EPUB::Lite::Container::Zip->new($filename); | 
| 459 | 2 |  |  |  |  | 16 | $container->add_path($tmpdir . "/OPS", "OPS/"); | 
| 460 | 2 |  |  |  |  | 27 | $container->add_root_file("OPS/content.opf", "application/oebps-package+xml"); | 
| 461 | 2 |  |  |  |  | 8 | foreach my $fref ($self->encrypted_filerefs) { | 
| 462 | 1 |  |  |  |  | 7 | $container->add_encrypted_path($fref); | 
| 463 |  |  |  |  |  |  | } | 
| 464 | 2 |  |  |  |  | 9 | return $container->write(); | 
| 465 |  |  |  |  |  |  | } | 
| 466 |  |  |  |  |  |  |  | 
| 467 |  |  |  |  |  |  | sub write_opf { | 
| 468 | 2 |  |  | 2 | 0 | 6 | my ($self) = @_; | 
| 469 | 2 |  |  |  |  | 14 | $self->_write_text([OPS => 'content.opf' ], $self->to_xml); | 
| 470 |  |  |  |  |  |  | } | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | sub write_ncx { | 
| 473 | 2 |  |  | 2 | 0 | 4 | my ($self) = @_; | 
| 474 | 2 |  |  |  |  | 20 | $self->_write_text([OPS => 'toc.ncx'], $self->ncx->to_xml) | 
| 475 |  |  |  |  |  |  | } | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | # helper function that performs Adobe content protection "encryption" | 
| 478 |  |  |  |  |  |  | sub adobe_encrypt | 
| 479 |  |  |  |  |  |  | { | 
| 480 | 1 |  |  | 1 | 0 | 3 | my ($src, $dst, $key) = @_; | 
| 481 | 1 |  |  |  |  | 8 | my @key_bytes = unpack "C*", $key; | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | # open source/destination files for read/write | 
| 484 | 1 | 50 |  |  |  | 42 | open (my $in,  '<', $src) or return; | 
| 485 | 1 | 50 |  |  |  | 133 | open (my $out, '>', $dst) or return; | 
| 486 | 1 |  |  |  |  | 4 | binmode $in; | 
| 487 | 1 |  |  |  |  | 3 | binmode $out; | 
| 488 |  |  |  |  |  |  |  | 
| 489 |  |  |  |  |  |  | # XOR first 1024 bytes of file by provided key | 
| 490 | 1 |  |  |  |  | 2 | my $data; | 
| 491 | 1 |  |  |  |  | 22 | read($in, $data, 1024); | 
| 492 | 1 |  |  |  |  | 112 | my @bytes = unpack ("C*", $data); | 
| 493 | 1 |  |  |  |  | 15 | my $key_ptr = 0; | 
| 494 | 1 |  |  |  |  | 3 | foreach my $d (@bytes) { | 
| 495 | 1024 |  |  |  |  | 738 | $d = $d ^ $key_bytes[$key_ptr]; | 
| 496 | 1024 |  |  |  |  | 719 | $key_ptr += 1; | 
| 497 | 1024 |  |  |  |  | 942 | $key_ptr = $key_ptr % @key_bytes; | 
| 498 |  |  |  |  |  |  | } | 
| 499 |  |  |  |  |  |  |  | 
| 500 | 1 |  |  |  |  | 36 | my $crypted_data = pack "C*", @bytes; | 
| 501 | 1 |  |  |  |  | 13 | print $out $crypted_data; | 
| 502 |  |  |  |  |  |  |  | 
| 503 |  |  |  |  |  |  | # Copy th erest of the file, 1M buffer seems to be reasonable default | 
| 504 | 1 |  |  |  |  | 29 | while (read($in, $data, 1024*1024)) { | 
| 505 | 1 |  |  |  |  | 6 | print $out $data; | 
| 506 |  |  |  |  |  |  | } | 
| 507 |  |  |  |  |  |  |  | 
| 508 | 1 |  |  |  |  | 11 | close $in; | 
| 509 | 1 |  |  |  |  | 90 | close $out; | 
| 510 |  |  |  |  |  |  | } | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | sub _write_text { | 
| 513 | 10 |  |  | 10 |  | 23 | my ($self, $path, $data) = @_; | 
| 514 | 10 |  |  |  |  | 32 | my $filename = File::Spec->catfile($self->tmpdir, @$path); | 
| 515 |  |  |  |  |  |  | # print "Writing $filename\n"; | 
| 516 | 10 | 50 |  | 2 |  | 997 | open (my $fh, '>:encoding(UTF-8)', $filename) | 
|  | 2 |  |  |  |  | 13 |  | 
|  | 2 |  |  |  |  | 2 |  | 
|  | 2 |  |  |  |  | 16 |  | 
| 517 |  |  |  |  |  |  | or die "Failed to open $filename $!"; | 
| 518 | 10 |  |  |  |  | 21497 | print $fh $data; | 
| 519 | 10 |  |  |  |  | 507 | close $fh; | 
| 520 |  |  |  |  |  |  | } | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  | sub _write_data { | 
| 523 | 2 |  |  | 2 |  | 3 | my ($self, $path, $data) = @_; | 
| 524 | 2 |  |  |  |  | 5 | my $filename = File::Spec->catfile($self->tmpdir, @$path); | 
| 525 | 2 | 50 |  |  |  | 141 | open (my $fh, '>', $filename) | 
| 526 |  |  |  |  |  |  | or die "Failed to open $filename $!"; | 
| 527 | 2 |  |  |  |  | 5 | binmode $fh; | 
| 528 | 2 |  |  |  |  | 7 | print $fh $data; | 
| 529 | 2 |  |  |  |  | 82 | close $fh; | 
| 530 |  |  |  |  |  |  | } | 
| 531 |  |  |  |  |  |  |  | 
| 532 |  |  |  |  |  |  | sub mkdir_and_copy { | 
| 533 | 0 |  |  | 0 | 0 | 0 | my ($from, $to) = @_; | 
| 534 | 0 |  |  |  |  | 0 | mkpath(dirname($to)); | 
| 535 | 0 |  |  |  |  | 0 | return copy($from, $to); | 
| 536 |  |  |  |  |  |  | } | 
| 537 |  |  |  |  |  |  |  | 
| 538 |  |  |  |  |  |  | 1; | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | __END__ |