Calendars - Month

This is just a simple monthly calendar. If you're willing to write in your own title (i.e. "July 2023") and numbers in each date's block, it can be used for any month you like.

OrientationRight HandedLeft Handed
PortraitP-CalendarMo-rh.png
P-CalendarMo-rh-sm.png
P-CalendarMo-lh.png
P-CalendarMo-lh-sm.png
LandscapeL-CalendarMo-rh.png
L-CalendarMo-rh-sm.png
L-CalendarMo-lh.png
L-CalendarMo-lh-sm.png

This was the first template script where I made both right- and left-handed versions, then a few days later, added Landscape versions. It was also the first template I made using Perl, with the GD graphics library. Now that I've done this one, I suspect that if I make any more templates, I'll be using Perl and GD for those as well.

The script also has an option where, if you give it a year and month, it will fill in a title and the day numbers. I'm not sure how useful the resulting image is as a template, since it could only be used for that one month, but if that's useful to you, feel free to add "-m 2023-07" to the command line.

License

This script is licensed under the MIT License.

The MIT License (MIT)

Copyright © 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.

The rm2-template-calendar Script

Download ⇒ rm2-template-calendar

#!/usr/bin/env perl -w
#
# rm2-template-calendar
# John Simpson <jms1@jms1.net> 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 <<EOF ;
$0 [options]

Create a reMarkable 2 template for a simple monthly calendar.

-l or -r    Left- or Right-handed. Default is Right-handed.

-L or -P    Landscape or Portrait. Default is Portrait.

-m ___      Month to pre-number. Must be "YYYY-MM" format (i.e. "2023-07")
            Default is no month, i.e. no pre-filled dates.

-o ___      Specify the name of the file to write. Default is 'CalendarMo.png'.

-h          Show this help message.

EOF

    if ( $msg ne '' )
    {
        print $msg ;
        exit 1 ;
    }

    exit 0 ;
}

###############################################################################
#
# Center a string around a point

sub center_text($$$$$$)
{
    my $img     = shift ;
    my $tx      = shift ;
    my $ty      = shift ;
    my $size    = shift ;
    my $text    = shift ;
    my $colour  = shift ;

    ########################################
    # Calculate real position for text

    my $a = GD::Text::Align->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 ) ;

Generated 2024-11-07 13:40:42 +0000
initial-86-g8ae27cf 2024-11-07 13:40:26 +0000