# FT_Bitmap to CAIRO_SURFACE_TYPE_IMAGE module
# ============================================
#
# Copyright 2017 Hin-Tak Leung
#
# FreeType is under FTL (BSD license with an advertising clause) or GPLv2+.
# Cairo is under LGPLv2 or MPLv1.1.
#
# This is a heavily modified copy of a few routines from Lawrence D'Oliveiro[1],
# adjusted for freetype-py, and bugfix/workaround for mono-rendering [2].
#
# The bugfix/workaround requires libtiff on small-endian platforms.
#
# TODO: Look into using FreeType's FT_Bitmap_Convert() instead. However, libtiff
#      is common enough, and probably not important.
#
#[1] https://github.com/ldo/python_freetype
#    https://github.com/ldo/python_freetype_examples
#
#[2] https://github.com/ldo/python_freetype/issues/1
#    https://github.com/ldo/python_freetype_examples/issues/1
#
'''
FT_Bitmap to CAIRO_SURFACE_TYPE_IMAGE module
============================================

Converting from Freetype's FT_Bitmap to Cairo's CAIRO_SURFACE_TYPE_IMAGE

Usage:
     from bitmap_to_surface import make_image_surface

Works with cairocffi too. (Replace "from cairo ..." with "from cairocffi ...")

Limitation: Surface.create_for_data is not in the "python 3, pycairo < 1.11" combo.
'''
from freetype import FT_PIXEL_MODE_MONO, FT_PIXEL_MODE_GRAY, FT_PIXEL_MODE_BGRA, FT_Pointer, FT_Bitmap

from cairo import ImageSurface, FORMAT_A1, FORMAT_A8, FORMAT_ARGB32
#from cairocffi import ImageSurface, FORMAT_A1, FORMAT_A8, FORMAT_ARGB32

from array import array
from ctypes import cast, memmove, CDLL, c_void_p, c_int
from sys import byteorder

def make_image_surface(bitmap, copy = True) :
    if ( type(bitmap) == FT_Bitmap ):
        # bare FT_Bitmap
        content = bitmap
    else:
        # wrapped Bitmap instance
        content = bitmap._FT_Bitmap
    "creates a Cairo ImageSurface containing (a copy of) the Bitmap pixels."
    if content.pixel_mode == FT_PIXEL_MODE_MONO :
        cairo_format = FORMAT_A1
    elif content.pixel_mode == FT_PIXEL_MODE_GRAY :
        cairo_format = FORMAT_A8
    elif content.pixel_mode == FT_PIXEL_MODE_BGRA : # small-endian
        cairo_format = FORMAT_ARGB32 # platform native
    else :
        raise NotImplementedError("unsupported bitmap format %d" % content.pixel_mode)
    src_pitch = content.pitch
    dst_pitch = ImageSurface.format_stride_for_width(cairo_format, content.width)
    if not copy and dst_pitch == src_pitch and content.buffer != None :
        pixels = content.buffer
    else :
        pixels = to_array(content, content.pixel_mode, dst_pitch)
    result =  ImageSurface.create_for_data(
        pixels, cairo_format,
        content.width, content.rows,
        dst_pitch)
    return result

def to_array(content, pixel_mode, dst_pitch = None) :
    "returns a Python array object containing a copy of the Bitmap pixels."
    if dst_pitch == None :
        dst_pitch = content.pitch
    buffer_size = content.rows * dst_pitch
    buffer = array("B", b"0" * buffer_size)
    dstaddr = buffer.buffer_info()[0]
    srcaddr = cast(content.buffer, FT_Pointer).value
    src_pitch = content.pitch
    if dst_pitch == src_pitch :
        memmove(dstaddr, srcaddr, buffer_size)
    else :
        # have to copy a row at a time
        if src_pitch < 0 or dst_pitch < 0 :
            raise NotImplementedError("can't cope with negative bitmap pitch")
        assert dst_pitch > src_pitch
        for i in range(content.rows) :
            memmove(dstaddr, srcaddr, src_pitch)
            dstaddr += dst_pitch
            srcaddr += src_pitch
    # pillow/PIL itself requires libtiff so it is assumed to be around.
    # swap the bit-order from freetype's (MSB) to cairo's (host order) if needed
    if ( ( byteorder == 'little' ) and (pixel_mode == FT_PIXEL_MODE_MONO ) ):
        libtiff = CDLL("libtiff.so.5")
        libtiff.TIFFReverseBits.restype = None
        libtiff.TIFFReverseBits.argtypes = (c_void_p, c_int)
        libtiff.TIFFReverseBits(buffer.buffer_info()[0], buffer_size)
    return buffer
