Spring naar hoofdtekst

PDF maandkalender in PHP

Geplaatst op door .
Laatste aanpassing op .

Inleiding

Zo rond oktober/november gaan mijn vrouw en ik op zoek naar een aantal nieuwe kalenders voor het volgende jaar. Vanaf 2019 maken we de grote weekkalender zelf. Dit jaar werd die uitgebreid met een kleiner broertje op A5-formaat. Toch ontbrak er nog één onderdeel: een compacte maandkalender met weeknummers. Ik ging opnieuw aan de slag en nam de code van de weekkalender als basis. Het resultaat is te vinden op GitHub.

Eenvoudig

De weekkalender heeft 52-53 pagina's met veel ruimte voor elke dag. Deze maandkalender moest daarentegen compact worden en liefst het hele jaar in één of twee oogopslagen samenvatten. Uiteindelijk koos ik voor een raster van 3×2 maanden, 2 kantjes A5; zie dit voorbeeld.

Opnieuw maakte ik gebruik van mPDF voor het omzetten van HTML naar PDF. Zo kon ik met relatief eenvoudige opmaak (lees: tabellen) toch het gewenste resultaat bereiken.

Opmaak

De eerste maand van 2023 ziet er als volgt uit:

     januari 2023
----------------------
wk | 52 01 02 03 04 05
---+------------------
ma |     2  9 16 23 30
di |     3 10 17 24 31
wo |     4 11 18 25
do |     5 12 19 26
vr |     6 13 20 27
za |     7 14 21 28
zo |  1  8 15 22 29

Elke maand is één tabel van in totaal 7 kolommen en 9 rijen. Daarvan is de eerste rij de titel (maandnaam + jaar). De volgende rij is gevuld met de weeknummers. De eerste kolom bevat de weekdagnamen (ma-di-wo-do-vr-za-zo). De rest van de tabel wordt gevuld met de dagnummers zelf. Voor het omliggende raster van 3×2 nestte ik de maandtabellen in één omliggende tabel genaamd scaffold.

Code in/uitklappen
table.scaffold{
  margin: 0 auto;  
}
table.month{
  font-family: sans-serif;
}
table.month th,
table.month td {
  border-right: solid 0.3mm black;
  border-bottom: solid 0.3mm black;
  padding: 1mm 2mm;
  text-align: center;
}
table.month tr.title{
  background-color: #999999;
}
table.month tr.week{
  background-color: #cccccc;
}

Code

Met onderstaande code wordt op basis van een maandnummer ($m) en het ingestelde jaar ($this->_year) een HTML-tabel gegenereerd. De functie _dtWrap() is een hulpje om gemakkelijk met meerdere van elkaar afgeleide datums te kunnen werken.

Code in/uitklappen
private function _generateMonthTable(int $m) : string
{
    $firstThisMonth = new \DateTime($this->_year.'-'.$m.'-01');
    $loopDate = clone $firstThisMonth;

    $html = '<table class="month">';
    $html .= sprintf(
        '<tr class="title"><th colspan="7">%s %d</th></tr>',
        strftime('%B', $firstThisMonth->getTimestamp()),
        $this->_year
    );
    while ($loopDate->format('N') > 1) {
        $loopDate->sub(new \DateInterval('P1D'));
    }
    // Array of 8 rows for weeknumbers (1x) + weekdays (7x)
    $rows = [
        -1 => [], 0 => [], 1 => [], 2 => [],
        3 => [], 4 => [], 5 => [], 6 => []
    ];

    // Construct first row (for weeknumbers)
    for ($weekLoop = -1; $weekLoop < 6; $weekLoop++) {
        if ($weekLoop == -1) {
            $rows[-1][] = '<th>wk</th>';
            continue;
        }
        $dt = self::_dtWrap($firstThisMonth, 7*$weekLoop);
        $rows[-1][] = strftime('<td>%V</td>', $dt->getTimestamp());
    }
    // Construct second to eighth rows
    foreach ($rows as $rowIx => &$row) {
        if ($rowIx == -1) {
            continue;
        }
        for ($colIx = -1; $colIx < 6; $colIx++) {
            $dt = self::_dtWrap($loopDate, 7*$colIx + $rowIx);
            if ($colIx == -1) {
                // Print abbreviated weekday name
                $row[] = strftime('<th>%a</th>', $dt->getTimestamp());
                continue;
            }
            if ($dt->format('m') != $m) {
                // Print empty cell
                $row[] = '<td>&nbsp;</td>';
                continue;
            }
            // Print day of month
            $row[] = strftime('<td>%e</td>', $dt->getTimestamp());
        }
    }
    foreach ($rows as $rowIx => &$row) {
        if ($rowIx == -1) {
            $html .= '<tr class="week">';
        } else {
            $html .= '<tr>';
        }
        $html .= implode('', $row);
        $html .= '</tr>';
    }
    $html .= '</table>';
    return $html;
}

