From 76ed2603aca201337d14e0ffd3073bc5e293caf8 Mon Sep 17 00:00:00 2001 From: Sergiotarxz Date: Mon, 8 May 2023 01:14:36 +0200 Subject: [PATCH] Adding previews to categories and attributes. --- lib/BurguillosInfo.pm | 2 + lib/BurguillosInfo/Categories.pm | 8 + lib/BurguillosInfo/Controller/Attribute.pm | 29 +++ lib/BurguillosInfo/Controller/Page.pm | 63 ++++--- lib/BurguillosInfo/Posts.pm | 169 +----------------- lib/BurguillosInfo/Preview.pm | 195 +++++++++++++++++++++ 6 files changed, 281 insertions(+), 185 deletions(-) create mode 100644 lib/BurguillosInfo/Preview.pm diff --git a/lib/BurguillosInfo.pm b/lib/BurguillosInfo.pm index 52c612e..1258352 100644 --- a/lib/BurguillosInfo.pm +++ b/lib/BurguillosInfo.pm @@ -34,7 +34,9 @@ sub startup ($self) { # $r->get('/:post')->to('Page#post'); $r->get('/stats')->to('Metrics#stats'); $r->get('/<:category>.rss')->to('Page#category_rss'); + $r->get('/:category_slug/atributo/<:attribute_slug>-preview.png')->to('Attribute#get_attribute_preview'); $r->get('/:category_slug/atributo/:attribute_slug')->to('Attribute#get'); + $r->get('/<:category>-preview.png')->to('Page#get_category_preview'); $r->get('/:category')->to('Page#category'); $r->get('/posts/<:slug>-preview.png')->to('Page#get_post_preview'); $r->get('/posts/:slug')->to('Page#post'); diff --git a/lib/BurguillosInfo/Categories.pm b/lib/BurguillosInfo/Categories.pm index e1715d7..b95e66b 100644 --- a/lib/BurguillosInfo/Categories.pm +++ b/lib/BurguillosInfo/Categories.pm @@ -11,6 +11,8 @@ use Const::Fast; use Mojo::DOM; use Path::Tiny; +use BurguillosInfo::Preview; + const my $CURRENT_FILE => __FILE__; const my $CATEGORIES_DIR => path($CURRENT_FILE)->parent->parent->parent->child('content/categories'); @@ -120,4 +122,10 @@ sub _AvoidGrandChildCategories($self, $categories) { } } } + +sub PreviewOg($self, $category) { + my $title = $category->{title}; + my $description = $category->{description}; + return BurguillosInfo::Preview->Generate($title, $description, undef); +} 1; diff --git a/lib/BurguillosInfo/Controller/Attribute.pm b/lib/BurguillosInfo/Controller/Attribute.pm index 8948d31..4477631 100644 --- a/lib/BurguillosInfo/Controller/Attribute.pm +++ b/lib/BurguillosInfo/Controller/Attribute.pm @@ -10,6 +10,29 @@ use BurguillosInfo::Categories; use Mojo::Base 'Mojolicious::Controller', -signatures; +use BurguillosInfo::Preview; + +sub get_attribute_preview ($self) { + my $category_slug = $self->param('category_slug'); + my $attribute_slug = $self->param('attribute_slug'); + my $categories = BurguillosInfo::Categories->new->Retrieve; + my $category = $categories->{$category_slug}; + if ( !defined $category ) { + return $self->reply->not_found; + } + my $attribute = $category->{attributes}{$attribute_slug}; + if ( !defined $attribute ) { + return $self->reply->not_found; + } + + $self->render( + format => 'png', + data => BurguillosInfo::Preview->Generate( + $attribute->{title}, $attribute->{description}, undef + ), + ); +} + sub get ($self) { my $category_slug = $self->param('category_slug'); my $attribute_slug = $self->param('attribute_slug'); @@ -26,12 +49,18 @@ sub get ($self) { my $posts = BurguillosInfo::Posts->RetrieveDirectPostsForCategory( $category->{slug} ); $posts = [ grep { defined $_->{attributes}{$attribute_slug} } @$posts ]; + my $base_url = $self->config('base_url'); $self->render( template => 'page/attribute', category => $category, attribute => $attribute, categories => $categories, posts => $posts, + ogimage => $base_url . '/' + . $category->{slug} + . '/atributo/' + . $attribute->{identifier} + . '-preview.png', ); } 1; diff --git a/lib/BurguillosInfo/Controller/Page.pm b/lib/BurguillosInfo/Controller/Page.pm index ff621fa..c1918a7 100644 --- a/lib/BurguillosInfo/Controller/Page.pm +++ b/lib/BurguillosInfo/Controller/Page.pm @@ -94,9 +94,10 @@ sub _post_to_rss { my $guid = Mojo::DOM->new_tag( 'guid', $post->{slug} ); my $date = Mojo::DOM->new_tag( 'pubDate', - ''.DateTime::Format::Mail->format_datetime( + '' + . DateTime::Format::Mail->format_datetime( DateTime::Format::ISO8601->parse_datetime( $post->{date} ) - ) + ) ); $item_tag->child_nodes->first->append_content($title_tag); @@ -112,15 +113,17 @@ sub post { my $slug = $self->param('slug'); my ( $posts_categories, $posts_slug ) = BurguillosInfo::Posts->new->Retrieve; - my $categories = BurguillosInfo::Categories->new->Retrieve; - my $post = $posts_slug->{$slug}; + my $categories = BurguillosInfo::Categories->new->Retrieve; + my $post = $posts_slug->{$slug}; if ( !defined $post ) { $self->render( template => '404', status => 404 ); return; } my $current_category = $categories->{ $post->{category} }; - $self->stash(ogimage => 'https://burguillos.info/posts/'.$post->{slug}.'-preview.png'); - $self->stash(useragent => $self->req->headers->user_agent); + my $base_url = $self->config('base_url'); + $self->stash( + ogimage => $base_url . '/posts/' . $post->{slug} . '-preview.png' ); + $self->stash( useragent => $self->req->headers->user_agent ); $self->render( post => $post, current_category => $current_category ); } @@ -129,30 +132,48 @@ sub category { my $categories = BurguillosInfo::Categories->new->Retrieve; my $category_name = $self->param('category'); my $current_category = $categories->{$category_name}; + my $base_url = $self->config('base_url'); if ( !defined $current_category ) { $self->render( template => '404', status => 404 ); return; } $self->render( - template => 'page/index', - categories => $categories, + template => 'page/index', + categories => $categories, + ogimage => $base_url . '/' . $current_category->{slug} . '-preview.png', current_category => $current_category ); } +sub get_category_preview { + my $self = shift; + my $category_slug = $self->param('category'); + my $category_model = BurguillosInfo::Categories->new; + my $categories = $category_model->Retrieve; + if ( !defined $categories->{$category_slug} ) { + $self->render( template => '404', status => 404 ); + return; + } + my $category = $categories->{$category_slug}; + $self->render( + format => 'png', + data => $category_model->PreviewOg($category) + ); +} + sub get_post_preview { - my $self = shift; - my $slug = $self->param('slug'); - my $post_model = BurguillosInfo::Posts->new; - my ( $posts_categories, $posts_slug ) = $post_model->Retrieve; - if ( !defined $posts_slug->{$slug} ) { - $self->render( template => '404', status => 404 ); - return; - } - my $post = $posts_slug->{$slug}; - $self->render( - format => 'png', - data => $post_model->PostPreviewOg($post) - ); + my $self = shift; + my $slug = $self->param('slug'); + my $post_model = BurguillosInfo::Posts->new; + my ( $posts_categories, $posts_slug ) = $post_model->Retrieve; + if ( !defined $posts_slug->{$slug} ) { + $self->render( template => '404', status => 404 ); + return; + } + my $post = $posts_slug->{$slug}; + $self->render( + format => 'png', + data => $post_model->PreviewOg($post) + ); } 1; diff --git a/lib/BurguillosInfo/Posts.pm b/lib/BurguillosInfo/Posts.pm index 772ca78..d5551ca 100644 --- a/lib/BurguillosInfo/Posts.pm +++ b/lib/BurguillosInfo/Posts.pm @@ -17,18 +17,13 @@ use Mojo::DOM; use Path::Tiny; use DateTime::Format::ISO8601; use DateTime; -use SVG; -use Capture::Tiny qw/capture/; + +use BurguillosInfo::Preview; 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 $POSTS_DIR => $ROOT_PROJECT->child('content/posts'); -const my $BURGUILLOS_LOGO => $PUBLIC_DIR->child('img/burguillos.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 => 200; my $cached_posts_by_category; my $cached_posts_by_slug; @@ -187,169 +182,15 @@ sub RetrieveDirectPostsForCategory ( $self, $category_name ) { return [@$posts]; } -sub PostPreviewOg { +sub PreviewOg { my $self = shift; my $post = shift; my $title = $post->{title}; my $content = $post->{content}; - my $dom = Mojo::DOM->new($content); - $content = $dom->all_text; - - 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; - } - } - - my $svg = - $self->_GenerateSVGPostPreview( $title, \@new_content, $post->{image} ); - 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; + my $image_file = $post->{image}; + return BurguillosInfo::Preview->Generate($title, $content, $image_file); } -sub _AttachImageSVG { - my $self = shift; - my $svg = shift; - my $image = shift; - $image = $PUBLIC_DIR->child( './' . $image ); - path($image = $self->_toPng($image)); - my ( $stdout, $stderr, $error ) = capture { - system qw/identify -format "%wx%h"/, $image; - }; - if ($error) { - warn "$image not recognized by identify."; - return; - } - my ( $width, $height ) = $stdout =~ /^"(\d+)x(\d+)"$/; - 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 = 90; - my ($output) = capture { - system qw/file --mime-type/, $image; - }; - my ($format) = $output =~ /(\S+)$/; - $svg->image( - x => $x, - y => $y, - width => $width, - height => $height, - -href => "data:$format;base64," . encode_base64( $image->slurp ) - ); - return $y + $height + 50; -} - -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 _GenerateSVGPostPreview { - my $self = shift; - my $title = shift; - my $content = shift; - my $image = shift; - my @content = @$content; - my $svg = SVG->new( width => $SVG_WIDTH, height => $SVG_HEIGHT ); - $svg->rect( - x => 0, - y => 0, - width => 1200, - height => 50, - style => { fill => 'blueviolet' } - ); - $svg->rect( - x => 0, - y => 50, - width => 1200, - height => 627, - style => { fill => '#F8F8FF' } - ); - - my $group = $svg->group( - id => 'group', - style => { - font => 'Arial', - 'font-size' => 30, - } - ); - - my $burguillos_logo_png = path($self->_toPng($BURGUILLOS_LOGO)); - say ''.$burguillos_logo_png; - $group->image( - x => 10, - y => 5, - width => 40, - height => 40, - -href => 'data:image/png;base64,' - . encode_base64( $burguillos_logo_png->slurp ) - ); - $group->text( - x => 60, - y => 40, - style => { 'font-size' => 50, fill => '#f2eb8c' } - )->cdata('Burguillos.info'); - my $new_y; - - if ( defined $image ) { - $new_y = $self->_AttachImageSVG( $group, $image ); - } - $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++; - } - path($ROOT_PROJECT)->child('a.svg')->spew_utf8( $svg->xmlify ); - return $svg->xmlify; -} 1; diff --git a/lib/BurguillosInfo/Preview.pm b/lib/BurguillosInfo/Preview.pm new file mode 100644 index 0000000..4fcf274 --- /dev/null +++ b/lib/BurguillosInfo/Preview.pm @@ -0,0 +1,195 @@ +package BurguillosInfo::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 $BURGUILLOS_LOGO => $PUBLIC_DIR->child('img/burguillos.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 => 200; + +sub Generate($self, $title, $content, $image_file) { + my $dom = Mojo::DOM->new($content); + $content = $dom->all_text; + + + my $svg = + $self->_GenerateSVGPreview( $title, $self->_DivideTextContentInLines($content), $image_file ); + 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) { + $group->rect( + x => 0, + y => 0, + width => 1200, + height => 50, + style => { fill => 'blueviolet' } + ); + $group->rect( + x => 0, + y => 50, + width => 1200, + height => 627, + style => { fill => '#F8F8FF' } + ); + + + my $burguillos_logo_png = path($self->_ToPng($BURGUILLOS_LOGO)); + say $burguillos_logo_png; + say ''.$burguillos_logo_png; + $group->image( + x => 10, + y => 5, + width => 40, + height => 40, + -href => 'data:image/png;base64,' + . encode_base64( $burguillos_logo_png->slurp ) + ); + $group->text( + x => 60, + y => 40, + style => { 'font-size' => 50, fill => '#f2eb8c' } + )->cdata('Burguillos.info'); +} + +sub _GenerateSVGPreview($self, $title, $content, $image_file) { + 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); + + 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+)"$/; + 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 = 90; + my ($output) = capture { + system qw/file --mime-type/, $image_file; + }; + my ($format) = $output =~ /(\S+)$/; + $svg->image( + x => $x, + y => $y, + width => $width, + height => $height, + -href => "data:$format;base64," . encode_base64( $image_file->slurp ) + ); + return $y + $height + 50; +} + +1;