<?php

class MiniPdf
{
    private string $orientation = 'P';
    private string $unit = 'mm';
    private array $pages = [];
    private array $fonts = [];
    private float $x = 0;
    private float $y = 0;
    private float $lMargin = 10;
    private float $tMargin = 10;
    private float $pageW = 210;
    private float $pageH = 297;
    private float $curFontSize = 10;
    private string $curFont = 'Helvetica';
    private string $title = '';

    public function __construct(string $orientation = 'P', string $unit = 'mm', string $format = 'A4')
    {
        $this->orientation = $orientation;
        $this->unit = $unit;
        if ($format === 'A4') {
            if ($orientation === 'P') { $this->pageW = 210; $this->pageH = 297; }
            else { $this->pageW = 297; $this->pageH = 210; }
        }
        $this->x = $this->lMargin;
        $this->y = $this->tMargin;
    }

    public function setTitle(string $title): void { $this->title = $title; }

    public function addPage(): void
    {
        $this->pages[] = ['objects' => [], 'content' => ''];
        $this->x = $this->lMargin;
        $this->y = $this->tMargin;
    }

    public function setFont(string $family, float $size): void
    {
        $this->curFont = $family;
        $this->curFontSize = $size;
    }

    public function setFillColor(int $r, int $g, int $b): void
    {
        $this->fillR = $r;
        $this->fillG = $g;
        $this->fillB = $b;
    }
    private int $fillR = 255;
    private int $fillG = 255;
    private int $fillB = 255;
    private int $textR = 0;
    private int $textG = 0;
    private int $textB = 0;

    public function setTextColor(int $r, int $g, int $b): void
    {
        $this->textR = $r;
        $this->textG = $g;
        $this->textB = $b;
    }

    public function ln(float $h = 5): void
    {
        $this->y += $h;
        $this->x = $this->lMargin;
    }

    public function cell(float $w, float $h, string $txt, int $border = 0, int $ln = 0, string $align = 'L', bool $fill = false): void
    {
        $hMm = $h;
        $idx = count($this->pages) - 1;
        if ($idx < 0) return;
        $page = &$this->pages[$idx];
        $content = &$page['content'];

        if ($fill) {
            $content .= sprintf("%.2f %.2f %.2f rg\n", $this->fillR / 255, $this->fillG / 255, $this->fillB / 255);
            $content .= sprintf("%.2f %.2f %.2f %.2f re f\n", $this->x, $this->y, $w, $hMm);
        }

        if ($border) {
            $content .= sprintf("%.2f %.2f %.2f %.2f re S\n", $this->x, $this->y, $w, $hMm);
        }

        $content .= sprintf("%.2f %.2f %.2f rg\n", $this->textR / 255, $this->textG / 255, $this->textB / 255);
        $fs = $this->curFontSize * 0.3528;

        $alignX = $this->x + 1;
        if ($align === 'C') $alignX = $this->x + $w / 2;
        elseif ($align === 'R') $alignX = $this->x + $w - 1;

        $escaped = $this->escapeText($txt);
        $content .= sprintf("BT /F1 %.2f Tf %.2f %.2f Td (%s) Tj ET\n", $fs, $alignX, $this->y + $hMm * 0.35, $escaped);

        $this->x += $w;
        if ($ln) {
            $this->y += $hMm;
            $this->x = $this->lMargin;
        }
    }

    private function escapeText(string $s): string
    {
        $s = iconv('UTF-8', 'windows-1252//TRANSLIT', $s);
        $s = str_replace(['\\', '(', ')'], ['\\\\', '\\(', '\\)'], $s ?? '');
        return $s;
    }

    public function output(string $filename): void
    {
        $pdf = '%PDF-1.4' . "\n";
        $objects = [];
        $offsets = [];
        $objNum = 1;

        // Font object
        $fonts = "/F1 " . ($objNum) . " 0 R\n";
        $offsets[$objNum] = strlen($pdf);
        $pdf .= $objNum . " 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n";
        $objNum++;

        foreach ($this->pages as $pi => $page) {
            $content = $page['content'];
            if ($content === '') $content = ' ';

            $stream = gzcompress($content);
            $streamLen = strlen($stream);

            // Content stream object
            $offsets[$objNum] = strlen($pdf);
            $pdf .= $objNum . " 0 obj\n<< /Length $streamLen /Filter /FlateDecode >>\nstream\n$stream\nendstream\nendobj\n";
            $contentObj = $objNum;
            $objNum++;

            // Page object
            $offsets[$objNum] = strlen($pdf);
            $pdf .= $objNum . " 0 obj\n<< /Type /Page /Parent 3 0 R /MediaBox [0 0 {$this->pageW} {$this->pageH}] /Contents $contentObj 0 R /Resources << /Font << $fonts >> >> >>\nendobj\n";
            $pageObj = $objNum;
            $objNum++;

            $pagesObjs[] = $pageObj;
        }

        // Pages object
        $pagesObjNum = $objNum;
        $offsets[$objNum] = strlen($pdf);
        $kids = implode(' 0 R ', $pagesObjs) . ' 0 R';
        $pdf .= $objNum . " 0 obj\n<< /Type /Pages /Count " . count($this->pages) . " /Kids [$kids] >>\nendobj\n";
        $objNum++;

        // Catalog
        $catalogObjNum = $objNum;
        $offsets[$objNum] = strlen($pdf);
        $pdf .= $objNum . " 0 obj\n<< /Type /Catalog /Pages $pagesObjNum 0 R";
        if ($this->title !== '') {
            $pdf .= " /Metadata << /Subtype /XML /Type /Metadata /Length 0 >>";
        }
        $pdf .= " >>\nendobj\n";

        // Cross-reference table
        $xrefOffset = strlen($pdf);
        $pdf .= "xref\n0 " . ($objNum + 1) . "\n0000000000 65535 f \n";
        for ($i = 1; $i <= $objNum; $i++) {
            $pdf .= sprintf("%010d 00000 n \n", $offsets[$i]);
        }

        $pdf .= "trailer\n<< /Size " . ($objNum + 1) . " /Root $catalogObjNum 0 R >>\n";
        $pdf .= "startxref\n$xrefOffset\n%%EOF";

        if (ob_get_level()) ob_end_clean();
        header('Content-Type: application/pdf');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        echo $pdf;
    }
}