private static function _dtWrap(\DateTime $dt, int $days = 0) : \DateTime
{
    $outDt = clone $dt;
    $di = new \DateInterval('P'.abs($days).'D');

    if ($days < 0) {
        $outDt->sub($di);
    } else {
        $outDt->add($di);
    }
    return $outDt;
}

Finale

Tot slot wordt in onderstaande functie het daadwerkelijke PDF-document gegenereerd. Er wordt gekozen voor een A5-formaat in landschap-ligging. Daarna wordt de stylesheet toegevoegd en – in 4 stappen van 3 – de maanden per stuk als tabel in een cel aan het omliggende raster toegevoegd.

Code in/uitklappen
public function getPDF() : void
{
    $pdfConfig = array(
        'format' => 'A5',
        'margin_left' => 5,
        'margin_right' => 5,
        'margin_top' => 5,
        'margin_bottom' => 5,
        'margin_header' => 0,
        'margin_footer' => 0,
        'orientation' => 'L',
    );
    $pdf = new \Mpdf\Mpdf($pdfConfig);
    $pdf->SetTitle('Maandkalender '.$this->_year);
    $pdf->SetAuthor('Frans-Willem Post (FWieP)');

    $css = file_get_contents('style.css');
    $pdf->WriteHTML($css, \Mpdf\HTMLParserMode::HEADER_CSS);

    $pdf->AddPage();
    $html = '<table class="scaffold"><tr>';
    for ($m = 1; $m <= 3; $m++) {
        // Add january - march to the first page's first row
        $html .= '<td>'.$this->_generateMonthTable($m).'</td>';
    }
    $html .= '</tr><tr>';
    for ($m = 4; $m <= 6; $m++) {
        // Add april - june to the first page's second row
        $html .= '<td>'.$this->_generateMonthTable($m).'</td>';
    }
    $html .= '</tr></table>';
    $pdf->WriteHTML($html, \Mpdf\HTMLParserMode::HTML_BODY);

    $pdf->AddPage();
    $html = '<table class="scaffold"><tr>';
    for ($m = 7; $m <= 9; $m++) {
        // Add july - september to the second page's first row
        $html .= '<td>'.$this->_generateMonthTable($m).'</td>';
    }
    $html .= '</tr><tr>';
    for ($m = 10; $m <= 12; $m++) {
        // Add october - december to the second page's second row
        $html .= '<td>'.$this->_generateMonthTable($m).'</td>';
    }
    $html .= '</tr></table>';
    $pdf->WriteHTML($html, \Mpdf\HTMLParserMode::HTML_BODY);

    $pdf->Output('Maandkalender '.$this->_year.'.pdf', D::DOWNLOAD);
}

Epiloog

Nog een kleine tip achteraf: bij het afdrukken van deze maandkalender op één vel van A4-formaat, ben je afhankelijk van de printerinstellingen. De meeste programma's en besturingssystemen bieden een mogelijkheid om 2, 4, 8 of 16 pagina's op één vel te printen.

In mijn geval moest ik dan wel het schaal-percentage op 141% zetten. Laat dat nou precies de verhouding tussen A5 en A4 zijn (2 × 100% = 141%)!

Terug naar boven

Inhoudsopgave

Delen

Met de deel-knop van uw browser, of met onderstaande koppelingen deelt u deze pagina via sociale media of e-mail.

Atom-feed van FWiePs weblog

Artikelen


Categorieën

Doorzoek de onderstaande categorieën om de lijst met artikelen te filteren.


Terug naar boven