#! /usr/bin/python

# Copyright (C) 2000, 2001, 2002 Intevation GmbH <intevation@intevation.de>
# Author: Bernhard Herzog <bh@intevation.de>
#
# This program is free software under the LGPL (>=v2)
# Read the file COPYING coming with MapIt! for details.

import sys, os
import tempfile
import dscparser
import PIL.Image
from math import floor, ceil

__version__ = "$Revision: 1.4 $"


#
#       Directory helper functions
#

def create_directory(dir):
    """Create the directory dir and all its parent directories if necessary"""
    if os.path.isdir(dir):
	return
    parent, base = os.path.split(dir)
    if parent:
        create_directory(parent)
    try:
        os.mkdir(dir, 0777)
    except os.error, exc:
        print "can't create directory %s:%s" % (dir, exc)
        raise

def make_directories(basedir, num_factors):
    """Create the tile directories for the individual scales under basedir.

    For each number in range(1, num_factors + 1) a directory is created
    under basedir. The numbers used for the names are 'backwards', i.e.
    num_factors for the lowest resolution and 1 for the highest, because
    that's what MapIt! expects (a map of 1:5000 gives more detail than a
    map of 1:100000).

    Return a list with the names of the directories created.
    """
    
    dirs = []
    for i in range(num_factors, 0, -1):
        dir = os.path.join(basedir, `i`)
        create_directory(dir)
        dirs.append(dir)
    return dirs


#
#       EPS rendering
#
# Render part of an eps file with ghostscript

# The command line to invoke ghostscript. Be careful with the quiting
# here so that it works under both, Linux/Unix and and NT, which
# basically means that double-quotes are the only acceptable quoting
# character.
gs_command = ('gs -sDEVICE=%(psdevice)s -r72 -dNOPAUSE -dSAFER -q'
              ' %(alphaparams)s'
	      ' -sOutputFile=%(output)s -g%(width)dx%(height)d'
	      ' -c "%(scalex)f %(scaley)f scale %(offx)f %(offy)f translate'
	      ' /oldshowpage /showpage load def /showpage {} def" '
	      ' -f%(filename)s -c oldshowpage quit')


def render_tile(filename, width, height, startx, starty, scalex, scaley,
                outputfile, alpha = 1, indexed = 0, supersampling = 1):
    """Render a region of an EPS-file filename.
    Parameters:

        filename -- name of the EPS-file
        width, height -- size of the output region in pixels
        startx, starty -- lower left corner of the region in point
        scalex, scaley -- scale factors
        outputfile -- name of the output file. The file format is
                      derived from the name by PIL
        alpha -- number of bits for anti-aliasing:
                1 = no antialiasing, 2 = medium, 4 maximum

    """
    temp = tempfile.mktemp()
    try:
        psdevice = "ppmraw"
        offx = -startx
        offy = -starty
        if alpha > 1:
            alphaparams = '-dGraphicsAlphaBits=%d -dTextAlphaBits=%d' \
                          % (alpha, alpha)
        else:
            alphaparams = ''
        output = temp

        if supersampling != 1:
            # if we're supersampling adapt scalex, scaley and the image
            # size so that we render into an appropriately larger image.
            # Save the intended size in old_width and old_width so that
            # we can resize to the correct size later.
            scalex = scalex * supersampling
            scaley = scaley * supersampling
            old_width = width
            old_height = height
            width = width * supersampling
            height = height * supersampling

        # render the tile
        print gs_command % locals()
        os.system(gs_command % locals())

        # read the rendered tile and convert it to the right output
        # format, resizing it if supersampling.
        image = PIL.Image.open(temp)
        if supersampling != 1:
            image = image.resize((old_width, old_height), PIL.Image.BILINEAR)
        if indexed:
            image = image.convert(mode = 'P', palette = PIL.Image.ADAPTIVE,
                                  dither = PIL.Image.NONE)
        print 'writing %s' % outputfile
        image.save(outputfile)
    finally:
        try:
            # Make sure to delete the temporary file
	    os.unlink(temp)
	except:
	    pass


