<?php

/*
 * (c) Serge Emond, 2008
 * <serge @at@ greyworld .dot. net>
 * http://greyworld.net/
 * 
 * *WARNING* This code is not for production (e.g. is incomplete, and NOT tested)
 */

/** Class */

class Code39
{
    /** Thick line's width multiplicator over thin line's width. */
    private $bcThinThickRatio = 3;

    /** Coding map.
     * 0 = thin line, 1 = thick line
     */
    private $bcMap = array(
                           '0' => '000110100',
                           '1' => '100100001',
                           '2' => '001100001',
                           '3' => '101100000',
                           '4' => '000110001',
                           '5' => '100110000',
                           '6' => '001110000',
                           '7' => '000100101',
                           '8' => '100100100',
                           '9' => '001100100',
                           'A' => '100001001',
                           'B' => '001001001',
                           'C' => '101001000',
                           'D' => '000011001',
                           'E' => '100011000',
                           'F' => '001011000',
                           'G' => '000001101',
                           'H' => '100001100',
                           'I' => '001001100',
                           'J' => '000011100',
                           'K' => '100000011',
                           'L' => '001000011',
                           'M' => '101000010',
                           'N' => '000010011',
                           'O' => '100010010',
                           'P' => '001010010',
                           'Q' => '000000111',
                           'R' => '100000110',
                           'S' => '001000110',
                           'T' => '000010110',
                           'U' => '110000001',
                           'V' => '011000001',
                           'W' => '111000000',
                           'X' => '010010001',
                           'Y' => '110010000',
                           'Z' => '011010000',
                           '-' => '010000101',
                           '*' => '010010100',
                           '+' => '010001010',
                           '$' => '010101000',
                           '%' => '000101010',
                           '/' => '010100010',
                           '.' => '110000100',
                           ' ' => '011000100'
                           );

    /** Draw the barcode.
     * @param $text (string) text to render
     * @param $height (int) height
     * @param $hunits (string) $height's units (cm, mm, in, px, whatever SVG supports)
     * @param $bcHeight (int) internal units height
     * @param $fntHeight (int) font's height in internal units (0 to disable), included in bcHeight
     * 
     * @retval (array) 0: width, 1: array of text (svg) lines
     */
    function render($text, $height, $hunits, $bcHeight, $fntHeight = 0)
    {
        // Append/prepend '*' as per specification
        $final_text = '*' . $text . '*';

        $barcode = array();
        $barcode_len = 0;
        $cnt = 0;
        foreach (str_split($final_text) as $character) {
            list($charWidth, $charCode) = $this->charToCode($character);
            $barcode_len += $charWidth;
            $barcode[] = array('char' => $character,
                               'code' => $charCode,
                               'width' => $charWidth);
        }

        $img = array();

        $img[] = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>';

        if ($fntHeight > 2) {
            $textHeight = $fntHeight - 2;
            $textY = -2;
        } else {
            $textHeight = $fntHeight;
            $textY = 0;
        }

        $heightWithFnt = $bcHeight;
        $bcHeight -= $fntHeight;
        $width = $barcode_len * $height / $heightWithFnt;
        $textY += $heightWithFnt;

        $img[] = sprintf('<svg:svg width="%.3f%s" height="%.3f%s" viewBox="0 0 %d %d" version="1.1" xmlns:svg="http://www.w3.org/2000/svg">',
                         $width, $hunits, $height, $hunits,
                         $barcode_len, $heightWithFnt);
        $img[] = sprintf('<svg:desc>%s</svg:desc>', htmlspecialchars($final_text));

        $img[] = '<svg:g style="stroke:white;fill:black">';

        $img[] = sprintf('<svg:rect width="%d" height="%d" fill="white" stroke-width="0" />',
                         $barcode_len, $heightWithFnt);

        $xpos = 0;

        foreach ($barcode as $char) {
            $img[] = '<svg:g>';
            $img[] = '<svg:desc>'.htmlspecialchars($char['char']).'</svg:desc>';
            if ($fntHeight > 0) {
                $img[] = sprintf('<svg:text x="%d" y="%d" font-family="Consolas, Courier New, Courier" font-size="%d" stroke-width="0" text-anchor="middle">%s</svg:text>',
                                 $xpos + (int)($char['width']/2), $textY, $textHeight, $char['char']);
            }
            foreach ($char['code'] as $cdef ) {
                if ($cdef['color'] == 'black') {
                    $img[] = sprintf('<svg:rect x="%d" y="%d" width="%d" height="%d" stroke-width="0" />',
                                     $xpos, 0, $cdef['width'], $bcHeight);
                }
                $xpos += $cdef['width'];
            }
            $img[] = '</svg:g>';
        }

        $img[] = '</svg:g>';
        $img[] = '</svg:svg>';

        return array($width, $img);
    } // render()
    
    /** Convert a character to 3of9 code */
    private function charToCode($char)
    {
        $code = $this->bcMap[$char] . '0';
        $color = 1; // 1: Black, 0: White, start with black

        $totalWidth = 0;
        $result = array();

        foreach (str_split($code) as $line) {
            $line_width = $line ? $this->bcThinThickRatio : 1;

            $result[] = array('color' => $color ? 'black' : 'white',
                              'width' => $line_width);
            $totalWidth += $line_width;

            // Invert color
            $color = $color ? 0 : 1;
        }
        
        // $totalWidth should be constant: 6*thin_width + 3*thick_width + thin_width
        // $totalWidth = 7 + 3 * $this->bcThinThickRatio
        
        return array($totalWidth, $result);
    } // charToCode()

} // Code39
