<?php
/**
 * Rubify action plugin (post processing)
 *
 * @license	GPLv3 (http://www.gnu.org/licenses/gpl.html)
 * @link	   http://www.dokuwiki.org/plugin:dpicorrect
 * @author	 Mike "Pomax" Kamermans <pomax@nihongoresources.com>
 */

if(!defined('DOKU_INC')) die();
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once(DOKU_PLUGIN.'action.php');

class action_plugin_dpicorrect extends DokuWiki_Action_Plugin {

	var $NO_RESOLUTION = -1;

	function getInfo() {
	  return array(
		'author' => 'Mike "Pomax" Kamermans',
		'email'  => 'pomax@nihongoresources.com',
		'date'   => '2010-09-16',
		'name'   => 'DPI correct',
		'desc'   => 'Corrects image size based on their resolution (dpi), giving them CSS width and height values in inches, instead of pixels',
		'url'	=> 'http://www.dokuwiki.org/plugin:dpicorrect');
	}

	/**
	 * Postprocesses the html that was built from that, to rubify kanji that have associated furigana.
	 */
	function register(&$controller) 
	{
		$controller->register_hook('IMAGE_LINK_POSTPROCESS', 'AFTER', $this, '_dpicorrect');
	}

	/**
	 * check if an image was a .jpg or .png image, because these can have custom dpi values.
	 */
	function _dpicorrect(&$event, $param)
	{
		// reference to data and associated data type
		$img = $event->data;
		
		// get image link. We only care about JPG and PNG, because GIF images are an on-screen format, and inherit the screen's dpi
		preg_match("/media=([^\"]+\.(jpg|png))\"/",$img,$matches);
		$link = str_replace(":","/",$matches[1]);
		
		$file = 'data/media/'.$link;

		// Not unimportant: only run if the link is valid.
		if(file_exists($file))
		{
			if(strpos($link,".jpg")!==false) {
				$jpg = ImageCreateFromJPEG($file);
				$imgdpi = $this->get_dpi_for_jpeg($file);
				// if there is no explicit dpi value, leave this image alone
				if($imgdpi == $this->NO_RESOLUTION) { return; }
				// if it's not, compute the corrected image dimensions
				$width = ImageSX($jpg) / $imgdpi["x"];
				$height = ImageSY($jpg) / $imgdpi["y"]; }

			else if (strpos($link,".png")!==false) {
				$png = imagecreatefrompng($file);
				$imgdpi = $this->get_dpi_for_png($file);
				// if there is no explicit dpi value, leave this image alone
				if($imgdpi == $this->NO_RESOLUTION) { return; }
				// if it's not, compute the corrected image dimensions
				$width = ImageSX($png) / $imgdpi["x"];
				$height = ImageSY($png) / $imgdpi["y"]; }
			
			// retrofit the dimensions as explicit width/height attributes now
			$event->data = str_replace('class="media"','class="media" style="width: '.$width.'in; height: '.$height.'in;"',$img);
		}
	}

	/**
	 * Mostly from http://www.w3.org/Graphics/JPEG/jfif3.pdf, with with a quick peek
	 * for the JFIF mark, because it's not guaranteeds to be at byte position 2!
	 */
	function get_dpi_for_jpeg($filename)
	{
		// open the file and read first 20 bytes.  
		$a = fopen($filename,'r');  
		$string = fread($a,20);
		fclose($a);  
		$pos = strpos($string, "JFIF");
		// if the image claims it's jpg but has no JFIF header, we can't do anything with it.
		if($pos===false) { return $this->NO_RESOLUTION; }
		// JFIF is followed by a zero byte, then a 2 byte version number, then 1 byte 'unit', and 2x2 byte x/y dimensions
		$pos += 5 + 2;
		$unit = ord(substr($string, $pos, 1));
		$x = (ord(substr($string, $pos+1, 1)) << 8) + ord(substr($string, $pos+2, 1));
		$y = (ord(substr($string, $pos+3, 1)) << 8) + ord(substr($string, $pos+4, 1));
		// if unit is 0, there is no unit. if unit is 1, the dimensions are in  pixels per inch, and if the unit is 2, dimensions are in pixels per cm.
		// note that if the dimensions are 0 pixels per unit, no sensible resolution can be determined.
		if($unit == 0 || $xdim==0 || $ydim==0) { return $this->NO_RESOLUTION; }
		if($unit==2) { $x *= 2.54; $y *= 2.54; }
		// finally, return
		return array("x"=>$x, "y"=>$y);
	}

	/**
	 * PNG file layout: cycle through chunks until we find "pHYs" chunk. This chunk consists of 4 + 4 + 1 bytes:
	 *   - xdim in pixels per unit
	 *   - ydim in pixels per unit
	 *   - unit (0=unspecified==useless, 1=meters)
	 * see http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html
	 */
	function get_dpi_for_png($filename)
	{
		// open the file and start reading chunks
		$a = fopen($filename,'r');  
		fread($a,8);  // dummy data: 0x137 0x80 0x78 0x71 0x13 0x10 0x26 0x10
		$skip = 0;
		$chunk = "";
		while($chunk != "pHYs" && $chunk != "IDAT") {
			// skip ahead to next chunk, making sure to skip the 4 byte crc, too
			if($skip>0) { fread($a, $skip+4);}
			// get size of chunk (in terms of bytes that it takes up after the chunk name)
			$b1 = ord(fread($a,1)) << 24; $b2 = ord(fread($a,1)) << 16; $b3 = ord(fread($a,1)) << 8; $b4 = ord(fread($a,1));
			$skip = $b1  + $b2 + $b3 + $b4;
			// get name of chunk
			$chunk = fread($a,4); }
		// no physical dimensions chunk exists: assume screen resolution (dpi=72)
		if($chunk == "IDAT") { fclose($a); return $this->NO_RESOLUTION; }

		// pHYs chunk found, get xdim/ydim/unit values
		$b1 = ord(fread($a,1)) << 24; $b2 = ord(fread($a,1)) << 16; $b3 = ord(fread($a,1)) << 8; $b4 = ord(fread($a,1));
		$xdim = $b1  + $b2 + $b3 + $b4;
		$b1 = ord(fread($a,1)) << 24; $b2 = ord(fread($a,1)) << 16; $b3 = ord(fread($a,1)) << 8; $b4 = ord(fread($a,1));
		$ydim = $b1  + $b2 + $b3 + $b4;
		$unit = ord(fread($a,1));
		fclose($a);
		
		// if the unit is unspecified, it means we can't tell the physical dimensions. Same for if there are 0 pixels per unit.
		if($unit != 1 || $xdim==0 || $ydim==0) { return $this->NO_RESOLUTION; }
		
		// For PNG, dimensions are written as pixels per meter, so we need to conver to pixels per inch
		return array("x"=>$xdim * 0.0254, "y"=>$ydim * 0.0254);
	}
}
