Adding previews to categories and attributes.
This commit is contained in:
parent
e6256041b7
commit
76ed2603ac
@ -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');
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
195
lib/BurguillosInfo/Preview.pm
Normal file
195
lib/BurguillosInfo/Preview.pm
Normal file
@ -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;
|
Loading…
Reference in New Issue
Block a user