class EPSFile:

    """Class to represent one EPS file.

    Public Instance Variables:

        width, height -- size of the bounding box (in pt)
        bbox -- bounding box as a tuple

    Public Methods:

        render tiles -- Render the tiles and create the info file for
                        one specific scale
    """

    def __init__(self, filename):
        self.filename = filename
        self.info = dscparser.parse_eps_file(filename)
        llx, lly, urx, ury = self.info.BoundingBox
        self.width = urx - llx
        self.height = ury - lly
        self.bbox = self.info.BoundingBox

    def render_tiles(self, size, directory, num_tiles_x, num_tiles_y,
                     region, reference_size):
        scale = min(float(size[0]) / self.width,
                    float(size[1]) / self.height)
        total_width = int(round(scale * self.width))
        total_height = int(round(scale * self.height))
        tile_width = int(round(float(total_width) / num_tiles_x))
        tile_height = int(round(float(total_height) / num_tiles_y))
        # now compute the scale that are actually used from the tile sizes
        scalex = tile_width * num_tiles_x / float(self.width)
        scaley = tile_height * num_tiles_y / float(self.height)

        infofile = open(os.path.join(directory, 'info'), 'w')
        infofile.write("%d\n%d\n" % (tile_width, tile_height))

        llx, lly, urx, ury = self.info.BoundingBox
        extension = '.png'; alpha = 1
        if region is not None:
            left, bottom, right, top = region
            x_range = range(floor(scalex * (left - llx) / tile_width),
                            ceil(scalex * (right - llx) / tile_width))
            y_range = range(floor(scaley * (bottom - lly) / tile_height),
                            ceil(scaley * (top - lly) / tile_height))
        else:
            x_range = range(num_tiles_x)
            y_range = range(num_tiles_y)
        for y in y_range:
            for x in x_range:
                outputfile = os.path.join(directory,
                                          "%dx%d%s" % (x + 1, y + 1,
                                                       extension))
                startx = tile_width * x / scalex + llx
                starty = tile_height * y / scaley + lly
                render_tile(self.filename, tile_width, tile_height,
                            startx, starty, scalex, scaley, outputfile,
                            alpha=alpha, indexed = 1)
                ref_llx = x * reference_size[0] / num_tiles_x
                ref_lly = y * reference_size[1] / num_tiles_y
                ref_urx = (x + 1) * reference_size[0] / num_tiles_x
                ref_ury = (y + 1) * reference_size[1] / num_tiles_y
                infofile.write("# [%d,%d]\n" % (x + 1, y + 1))
                infofile.write("%d\n%d\n%d\n%d\n" % (ref_llx, ref_lly,
                                                     ref_urx, ref_ury))
        infofile.close()
#
#
#

def make_tile_hierarchy(epsfilename, basedir, factors, regions, overview_size):
    """Render a complete tile hierarchy from an EPS.

    For each magnification factor, create directories under basedir and
    render the tiles for that factor in that directory. At the end,
    print information about the transformation of the standard
    postscript coordinate system (the one in which the BoundingBox is
    specified) into the tileset's coordinate system to stddout.

    Parameters:

    epsfilename -- the filename of the EPS file to render
    basedir -- directory in which to create the tile sets

    factors -- list of magnification factors. See below.
    regions -- list of regions, one for each factor, or None. See below

    overview_size -- the size (width, height) of the overview tile.

    The overview_size is the basis for the total sizes of the tile sets.
    Each factor in factors gives the size of a tileset as a multiple of
    the overview size.

    regions if it is not None, is a sequence of regions, where a region
    may either be None or a 4-tuple (left, bottom, right, top) in the
    EPS coordinates giving the region of the EPS to cover by the tiles
    of the corresponding tile set. None means the entire BoundingBox.
    The regions list may be shorter than the factors list, in which case
    the "missing" entries at the end are assumed to be None. If regions
    itself is None, all tilesets will cover the entire bounding box.
    """
    
    eps = EPSFile(epsfilename)
    overview_scale = min(overview_size[0] / eps.width,
                         overview_size[1] / eps.height)
    overview_width = int(round(overview_scale * eps.width))
    overview_height = int(round(overview_scale * eps.height))

    directories = make_directories(basedir, len(factors))

    reference_size = (factors[-1] * overview_size[0],
                      factors[-1] * overview_size[1])
    for i in range(len(factors)):
        size = (factors[i] * overview_width,
                factors[i] * overview_height)
        if regions is not None and len(regions) > i:
            region = regions[i]
        else:
            region = None
        eps.render_tiles(size, directories[i], factors[i],
                         factors[i], region, reference_size)
    # Print the transformation from EPS coordinates to MapIt internal
    # coordinates
    llx, lly, urx, ury = eps.bbox    
    print "Transformation from EPS-coordinates (px, py)"
    print "to MapIt! coordinates (mx, my):"
    print "mx = %.17g * (px - %.17g)" % (reference_size[0] / float(eps.width),
                                         llx)
    print "my = %.17g * (py - %.17g)" % (reference_size[1] / float(eps.height),
                                         lly)


def main():
    if len(sys.argv) < 2:
        print 'Usage: epscut epsfile [basedir]'
        sys.exit(1)
    epsfile = sys.argv[1]
    if len(sys.argv) < 3:
        basedir = os.path.splitext(os.path.basename(epsfile))[0]
    else:
        basedir = sys.argv[2]

    # For factors and regions, see the doc-string of make_tile_hierarchy
    factors = (1, 2)
    regions = (None)

    overview_size = (400, 400)

    make_tile_hierarchy(epsfile, basedir, factors, regions, overview_size)


if __name__ == '__main__':
    main()
