printermockup/print.pl

386 lines
12 KiB
Perl

#!/usr/bin/env perl
use v5.38.2;
use strict;
use warnings;
use feature 'signatures';
use Printer::ESCPOS;
use GD::Image;
use Cairo;
use Path::Tiny;
use Encode qw/decode/;
use Pango;
use Imager::QRCode;
use Data::Dumper;
my $debug = $ARGV[0] // 0;
my $vendor_id = 0x0416;
my $product_id = 0x5011;
my $deviceFilePath = '/dev/usb/lp0';
my $device = Printer::ESCPOS->new(
driverType => 'File',
deviceFilePath => $deviceFilePath,
);
my $diploma = get_gd_image('diploma-currado.png');
my $black = $diploma->colorAllocate(0,0,0);
$diploma->stringFT($black, '/usr/share/fonts/noto-cjk/NotoSansCJK-Medium.ttc', 20, 0, 500, 190, 'Diploma al merito.');
$diploma = $diploma->copyRotate90();
my $tempdir = Path::Tiny->tempdir(CLEANUP=>0);
my $diploma_image = $tempdir->child('image.png');
$diploma->_file($diploma_image);
image($device, ''.$diploma_image);
$device->printer->lf for ( 0 .. 3 );
$device->printer->print;
sub qr($device, $url) {
my $tmpdir = Path::Tiny->tempdir;
my $image_file = $tmpdir->child('image.png');
my $qrcode = Imager::QRCode->new( size => 8 );
my $img = $qrcode->plot( $url, {} );
$img->write( file => '' . $image_file ) or die $img->errstr;
image($device, $image_file);
print_text($device, $url, 20);
}
sub qr_as_gd_image($device, $url) {
my $tmpdir = Path::Tiny->tempdir;
my $image_file = $tmpdir->child('image.png');
my $qrcode = Imager::QRCode->new( size => 8 );
my $img = $qrcode->plot( $url, {} );
$img->write( file => '' . $image_file ) or die $img->errstr;
return get_gd_image($image_file);
}
sub get_gd_image($file) {
$file = path($file);
my ($extension) = $file =~ /\.(.*?)$/;
my $tempdir = Path::Tiny->tempdir;
my $new_image = $tempdir->child( 'image.' . $extension );
system 'convert', $file, '-background', 'white', '-alpha', 'remove',
$new_image;
$file = $new_image;
my $image;
if ( $file =~ /\.png$/ ) {
$image = GD::Image->newFromPng( $file . '' );
}
elsif ( $file =~ /\.jpg$/ ) {
$image = GD::Image->newFromJpeg( $file . '' );
}
else {
die "$image format not recognized";
}
return $image;
}
sub image( $device, $file ) {
my $image = get_gd_image($file);
my $width = $image->width;
my $height = $image->height;
my $dest_width = 380;
my $dest_height = $dest_width * ( $height / $width );
my $image_dest = GD::Image->new( $dest_width, $dest_height );
$image_dest->copyResampled( $image, 0, 0, 0, 0, $dest_width, $dest_height,
$width, $height, );
for (
my $single_image_y = 0 ;
$single_image_y < $dest_height + 255 ;
$single_image_y += 255
)
{
if ( $single_image_y >= $dest_height ) {
last;
}
my $final_height = 255;
if ( $single_image_y + 255 > $dest_height ) {
$final_height = $dest_height - $single_image_y;
}
my $image_final = GD::Image->new( $dest_width, $final_height );
$image_final->copy( $image_dest, 0, 0, 0, $single_image_y, $dest_width,
$final_height );
my ($alpha) = $image_final->colorAllocateAlpha( 0, 0, 0, 127 );
$image_final->colorDeallocate($alpha);
$image_final->colorAllocate( 255, 255, 255 );
$device->printer->image($image_final);
}
}
sub hex_to_char {
return pack 'C', shift;
}
sub split_text_lines( $text, $max_width, $font, $font_size ) {
my @return;
my @lines;
if ( !ref $text ) {
@lines = map { $_ } split "\n", $text, -1;
}
else {
for my $element (@$text) {
if ( ref $element ) {
push @lines, $element;
next;
}
$element = decode 'utf-8', $element;
push @lines, $element;
}
}
my $surface = Cairo::ImageSurface->create( 'argb32', 380, 100 );
my $cr = Cairo::Context->create($surface);
my $layout = Pango::Cairo::create_layout($cr);
$layout->set_font_description($font);
while (@lines) {
my $current_line = get_next_line( \@lines );
my ( $w, $h ) = _get_size_text($cr, $layout, $font, $font_size, $current_line );
my $final_line = [];
my $remainder = [];
if ( $w > $max_width ) {
LOOP_SPLIT: while (1) {
my $word = get_word($current_line);
if (!defined $word) {
die 'wtf?';
}
my ( $w, $h ) =
_get_size_text($cr, $layout, $font, $font_size,
join_line_with_word( $final_line, $word ) );
if ( $w > $max_width ) {
my ( $w_cutted_word, $h_cutted_word ) =
_get_size_text($cr, $layout, $font, $font_size, [$word] );
if ( $w_cutted_word > $max_width ) {
my $i = 0;
$final_line = join_line_with_word( $final_line, $word );
while (1) {
my $possible_cut =
substr_line( $final_line, 0, $i );
my ( $w_cutted_word, $h_cutted_word ) =
_get_size_text($cr, $layout, $font, $font_size, $possible_cut );
if ( $w_cutted_word > $max_width ) {
$remainder = substr_line( [@$final_line, @$current_line], $i - 1 );
$final_line =
substr_line( $final_line, 0, $i - 1 );
$current_line = $final_line;
last LOOP_SPLIT;
}
$i++;
}
}
$remainder = unshift_line_with_word( $word, $current_line );
$current_line = $final_line;
last;
}
$final_line = join_line_with_word( $final_line, $word );
}
}
$remainder->[0] =~ s/^\s+// if defined $remainder->[0] && !(ref $remainder->[0]);
unshift @lines, @$remainder if scalar @$remainder > 0;
push @return, $current_line;
}
return @return;
}
sub substr_line( $text, $offset, $how_much = undef ) {
my $result = [];
my $cur_offset = 0;
my $element = 0;
my $needed_offset = 0;
while ( $cur_offset < $offset ) {
if ( !defined $text->[$element] ) {
die 'Overflow';
}
if ( ref $text->[$element] ) {
$cur_offset += 1;
}
else {
my $length = (length $text->[$element]) - 1;
if ( $cur_offset + $length > $offset ) {
$needed_offset = $offset - $cur_offset;
last;
}
$cur_offset += $length;
}
$element++;
}
my $gotten = 0;
while ( !defined $how_much || $gotten < $how_much ) {
if ( !defined $text->[$element] ) {
last;
}
if ( ref $text->[$element] ) {
push @$result, $text->[$element];
$gotten += 1;
}
else {
my $substr_length = (length $text->[$element]) - $needed_offset;
if ( defined $how_much && $gotten + $substr_length > $how_much ) {
$substr_length = $how_much - $gotten;
}
$result = join_line_with_word( $result, substr $text->[$element],
$needed_offset, $substr_length );
$needed_offset = 0;
$gotten += $substr_length;
}
$element++;
}
return $result;
}
sub unshift_line_with_word( $word, $line ) {
my $result = [@$line];
if (defined $result->[0] && !ref $result->[0]) {
$result->[0] = $word.$result->[0];
} else {
unshift @$result, $word;
}
return $result;
}
sub join_line_with_word( $line, $word ) {
my $result = [@$line];
if ( scalar @$result == 0 || scalar @$result == 0 || ref $word || ref $result->[$#$result] ) {
push @$result, $word;
}
else {
$result->[$#$result] .= $word;
}
return $result;
}
sub get_word($line) {
if ( ref $line->[0] ) {
return shift @$line;
}
return unless @$line;
$line->[0] =~ /^(\s*\S*)(.*)$/;
if ($2 eq '') {
shift @$line;
} else {
$line->[0] = $2;
}
return $1;
}
sub get_next_line($lines) {
my @result;
my $last_is_image = 0;
my $first = 1;
my $finish = 0;
state $calls = 0;
$calls++;
while (1) {
my $current_line = shift @$lines;
my $pre_split;
my $post_split;
if ( !defined $current_line ) {
last;
}
if ( (!(ref $current_line) && !$first && !$last_is_image)
|| ($current_line =~ /^(.*?)?\n(.*)$/s)) {
$pre_split = $1 if defined $1;
$post_split = $2 if defined $2;
$finish = 1;
}
$first = 0;
$last_is_image = !!(ref $current_line);
if ($finish) {
if (defined $post_split) {
push @result, $pre_split if defined $pre_split;
unshift @$lines, $post_split;
} else {
unshift @$lines, $current_line
}
last;
}
push @result, $current_line;
}
return \@result;
}
sub print_text( $device, $text, $font_size ) {
if ( !(ref $text) ) {
$text = decode 'utf-8', $text;
}
my $tempdir = Path::Tiny->tempdir;
my $file = $tempdir->child('result.png');
my $font = Pango::FontDescription->from_string(
'Noto Sans CJK JP ' . ( $font_size - ( 2 * 50 / 30 ) ) );
my @lines = split_text_lines( $text, 380, $font, $font_size );
while (@lines) {
my $current_line = shift @lines;
my $height = $font_size * 30 / 17;
my $surface = Cairo::ImageSurface->create( 'argb32', 380, $height );
my $cr = Cairo::Context->create($surface);
$cr->set_source_rgb( 1, 1, 1 );
$cr->rectangle( 0, 0, 380, $font_size * 30 / 17 );
$cr->fill;
$cr->set_source_rgb( 0, 0, 0 );
my $x = 0;
my $layout = Pango::Cairo::create_layout($cr);
for my $part (@$current_line) {
$cr->move_to( 0, 0 );
if (!ref $part) {
Pango::Cairo::update_layout($cr, $layout);
$layout->set_text($part);
$layout->set_font_description($font);
$cr->move_to( $x, 0 );
my ($new_x) = $layout->get_pixel_size;
$x += $new_x;
Pango::Cairo::show_layout( $cr, $layout );
next;
}
my $gd_image = $part;
my $tempdir = Path::Tiny->tempdir(CLEANUP=>0);
my $image_file = $tempdir->child('out.png');
$gd_image->_file($image_file);
my $image = Cairo::ImageSurface->create_from_png(
$image_file );
my $ori_width = $gd_image->width;
my $ori_height = $gd_image->height;
my $scale_w = $font_size / $ori_width;
my $total_height = $font_size * 30 / 17 / $scale_w;
my $scale_h = $scale_w;
$cr->save;
$cr->scale($scale_w, $scale_h);
$cr->set_source_surface( $image, $x/$scale_w,($total_height - $x * $scale_w) / 4);
$cr->paint;
$cr->restore;
$x+= $font_size;
}
$surface->write_to_png( $file . '' );
my $img = GD::Image->newFromPng( $file . '' );
$device->printer->image($img);
}
}
sub _get_size_text( $cr, $layout, $font, $font_size, $text ) {
my $x = 0;
my $i;
for my $element (@$text) {
$cr->move_to( $x, 0 );
if ( ref $element ) {
$x += $font_size;
next;
}
$layout->set_text($element);
$layout->set_font_description($font);
my ($new_x) = $layout->get_pixel_size;
$x += $new_x;
}
return $x;
}