#!/usr/bin/env perl -w # # rm2-template-calendar # John Simpson 2023-07-04 # # 2023-07-09 jms1 - added support for landscape mode, added command line # options to choose which of the four images it should generate. # ############################################################################### # # The MIT License (MIT) # # Copyright (C) 2023 John Simpson # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the “Software”), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. # ############################################################################### require 5.005 ; use strict ; use warnings ; use GD ; # cpan install GD use GD::Text::Align ; # cpan install GD::Text use Getopt::Std ; # (normally installed with Perl itself) use POSIX 'strftime' ; # cpan install POSIX use Time::Local qw( timegm_posix ) ; # (normally installed with Perl itself) ######################################## # reMarkable 2 screen attributes my $rm2_page_w = 1404 ; # page width my $rm2_page_h = 1872 ; # page height my $rm2_menu_w = 120 ; # width of menu and close button areas my $rm2_title_h = 120 ; # height of title area ######################################## # Calendar attributes my $padding = 20 ; # blank space around calendar my $dow_h = 30 ; # height of "day of week" labels my $font_file = "$ENV{'HOME'}/Library/Fonts/LiberationSans-Regular.ttf" ; my $font_size = 18 ; my @dow = qw( Sunday Monday Tuesday Wednesday Thursday Friday Saturday ) ; my %dd = () ; # "row,col" => day of month my $title = '' ; # Pre-filled calendar title my $title_size = 60 ; # Font size for title my $dom_size = 24 ; # Font size for day of month ######################################## # Other globals my %opt = () ; # getopts() ############################################################################### # # Usage sub usage(;$) { my $msg = ( shift || '' ) ; print <new( $img , 'valign' => 'center' , 'halign' => 'center' , ) ; $a->set_font( $font_file , $size ) ; $a->set_text( $text ) ; my @b = $a->bounding_box( $tx , $ty , 0 ) ; ######################################## # DEBUG - print the position # - needed this to figure out the correct font size # # printf "(%4d,%4d) ... (%4d,%4d) (%4d,%4d) (%4d,%4d) (%4d,%4d) %s\n" , # $tx , $ty , @b[0..7] , $text ; ######################################## # Add the text to the image # - (x,y) should be lower left corner $img->stringFT( $colour , $font_file , $size , 0 , $b[0] , $b[1] , $text ) ; } ############################################################################### # # Create one calendar image sub calendar_basic($$$) { my $left_handed = shift ; my $landscape = shift ; my $out_file = shift ; ######################################## # Adjust page size for portrait/landscape mode my $page_w = $landscape ? $rm2_page_h : $rm2_page_w ; my $page_h = $landscape ? $rm2_page_w : $rm2_page_h ; my $menu_w = $rm2_menu_w ; my $title_h = $rm2_title_h ; ######################################## # Adjust positions for left/right handedness my $dat = $title_h + $padding ; # drawing area, top y (same for lh/rh) my $dab = $page_h - $padding ; # drawing area, bottom y (same for lh/rh) my $dal = $menu_w + $padding ; # drawing area, left x my $dar = $page_w - $padding ; # drawing area, right x my $mal = 0 ; # menu area, left x my $mar = $menu_w - 1 ; # menu area, right x my $msx = $menu_w ; # menu separator, x my $cal = $page_w - $menu_w ; # close button area, left x my $car = $page_w - 1 ; # close button area, right x my $csx = $cal - 1 ; # close button separator, x if ( $left_handed ) { ######################################## # menu is on right, content area is on left $dal = $padding ; # drawing area, left x $dar = $page_w - $menu_w - $padding - 1 ; # drawing area, right x $mal = $page_w - $menu_w ; # menu area, left x $mar = $page_w - 1 ; # menu area, right x $msx = $mal - 1 ; # menu separator, x $cal = 0 ; # close button area, left x $car = $menu_w - 1 ; # close button area, right x $csx = $menu_w ; # close button separator, x } ############################################################ # Create canvas my $img = new GD::Image( $page_w , $page_h ) ; ######################################## # Allocate colours my $white = $img->colorAllocate( 255 , 255 , 255 ) ; my $grey75 = $img->colorAllocate( 192 , 192 , 192 ) ; my $grey90 = $img->colorAllocate( 230 , 230 , 230 ) ; my $black = $img->colorAllocate( 0 , 0 , 0 ) ; ############################################################ # Menu/title zones $img->setThickness( 1 ) ; ######################################## # Grey area under close button, top right/left # and line across top of page, bottom of title area $img->filledRectangle( $cal , 0 , $car , $title_h-1 , $grey75 ) ; $img->line( 0 , $title_h-1 , $page_w-1 , $title_h-1 , $black ) ; $img->line( $csx , 0 , $csx , $title_h-1 , $black ) ; ######################################## # Grey area under menu, on left/right, drawn *after* the line across # the top of page so it covers whichever end is appropriate $img->filledRectangle( $mal , 0 , $mar , $page_h-1 , $grey75 ) ; $img->line( $msx , 0 , $msx , $page_h-1 , $black ) ; ######################################## # If we need to add a title, do it if ( $title ne '' ) { my $tx = int( $page_w / 2 ) ; my $ty = int( $title_h / 2 ) ; center_text( $img , $tx , $ty , $title_size , $title , $black ) ; } ############################################################ # Draw boxes for the days ######################################## # Calculate box size my $box_w = int( ( $dar - $dal ) / 7 ) ; my $box_h = int( ( $dab - $dat - $dow_h ) / 6 ) ; my $ib_w = $landscape ? int( $box_w * 0.35 ) : int( $box_w * 0.40 ) ; my $ib_h = $landscape ? int( $box_h * 0.40 ) : int( $box_h * 0.25 ) ; ######################################## # Draw the boxes $img->setThickness( 1 ) ; for my $r ( 0 .. 5 ) { for my $c ( 0 .. 6 ) { ######################################## # Coordinates of outer box my $ax = $dal + ( $c * $box_w ) ; my $ay = $dat + $dow_h + ( $r * $box_h ) ; my $bx = $ax + $box_w ; my $by = $ay + $box_h ; ######################################## # Inner box lower right corner my $dx = $ax + $ib_w ; my $dy = $ay + $ib_h ; ######################################## # Draw the two boxes $img->rectangle( $ax , $ay , $bx , $by , $black ) ; ######################################## # MAYBE draw inner box if ( ( $title eq '' ) || exists( $dd{"$r,$c"} ) ) { $img->rectangle( $ax , $ay , $dx , $dy , $black ) ; ######################################## # If we have a date for this box, add it if ( exists( $dd{"$r,$c"} ) ) { my $tx = $ax + int( $ib_w / 2 ) ; my $ty = $ay + int( $ib_h / 2 ) ; center_text( $img , $tx , $ty , $dom_size , $dd{"$r,$c"} , $black ) ; } } } } ############################################################ # Add day-of-week labels for my $c ( 0 .. 6 ) { ######################################## # Box around day of week my $ax = $dal + ( $c * $box_w ) ; my $ay = $dat ; my $bx = $ax + $box_w ; my $by = $ay + $dow_h ; $img->filledRectangle( $ax , $ay , $bx , $by , $grey90 ) ; $img->rectangle( $ax , $ay , $bx , $by , $black ) ; ######################################## # Draw DOW text my $tx = $ax + int( $box_w / 2 ) ; my $ty = $ay + int( $dow_h / 2 ) ; center_text( $img , $tx , $ty , $font_size , $dow[$c] , $black ) ; } ############################################################ # If the image is landscape mode, rotate it if ( $landscape ) { my $new_img = $img->copyRotate270() ; $img = $new_img ; } ######################################## # Write the output file open( O , '>' , $out_file ) or die "ERROR: can't create \"$out_file\": $!\n" ; binmode O ; print O $img->png() ; close O ; } ############################################################################### # # How many days in a given month? sub days_in_month($$) { my $yyyy = shift ; my $mm = shift ; ######################################## # Most months have fixed counts my $rv = (31,0,31,30,31,30,31,31,30,31,30,31)[$mm-1] ; ( $rv > 0 ) && return $rv ; ######################################## # February rules are weird if ( $yyyy % 4 ) { return 28 ; } if ( $yyyy % 100 ) { return 29 ; } if ( $yyyy % 400 ) { return 28 ; } return 29 ; } ############################################################################### ############################################################################### ############################################################################### getopts( 'hlrLPo:m:' , \%opt ) ; $opt{'h'} && usage() ; ######################################## # Left or right handed? my $lh = 0 ; if ( $opt{'l'} ) { if ( $opt{'r'} ) { usage( "ERROR: cannot use -l and -r together\n" ) ; } $lh = 1 ; } ######################################## # Portrait or landscape? my $landscape = 0 ; if ( $opt{'L'} ) { if ( $opt{'P'} ) { usage( "ERROR: cannot use -L and -P together\n" ) ; } $landscape = 1 ; } ######################################## # Output filename my $outfile = ( $opt{'o'} || 'CalendarMo.png' ) ; ############################################################ # If a month was specified, figure where the extra bits (which make it # a NOT-blank calendar) will be. if ( ( $opt{'m'} || '' ) =~ m|^(\d\d\d\d)\-(\d\d?)$| ) { my ( $yyyy , $mm ) = ( $1 , $2 ) ; $mm =~ s|^0|| ; if ( ( $mm < 1 ) || ( $mm > 12 ) ) { die "ERROR: invalid YYYY-MM value '$opt{'m'}'\n" ; } ######################################## # Figure out the time_t values my $t = timegm_posix( 0 , 0 , 12 , 1 , $mm - 1 , $yyyy - 1900 ) ; my @d = gmtime( $t ) ; ######################################## # Remember the human-formatted name of the month/year $title = strftime( '%B %Y' , @d ) ; ######################################## # Figure out which row,col each day of the month goes in my $x = days_in_month( $yyyy , $mm ) ; my $row = 0 ; my $col = $d[6] ; for my $n ( 1 .. $x ) { $dd{"$row,$col"} = $n ; $col ++ ; if ( $col > 6 ) { $col = 0 ; $row ++ ; } } } ############################################################ # Do the deed calendar_basic( $lh , $landscape , $outfile ) ;