package OwlcodeTech::Preview; use v5.36.0; use strict; use warnings; use feature 'signatures'; use SVG; use Path::Tiny; use Const::Fast; use Capture::Tiny qw/capture/; use MIME::Base64; const my $CURRENT_FILE => __FILE__; const my $ROOT_PROJECT => path($CURRENT_FILE)->parent->parent->parent; const my $PUBLIC_DIR => $ROOT_PROJECT->child('public'); const my $OWLCODE_LOGO => $PUBLIC_DIR->child('img/owlcode_tech.webp'); const my $SVG_WIDTH => 1200; const my $SVG_HEIGHT => 627; const my $SVG_EMBEDDED_IMAGE_MAX_WIDTH => 1000; const my $SVG_EMBEDDED_IMAGE_MAX_HEIGHT => 400; sub Generate ( $self, $title, $content, $image_file, $site_name ) { my $dom = Mojo::DOM->new($content); $content = $dom->all_text; $title = Mojo::DOM->new('
'.$title.'
')->all_text; my $svg = $self->_GenerateSVGPreview( $title, $self->_DivideTextContentInLines($content), $image_file, $site_name ); return $self->_SVGToPNG($svg); } sub _ToPng ( $self, $image ) { if ( $image =~ /\.\w+$/ ) { my $new_image = $image =~ s/\.\w+$/.generated.png/r; say $new_image; if ( !-e $new_image ) { system 'convert', "$image", "$new_image"; } $image = $new_image; } return path($image); } sub _GenerateSVGPreviewHeaderBar ( $self, $svg, $group, $site_name ) { $group->rect( x => 0, y => 0, width => 1200, height => 50, style => { fill => '#5C8CED' } ); $group->rect( x => 0, y => 50, width => 1200, height => 627, style => { fill => 'azure' } ); my $owlcode_logo_png = path( $self->_ToPng($OWLCODE_LOGO) ); $group->rect( x => 10, y => 5, width => 40, height => 40, style => { fill => 'azure' }, ); $group->image( x => 10, y => 5, width => 40, height => 40, -href => 'data:image/png;base64,' . encode_base64( $owlcode_logo_png->slurp ) ); $group->text( x => 60, y => 40, style => { 'font-size' => 50, fill => 'azure' } )->cdata($site_name); } sub _GenerateSVGPreview ( $self, $title, $content, $image_file, $site_name ) { my @content = @$content; my $svg = SVG->new( width => $SVG_WIDTH, height => $SVG_HEIGHT ); my $group = $svg->group( id => 'group', style => { font => 'Arial', 'font-size' => 30, } ); $self->_GenerateSVGPreviewHeaderBar( $svg, $group, $site_name ); my $new_y; if ( defined $image_file ) { $new_y = $self->_AttachImageSVG( $group, $image_file ); } $new_y //= 100; $group->text( x => 10, y => $new_y, style => { 'font-size' => 50 } )->cdata($title); my $n = 0; for my $line (@content) { $group->text( x => 10, y => $new_y + 40 + ( 30 * $n ), style => { 'font-size' => 38 } )->cdata($line); $n++; } return $svg->xmlify; } sub _SVGToPNG ( $self, $svg ) { my ( $stdout, $stderr ) = capture { open my $fh, '|-', qw{convert /dev/stdin png:fd:1}; binmode $fh, 'utf8'; print $fh $svg; close $fh; }; say STDERR $stderr; return $stdout; } sub _DivideTextContentInLines ( $self, $content ) { my @content_divided_in_lines = split /\n/, $content; my @new_content; my $n_chars_per_line = 70; for my $line (@content_divided_in_lines) { if ( length($line) <= $n_chars_per_line ) { push @new_content, $line; next; } my $last_word = ''; while ( $line =~ /(.{1,${n_chars_per_line}})/g ) { my $new_line = $last_word . $1; $new_line =~ s/(\S*)$//; $last_word = $1; push @new_content, $new_line; } if ($last_word) { $new_content[$#new_content] .= $last_word; } } return \@new_content; } sub _AttachImageSVG ( $self, $svg, $image_file ) { $image_file = $PUBLIC_DIR->child( './' . $image_file ); $image_file = path( $self->_ToPng($image_file) ); my ( $stdout, $stderr, $error ) = capture { system qw/identify -format "%wx%h"/, $image_file; }; if ($error) { warn "$image_file not recognized by identify."; return; } my ( $width, $height ) = $stdout =~ /^"(\d+)x(\d+)"$/; my $height_complete_image = (1200 / $width) * $height; if ( $height > $SVG_EMBEDDED_IMAGE_MAX_HEIGHT ) { $width /= $height / $SVG_EMBEDDED_IMAGE_MAX_HEIGHT; $width = int($width); $height = $SVG_EMBEDDED_IMAGE_MAX_HEIGHT; } if ( $width > $SVG_EMBEDDED_IMAGE_MAX_WIDTH ) { $height /= $width / $SVG_EMBEDDED_IMAGE_MAX_WIDTH; $height = int($height); $width = $SVG_EMBEDDED_IMAGE_MAX_WIDTH; } my $x = int( ( $SVG_WIDTH / 2 ) - ( $width / 2 ) ); my $y = 50; my ($output) = capture { system qw/file --mime-type/, $image_file; }; my ($format) = $output =~ /(\S+)$/; $svg->image( x => 0, y => $y, width => $SVG_WIDTH, height => $height_complete_image, -href => "data:$format;base64," . encode_base64( $image_file->slurp ) ); $svg->rect( x => 0, y => $y+$height, width => $SVG_WIDTH, height => $SVG_HEIGHT, style => { fill => 'azure' }, ); return $y + $height + 50; } 1;