Adding inital support for printing.
This commit is contained in:
commit
572f3e278f
BIN
diploma-currado.png
Normal file
BIN
diploma-currado.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
385
print.pl
Normal file
385
print.pl
Normal file
@ -0,0 +1,385 @@
|
||||
#!/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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user