owlcode.tech/lib/OwlcodeTech/Preview.pm

211 lines
5.5 KiB
Perl

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('<div>'.$title.'</div>')->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;