From 76ed2603aca201337d14e0ffd3073bc5e293caf8 Mon Sep 17 00:00:00 2001
From: Sergiotarxz <sergiotarxz@posteo.net>
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;