File Coverage

blib/lib/BeamerReveal/Object/BeamerFrame.pm
Criterion Covered Total %
statement 21 242 8.6
branch 0 96 0.0
condition 0 22 0.0
subroutine 7 13 53.8
pod 3 4 75.0
total 31 377 8.2


line stmt bran cond sub pod time code
1             # -*- cperl -*-
2             # ABSTRACT: BeamerFrame object
3              
4              
5             package BeamerReveal::Object::BeamerFrame;
6             our $VERSION = '20260408.1240'; # VERSION
7              
8 1     1   2194 use parent 'BeamerReveal::Object';
  1         1  
  1         6  
9 1     1   57 use Carp;
  1         2  
  1         71  
10              
11 1     1   4 use Data::Dumper;
  1         1  
  1         39  
12              
13 1     1   5 use BeamerReveal::TemplateStore;
  1         1  
  1         21  
14 1     1   3 use BeamerReveal::MediaManager;
  1         1  
  1         29  
15              
16 1     1   4 use BeamerReveal::Log;
  1         1  
  1         20  
17              
18 1     1   430 use Digest::SHA;
  1         2599  
  1         3143  
19              
20              
21             our $maxRawPage;
22             our $maxNotePage = 0;
23             our $embeddedID;
24              
25 0     0 0   sub nofdigits { length( "$_[0]" ) }
26              
27             sub new {
28 0     0 1   my $class = shift;
29 0           my ( $chunkData, $lines, $lineCtr, $rawpage ) = @_;
30            
31 0 0         $class = (ref $class ? ref $class : $class );
32 0           my $self = {};
33 0           bless $self, $class;
34              
35 0           my $logger = $BeamerReveal::Log::logger;
36            
37 0           $self->{videos} = [];
38 0           $self->{audios} = [];
39 0           $self->{images} = [];
40 0           $self->{iframes} = [];
41 0           $self->{animations} = [];
42 0           $self->{stills} = [];
43 0           $self->{voiceovers} = [];
44 0           $self->{hasnotes} = 0;
45 0           $self->{mtoracle} = MIME::Types->new();
46              
47 0           ++$lineCtr;
48 0           for ( my $i = 0; $i < @$lines; ++$i ) {
49 0 0         ( $lines->[$i] =~ /^-(?\w+):(?.*)$/ )
50             or $logger->fatal( "Error: syntax incorrect in rvl file on line $lineCtr '$lines->[$i]'\n" );
51 0 0         if ( $+{command} eq 'parameters' ) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
52 0           $self->{parameters} = BeamerReveal::Object::readParameterLine( $+{payload} );
53             # The correctness of this relies on the pages appearing in order in the .rvl file
54             # however, the rawpage counter of LaTeX is not all cases a true arabic counter and
55             # therefore not reliable
56 0           $self->{parameters}->{rawpage} = $rawpage;
57 0           $maxRawPage = $rawpage;
58             } elsif ( $+{command} eq 'video' ) {
59 0           push @{$self->{videos}}, BeamerReveal::Object::readParameterLine( $+{payload} );
  0            
60             } elsif ( $+{command} eq 'audio' ) {
61 0           push @{$self->{audios}}, BeamerReveal::Object::readParameterLine( $+{payload} );
  0            
62             } elsif ( $+{command} eq 'image' ) {
63 0           push @{$self->{images}}, BeamerReveal::Object::readParameterLine( $+{payload} );
  0            
64             } elsif ( $+{command} eq 'iframe' ) {
65 0           push @{$self->{iframes}}, BeamerReveal::Object::readParameterLine( $+{payload} );
  0            
66             } elsif ( $+{command} eq 'animation' ) {
67 0           push @{$self->{animations}}, BeamerReveal::Object::readParameterLine( $+{payload} );
  0            
68 0           $self->{animations}->[-1]->{tex} = $lines->[++$i];
69             } elsif ( $+{command} eq 'still' ) {
70 0           push @{$self->{stills}}, BeamerReveal::Object::readParameterLine( $+{payload} );
  0            
71 0           $self->{stills}->[-1]->{tex} = $lines->[++$i];
72             } elsif ( $+{command} eq 'voiceover' ) {
73 0           push @{$self->{voiceovers}}, BeamerReveal::Object::readParameterLine( $+{payload} );
  0            
74             } elsif ( $+{command} eq 'hasnote' ) {
75 0           $self->{hasnotes} = 1;
76             } else {
77 0           $logger->fatal( "Error: unknown BeamerFrame data on line @{[ $lineCtr + $i ]} '$lines->[$i]'\n" );
  0            
78             }
79             }
80 0 0         ++$maxNotePage if $self->{hasnotes};
81            
82 0 0         $embeddedID = 0 unless defined( $embeddedID );
83            
84 0           return $self;
85             }
86              
87              
88              
89             sub extractContentToGenerate {
90 0     0 1   my $self = shift;
91 0           my ( $i, $mediaManager, $presentation ) = @_;
92              
93 0           my $logger = $BeamerReveal::Log::logger;
94 0           $logger->log( 2, "- extracting content to generate from slide $i" );
95              
96 0           my $contentToGenerate = {
97             animations => [],
98             stills => [],
99             voiceovers => [],
100             };
101            
102             #########################
103             # process all animations
104 0           foreach my $animation (@{$self->{animations}}) {
  0            
105 0           $logger->log( 4, "- extracting animation" );
106            
107             # 1. Generate the animation
108 0           push @{$contentToGenerate->{animations}}, $mediaManager->animationRegisterInStore( $animation );
  0            
109             }
110              
111             #####################
112             # process all stills
113 0           foreach my $still (@{$self->{stills}}) {
  0            
114 0           $logger->log( 4, "- adding still" );
115              
116             # 1. Generate the animation
117 0           push @{$contentToGenerate->{stills}}, $mediaManager->stillRegisterInStore( $still );
  0            
118             }
119              
120             #########################
121             # process all voiceoversg
122 0           foreach my $voiceover (@{$self->{voiceovers}}) {
  0            
123 0           $logger->log( 4, "- adding voiceover" );
124              
125             # 1. Generate the animation
126 0           push @{$contentToGenerate->{voiceovers}}, $mediaManager->voiceoverRegisterInStore( $voiceover );
  0            
127             }
128              
129 0           return $contentToGenerate;
130             }
131              
132              
133              
134              
135             sub makeSlide {
136 0     0 1   my $self = shift;
137 0           my ( $i, $mediaManager, $presentation, $generatedContent ) = @_;
138              
139 0           my $logger = $BeamerReveal::Log::logger;
140 0           $logger->log( 2, "- making slide $i" );
141            
142 0           my $templateStore = BeamerReveal::TemplateStore->new();
143 0           my $content = '';
144              
145             my $slideEmbed =
146 0 0         exists $self->{parameters}->{embed} or exists $presentation->{parameters}->{embed};
147            
148             ###############################################
149             # process all video material / can be embedded
150 0           foreach my $video (@{$self->{videos}}) {
  0            
151             my %commonStamps = ( X => _topercent( $video->{x} ),
152             Y => _topercent( $video->{y} ),
153             W => _topercent( $video->{width} ),
154             H => _topercent( $video->{height} ),
155             VIDEOID => 'embedded-id-' . $embeddedID++,
156             VIDEOEMBEDDEDB64 => $videoContent,
157             MIMETYPE => $mimeType,
158             FIT => $video->{fit},
159             AUTOPLAY => exists $video->{autoplay} ? 'data-autoplay' : '',
160             CONTROLS => exists $video->{controls} ? 'controls' : '',
161             MUTED => exists $video->{muted} ? 'muted' : '',
162 0 0         LOOP => exists $video->{loop} ? 'loop' : '',
    0          
    0          
    0          
163             );
164 0           my $vStamps;
165             my $vTemplate;
166 0 0 0       if ( exists $video->{embed} or $slideEmbed ) {
167 0           $logger->log( 4, "- adding embedded video" );
168 0           $vTemplate = $templateStore->fetch( 'html', 'video-embedded.html' );
169             my ( $mimeType ) = $mediaManager->videoFromStore( $video->{file},
170 0           to_embed => 1 );
171 0           $vStamps = { %commonStamps,
172             VIDEOID => 'embedded-id-' . $embeddedID++,
173             VIDEOEMBEDDEDB64 => $videoContent,
174             MIMETYPE => $mimeType,
175             };
176             } else {
177 0           $logger->log( 4, "- adding video" );
178 0           $vTemplate = $templateStore->fetch( 'html', 'video.html' );
179 0           my $videoFile;
180 0 0         if ( $video->{file} =~ /^https?:\/\// ) {
181 0           $videoFile = $video->{file};
182             }
183             else {
184 0           ( undef, $videoFile ) = $mediaManager->videoFromStore( $video->{file} );
185             }
186 0           $vStamps = { %commonStamps,
187             VIDEO => $videoFile
188             };
189             }
190 0           $content .= BeamerReveal::TemplateStore::stampTemplate( $vTemplate, $vStamps);
191             }
192              
193             ###############################################
194             # process all audio material / can be embedded
195 0           my $voCounter = 0;
196 0           foreach my $audio (@{$self->{audios}}) {
  0            
197             my %commonStamps = ( X => _topercent( $audio->{x} ),
198             Y => _topercent( $audio->{y} ),
199             W => _topercent( $audio->{width} ),
200             H => _topercent( $audio->{height} ),
201             FIT => $audio->{fit},
202             AUTOPLAY => exists $audio->{autoplay} ? 'data-autoplay' : '',
203             CONTROLS => exists $audio->{controls} ? 'controls' : '',
204             MUTED => exists $audio->{muted} ? 'muted' : '',
205 0 0         LOOP => exists $audio->{loop} ? 'loop' : '' );
    0          
    0          
    0          
206 0           my $aStamps;
207             my $aTemplate;
208              
209 0 0 0       if ( exists $audio->{embed} or $slideEmbed ) {
210 0           $logger->log( 4, "- adding embedded audio" );
211 0           $aTemplate = $templateStore->fetch( 'html', 'audio-embedded.html' );
212 0           my ( $mimeType, $audioContent );
213 0 0         if ( $audio->{file} =~ /^voiceover-/ ) {
214             ( $mimeType, $audioContent ) =
215 0           $mediaManager->voiceoverFromStore( $generatedContent->{voiceovers}->[$voCounter++],
216             to_embed => 1 );
217             }
218             else {
219             ( $mimeType, $audioContent ) = $mediaManager->audioFromStore( $audio->{file},
220 0           to_embed => 1 );
221             }
222 0           $aStamps = { %commonStamps,
223             AUDIOID => 'embedded-id-' . $embeddedID++,
224             AUDIOEMBEDDEDB64 => $audioContent,
225             MIMETYPE => $mimeType,
226             };
227             } else {
228 0           $logger->log( 4, "- adding audio" );
229 0           $aTemplate = $templateStore->fetch( 'html', 'audio.html' );
230 0           my $audioFile;
231 0 0         if ( $audio->{file} =~ /^https?:\/\// ) {
232 0           $audioFile = $audio->{file};
233             }
234             else {
235 0 0         if ( $audio->{file} =~ /^voiceover-/ ) {
236             ( undef, $audioFile ) =
237 0           $mediaManager->voiceoverFromStore( $generatedContent->{voiceovers}->[$voCounter++] );
238             }
239             else {
240 0           ( undef, $audioFile ) = $mediaManager->audioFromStore( $audio->{file} );
241             }
242             }
243 0           $aStamps = { %commonStamps,
244             AUDIO => $audioFile,
245             };
246             }
247 0           $content .= BeamerReveal::TemplateStore::stampTemplate( $aTemplate, $aStamps );
248             }
249              
250             ###############################################
251             # process all image material / can be embedded
252 0           foreach my $image (@{$self->{images}}) {
  0            
253             my %commonStamps = ( X => _topercent( $image->{x} ),
254             Y => _topercent( $image->{y} ),
255             W => _topercent( $image->{width} ),
256             H => _topercent( $image->{height} ),
257             IMAGEID => 'embedded-id-' . $embeddedID++,
258             IMAGEEMBEDDEDB64 => $imageContent,
259             MIMETYPE => $mimeType,
260             FIT => $image->{fit}
261 0           );
262 0           my $iStamps;
263             my $iTemplate;
264 0 0 0       if ( exists $image->{embed} or $slideEmbed ) {
265 0           $logger->log( 4, "- adding embedded image" );
266 0           $iTemplate = $templateStore->fetch( 'html', 'image-embedded.html' );
267             my( $mimeType, $imageContent ) = $mediaManager->imageFromStore( $image->{file},
268 0           to_embed => 1 );
269 0           $iStamps = { %commonStamps,
270             IMAGEID => 'embedded-id-' . $embeddedID++,
271             IMAGEEMBEDDEDB64 => $imageContent,
272             MIMETYPE => $mimeType,
273             };
274             } else {
275 0           $logger->log( 4, "- adding image" );
276 0           $iTemplate = $templateStore->fetch( 'html', 'image.html' );
277 0           my $imageFile;
278 0 0         if ( $image->{file} =~ /^https?:\/\// ) {
279 0           $imageFile = $image->{file};
280             }
281             else {
282 0           ( undef, $imageFile ) = $mediaManager->imageFromStore( $image->{file} );
283             }
284 0           $iStamps = { %commonStamps,
285             IMAGE => $imageFile };
286             }
287 0           $content .= BeamerReveal::TemplateStore::stampTemplate( $iTemplate,
288             $iStamps );
289             }
290              
291             ################################################
292             # process all iframe material / can be embedded
293 0           foreach my $iframe (@{$self->{iframes}}) {
  0            
294             my %commonStamps = ( X => _topercent( $iframe->{x} ),
295             Y => _topercent( $iframe->{y} ),
296             W => _topercent( $iframe->{width} ),
297             H => _topercent( $iframe->{height} ),
298             FIT => $iframe->{fit}
299 0           );
300 0           my $iStamps;
301             my $iTemplate;
302 0 0 0       if ( exists $iframe->{embed} or exists $presentation->{parameters}->{embed} ) {
303 0           $logger->log( 4, "- adding embedded iframe" );
304 0           $iTemplate = $templateStore->fetch( 'html', 'iframe-embedded.html' );
305             my ( $mimeType, $iframeContent ) = $mediaManager->iframeFromStore( $iframe->{file},
306 0           to_embed => 1 );
307 0           $iStamps = { %commonStamps,
308             IFRAMEID => 'embedded-id-' . $embeddedID++,
309             IFRAMEEMBEDDEDB64 => $iframeContent,
310             };
311             } else {
312 0           $logger->log( 4, "- adding iframe" );
313 0           $iTemplate = $templateStore->fetch( 'html', 'iframe.html' );
314 0           my $iframeFile;
315 0 0         if ( $iframe->{file} =~ /^https?:\/\// ) {
316 0           $iframeFile = $iframe->{file};
317             }
318             else {
319 0           ( undef, $iframeFile ) = $mediaManager->iframeFromStore( $iframe->{file} );
320             }
321 0           $iStamps = { %commonStamps,
322             IFRAME => $iframeFile,
323             };
324             }
325 0           $content .= BeamerReveal::TemplateStore::stampTemplate( $iTemplate,
326             $iStamps );
327             }
328              
329             ###########################################
330             # process all animations / can be embedded
331 0           my $aCounter = 0;
332 0           foreach my $animation (@{$self->{animations}}) {
  0            
333             my %commonStamps = (X => _topercent( $animation->{x} ),
334             Y => _topercent( $animation->{y} ),
335             W => _topercent( $animation->{width} ),
336             H => _topercent( $animation->{height} ),
337             AUTOPLAY => exists $animation->{autoplay} ? 'data-autoplay' : '',
338             CONTROLS => exists $animation->{controls} ? 'controls' : '',
339             LOOP => exists $animation->{loop} ? 'loop' : '',
340             FIT => $animation->{fit},
341 0 0         );
    0          
    0          
342            
343 0           my $aTemplate;
344             my $aStamps;
345 0 0 0       if ( exists $animation->{embed} or $slideEmbed ) {
346 0           $logger->log( 4, "- adding embedded animation" );
347 0           $aTemplate = $templateStore->fetch( 'html', 'animation-embedded.html' );
348             my ( $mimeType, $videoContent ) =
349 0           $mediaManager->animationFromStore( $generatedContent->{animations}->[$aCounter++],
350             to_embed => 1 );
351             $aStamps =
352             {
353             %commonStamps,
354             ANIMATIONID => 'embedded-id-' . $embeddedID++,
355             ANIMATIONEMBEDDEDB64 => $videoContent,
356             MIMETYPE => $mimeType,
357             FIT => $image->{fit}
358 0           };
359             }
360             else {
361 0           $logger->log( 4, "- adding animation: " . $generatedContent->{animations}->[$aCounter] );
362 0           $aTemplate = $templateStore->fetch( 'html', 'animation.html' );
363             my ( $undef, $file ) =
364 0           $mediaManager->animationFromStore( $generatedContent->{animations}->[$aCounter++] );
365            
366 0           $aStamps =
367             {
368             %commonStamps,
369             ANIMATION => $file,
370             };
371             }
372 0           $content .= BeamerReveal::TemplateStore::stampTemplate( $aTemplate,
373             $aStamps );
374             }
375              
376             #######################################
377             # process all stills / can be embedded
378 0           my $sCounter = 0;
379 0           foreach my $still (@{$self->{stills}}) {
  0            
380             my %commonStamps = ( X => _topercent( $still->{x} ),
381             Y => _topercent( $still->{y} ),
382             W => _topercent( $still->{width} ),
383             H => _topercent( $still->{height} ),
384             FIT => $still->{fit},
385 0           );
386              
387 0           my $sTemplate;
388             my $sStamps;
389 0 0 0       if ( exists $still->{embed} or $slideEmbed ) {
390 0           $logger->log( 4, "- adding embedded still" );
391 0           $sTemplate = $templateStore->fetch( 'html', 'still-embedded.html' );
392             my ( $mimeType, $videoContent ) =
393 0           $mediaManager->stillFromStore( $generatedContent->{stills}->[$sCounter++],
394             to_embed => 1 );
395             $sStamps =
396             {
397             %commonStamps,
398             STILLID => 'embedded-id-' . $embeddedID++,
399             STILLEMBEDDEDB64 => $videoContent,
400             MIMETYPE => $mimeType,
401             FIT => $image->{fit}
402 0           };
403             }
404             else {
405 0           $logger->log( 4, "- adding still: " . $generatedContent->{stills}->[$sCounter] );
406 0           $sTemplate = $templateStore->fetch( 'html', 'still.html' );
407             my ( undef, $file ) =
408 0           $mediaManager->stillFromStore( $generatedContent->{stills}->[$sCounter++] );
409              
410 0           $sStamps =
411             {
412             %commonStamps,
413             STILL => $file,
414             };
415             }
416 0           $content .= BeamerReveal::TemplateStore::stampTemplate( $sTemplate,
417             $sStamps );
418             }
419              
420             ###########################
421             # process the frame itself
422              
423             # title
424 0           my $title = _modernize( $self->{parameters}->{title} );
425 0 0         if ( $title =~ /\\/ ) {
426 0           $logger->fatal( "Error: the title of slide $i contains TeX-like code (observe below between <<< >>>); provide a clean version using \\frametitle[clean-version]{TeX-like code}\n" .
427             "<<< $self->{parameters}->{title} >>>" );
428             } else {
429 0           $self->{parameters}->{title} = $title;
430             }
431             ;
432              
433             # menu entries
434 0           my $menuTitle;
435 0 0         if ( exists $self->{parameters}->{toc} ) {
436 0 0         if ( $self->{parameters}->{toc} eq 'titlepage' ) {
    0          
    0          
437 0           $menuTitle = "%s";
438             } elsif ( $self->{parameters}->{toc} eq 'section' ) {
439 0           $menuTitle = "• %s";
440             } elsif ( $self->{parameters}->{toc} eq 'subsection' ) {
441 0           $menuTitle = "∘ %s";
442             } else {
443 0           $logger->fatal( "Error: invalid toc parameter in rvl file" );
444             }
445             } else {
446 0           $menuTitle = "• %s";
447             }
448             $menuTitle = sprintf( $menuTitle,
449 0           $self->{parameters}->{title} );
450              
451             # autoSlide
452 0           my $autoSlide = '';
453 0 0         if( exists $self->{parameters}->{autoslide} ) {
454 0           $autoSlide = 'data-autoslide="' . $self->{parameters}->{autoslide} . '"';
455             }
456            
457             # notes
458 0           my $notes = ' '; # the space is important as default, if you remove it, the notes will be sticky!
459            
460 0 0         if ( $self->{hasnotes} ) {
461 0 0         if ( $slideEmbed ) {
462 0           my $nTemplate = $templateStore->fetch( 'html', 'note-embedded.html' );
463            
464             # remember: the hasnote entry holds the note number
465             my ( $mimeType, $notesImage ) = $mediaManager->noteFromStore( $self->{hasnotes},
466 0           to_embed => 1 );
467            
468 0           my $nStamps =
469             {
470             NOTEID => 'embedded-id-' . $embeddedID++,
471             NOTEEMBEDDEDB64 => $notesImage,
472             MIMETYPE => $mimeType,
473             };
474            
475 0           $notes = BeamerReveal::TemplateStore::stampTemplate( $nTemplate, $nStamps );
476             }
477             else {
478 0           my $nTemplate = $templateStore->fetch( 'html', 'note.html' );
479            
480             # remember: the hasnote entry holds the note number
481 0           my ( undef, $notesImage ) = $mediaManager->noteFromStore( $self->{hasnotes} );
482            
483 0           my $nStamps =
484             {
485             NOTESIMAGE => $notesImage
486             };
487            
488 0           $notes = BeamerReveal::TemplateStore::stampTemplate( $nTemplate, $nStamps );
489             }
490             }
491              
492             # generate slide itself
493 0 0         if ( $slideEmbed ) {
494 0           my $fTemplate = $templateStore->fetch( 'html', 'beamerframe-embedded.html' );
495            
496             my ( $mimeType, $slideImage ) = $mediaManager->slideFromStore( $self->{parameters}->{rawpage},
497 0           to_embed => 1 );
498             my $fStamps =
499             {
500             DATAMENUTITLE => $menuTitle,
501             SLIDEID => 'embedded-id-' . $embeddedID++,
502             SLIDEEMBEDDEDB64 => $slideImage,
503             MIMETYPE => $mimeType,
504             SLIDECONTENT => $content,
505 0   0       TRANSITION => $self->{parameters}->{transition} || 'fade',
506             AUTOSLIDE => $autoSlide,
507             NOTESIMAGE => $notes
508             };
509            
510 0           return BeamerReveal::TemplateStore::stampTemplate( $fTemplate, $fStamps );
511            
512             } else {
513 0           my $fTemplate = $templateStore->fetch( 'html', 'beamerframe.html' );
514            
515 0           my ( undef, $slideImage ) = $mediaManager->slideFromStore( $self->{parameters}->{rawpage} );
516            
517             my $fStamps =
518             {
519             DATAMENUTITLE => $menuTitle,
520             SLIDEIMAGE => $slideImage,
521             SLIDECONTENT => $content,
522 0   0       TRANSITION => $self->{parameters}->{transition} || 'fade',
523             AUTOSLIDE => $autoSlide,
524             NOTESIMAGE => $notes
525             };
526            
527 0           return BeamerReveal::TemplateStore::stampTemplate( $fTemplate, $fStamps );
528             }
529             }
530              
531             sub _topercent {
532 0 0   0     confess() unless defined $_[0];
533 0           return sprintf( "%.2f%%", $_[0] * 100 );
534             }
535              
536             sub _modernize {
537 0     0     my $string = shift;
538 0 0         defined( $string ) or die();
539 0           my $dictionary = {
540             qr/\\`\{?a\}?/ => 'à',
541             qr/\\'\{?a\}?/ => 'á',
542             qr/\\"\{?a\}?/ => 'ä',
543             qr/\\\^\{?a\}?/ => 'â',
544             qr/\\~\{?a\}?/ => 'ã',
545             qr/\\=\{?a\}?/ => 'ā',
546             qr/\\\.\{?a\}?/ => 'ȧ',
547             qr/\\u\{?a\}?/ => 'ă',
548             qr/\\v\{?a\}?/ => 'ǎ',
549             qr/\\c\{?a\}?/ => 'ą',
550             qr/\\r\{?a\}?/ => 'å',
551             qr/\\`\{?e\}?/ => 'è',
552             qr/\\'\{?e\}?/ => 'é',
553             qr/\\"\{?e\}?/ => 'ë',
554             qr/\\\^\{?e\}?/ => 'ê',
555             qr/\\~\{?e\}?/ => 'ẽ',
556             qr/\\=\{?e\}?/ => 'ē',
557             qr/\\\.\{?e\}?/ => 'ė',
558             qr/\\u\{?e\}?/ => 'ĕ',
559             qr/\\v\{?e\}?/ => 'ě',
560             qr/\\c\{?e\}?/ => 'ę',
561             qr/\\`\{?\\?i(?:\{\})?\}?/ => 'ì',
562             qr/\\'\{?\\?i(?:\{\})?\}?/ => 'í',
563             qr/\\"\{?\\?i(?:\{\})?\}?/ => 'ï',
564             qr/\\\^\{?\\?i(?:\{\})?\}?/ => 'î',
565             qr/\\~\{?\\?i(?:\{\})?\}?/ => 'ĩ',
566             qr/\\=\{?\\?i(?:\{\})?\}?/ => 'ī',
567             qr/\\u\{?\\?i(?:\{\})?\}?/ => 'ĭ',
568             qr/\\v\{?\\?i(?:\{\})?\}?/ => 'ǐ',
569             qr/\\c\{?\\?i(?:\{\})?\}?/ => 'į',
570             qr/\\`\{?o\}?/ => 'ò',
571             qr/\\'\{?o\}?/ => 'ó',
572             qr/\\"\{?o\}?/ => 'ö',
573             qr/\\\^\{?o\}?/ => 'ô',
574             qr/\\~\{?o\}?/ => 'õ',
575             qr/\\=\{?o\}?/ => 'ō',
576             qr/\\\.\{?o\}?/ => 'ȯ',
577             qr/\\u\{?o\}?/ => 'ŏ',
578             qr/\\v\{?o\}?/ => 'ǒ',
579             qr/\\c\{?o\}?/ => 'ǫ',
580             qr/\\`\{?u\}?/ => 'ù',
581             qr/\\'\{?u\}?/ => 'ú',
582             qr/\\"\{?u\}?/ => 'ü',
583             qr/\\\^\{?u\}?/ => 'û',
584             qr/\\~\{?u\}?/ => 'ũ',
585             qr/\\=\{?u\}?/ => 'ū',
586             qr/\\u\{?u\}?/ => 'ŭ',
587             qr/\\v\{?u\}?/ => 'ǔ',
588             qr/\\c\{?u\}?/ => 'ų',
589             qr/\\r\{?u\}?/ => 'ů',
590             qr/\\c\{?c\}?/ => 'ç',
591             qr/\\~\{?n\}?/ => 'ñ',
592             qr/\\oe/ => 'œ',
593             qr/\\OE/ => 'Œ',
594             qr/\\&/ => '&',
595             };
596 0           while ( my ( $regexp, $rep ) = each ( %$dictionary ) ) {
597 0           $string =~ s/$regexp/$rep/g;
598             }
599 0           return $string;
600             }
601              
602             1;
603              
604             __END__