# ----------------------------------------------------------------------------
# pyglet
# Copyright (c) 2006-2008 Alex Holkner
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#  * Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
#  * Neither the name of pyglet nor the names of its
#    contributors may be used to endorse or promote products
#    derived from this software without specific prior written
#    permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------

'''Abstract classes used by pyglet.font implementations.

These classes should not be constructed directly.  Instead, use the functions
in `pyglet.font` to obtain platform-specific instances.  You can use these
classes as a documented interface to the concrete classes.
'''

__docformat__ = 'restructuredtext'
__version__ = '$Id: base.py 2541 2009-12-31 04:31:11Z benjamin.coder.smith@gmail.com $'

import unicodedata

from pyglet.gl import *
from pyglet import image

_other_grapheme_extend = \
    map(unichr, [0x09be, 0x09d7, 0x0be3, 0x0b57, 0x0bbe, 0x0bd7, 0x0cc2,
                 0x0cd5, 0x0cd6, 0x0d3e, 0x0d57, 0x0dcf, 0x0ddf, 0x200c,
                 0x200d, 0xff9e, 0xff9f]) # skip codepoints above U+10000
_logical_order_exception = \
    map(unichr, range(0xe40, 0xe45) + range(0xec0, 0xec4))

_grapheme_extend = lambda c, cc: \
    cc in ('Me', 'Mn') or c in _other_grapheme_extend

_CR = u'\u000d'
_LF = u'\u000a'
_control = lambda c, cc: cc in ('ZI', 'Zp', 'Cc', 'Cf') and not \
    c in map(unichr, [0x000d, 0x000a, 0x200c, 0x200d])
_extend = lambda c, cc: _grapheme_extend(c, cc) or \
    c in map(unichr, [0xe30, 0xe32, 0xe33, 0xe45, 0xeb0, 0xeb2, 0xeb3])
_prepend = lambda c, cc: c in _logical_order_exception
_spacing_mark = lambda c, cc: cc == 'Mc' and c not in _other_grapheme_extend

def _grapheme_break(left, right):
    # GB1
    if left is None:
        return True

    # GB2 not required, see end of get_grapheme_clusters

    # GB3
    if left == _CR and right == _LF:
        return False

    left_cc = unicodedata.category(left)

    # GB4
    if _control(left, left_cc):
        return True

    right_cc = unicodedata.category(right)

    # GB5
    if _control(right, right_cc):
        return True

    # GB6, GB7, GB8 not implemented

    # GB9
    if _extend(right, right_cc):
        return False

    # GB9a
    if _spacing_mark(right, right_cc):
        return False

    # GB9b
    if _prepend(left, left_cc):
        return False

    # GB10
    return True

def get_grapheme_clusters(text):
    '''Implements Table 2 of UAX #29: Grapheme Cluster Boundaries.

    Does not currently implement Hangul syllable rules.

    :Parameters:
        `text` : unicode
            String to cluster.

    :since: pyglet 1.1.2

    :rtype: List of `unicode`
    :return: List of Unicode grapheme clusters
    '''
    clusters = []
    cluster = ''
    left = None
    for right in text:
        if cluster and _grapheme_break(left, right):
            clusters.append(cluster)
            cluster = ''
        elif cluster:
            # Add a zero-width space to keep len(clusters) == len(text)
            clusters.append(u'\u200b')
        cluster += right
        left = right

    # GB2
    if cluster:
        clusters.append(cluster)
    return clusters

class Glyph(image.TextureRegion):
    '''A single glyph located within a larger texture.

    Glyphs are drawn most efficiently using the higher level APIs, for example
    `GlyphString`.

    :Ivariables:
        `advance` : int
            The horizontal advance of this glyph, in pixels.
        `vertices` : (int, int, int, int)
            The vertices of this glyph, with (0,0) originating at the
            left-side bearing at the baseline.

    '''

    advance = 0
    vertices = (0, 0, 0, 0)

    def set_bearings(self, baseline, left_side_bearing, advance):
        '''Set metrics for this glyph.

        :Parameters:
            `baseline` : int
                Distance from the bottom of the glyph to its baseline;
                typically negative.
            `left_side_bearing` : int
                Distance to add to the left edge of the glyph.
            `advance` : int
                Distance to move the horizontal advance to the next glyph.

        '''
        self.advance = advance
        self.vertices = (
            left_side_bearing,
            -baseline,
            left_side_bearing + self.width,
            -baseline + self.height)

    def draw(self):
        '''Debug method.

        Use the higher level APIs for performance and kerning.
        '''
        glBindTexture(GL_TEXTURE_2D, self.owner.id)
        glBegin(GL_QUADS)
        self.draw_quad_vertices()
        glEnd()

    def draw_quad_vertices(self):
        '''Debug method.

        Use the higher level APIs for performance and kerning.
        '''
        glTexCoord3f(*self.tex_coords[:3])
        glVertex2f(self.vertices[0], self.vertices[1])
        glTexCoord3f(*self.tex_coords[3:6])
        glVertex2f(self.vertices[2], self.vertices[1])
        glTexCoord3f(*self.tex_coords[6:9])
        glVertex2f(self.vertices[2], self.vertices[3])
        glTexCoord3f(*self.tex_coords[9:12])
        glVertex2f(self.vertices[0], self.vertices[3])

    def get_kerning_pair(self, right_glyph):
        '''Not implemented.
        '''
        return 0

class GlyphTextureAtlas(image.Texture):
    '''A texture within which glyphs can be drawn.
    '''
    region_class = Glyph
    x = 0
    y = 0
    line_height = 0

    def apply_blend_state(self):
        '''Set the OpenGL blend state for the glyphs in this texture.
        '''
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glEnable(GL_BLEND)

    def fit(self, image):
        '''Place `image` within this texture.

        :Parameters:
            `image` : `pyglet.image.AbstractImage`
                Image to place within the texture.

        :rtype: `Glyph`
        :return: The glyph representing the image from this texture, or None
            if the image doesn't fit.
        '''
        if self.x + image.width > self.width:
            self.x = 0
            self.y += self.line_height
            self.line_height = 0
        if self.y + image.height > self.height:
            return None

        self.line_height = max(self.line_height, image.height)
        region = self.get_region(
            self.x, self.y, image.width, image.height)
        if image.width > 0:
            region.blit_into(image, 0, 0, 0)
            self.x += image.width + 1
        return region

class GlyphRenderer(object):
    '''Abstract class for creating glyph images.
    '''
    def __init__(self, font):
        pass

    def render(self, text):
        raise NotImplementedError('Subclass must override')

class FontException(Exception):
    '''Generic exception related to errors from the font module.  Typically
    these relate to invalid font data.'''
    pass

class Font(object):
    '''Abstract font class able to produce glyphs.

    To construct a font, use `pyglet.font.load`, which will instantiate the
    platform-specific font class.

    Internally, this class is used by the platform classes to manage the set
    of textures into which glyphs are written.

    :Ivariables:
        `ascent` : int
            Maximum ascent above the baseline, in pixels.
        `descent` : int
            Maximum descent below the baseline, in pixels. Usually negative.
    '''
    texture_width = 256
    texture_height = 256
    texture_internalformat = GL_ALPHA

    # These should also be set by subclass when known
    ascent = 0
    descent = 0

    glyph_renderer_class = GlyphRenderer
    texture_class = GlyphTextureAtlas

    def __init__(self):
        self.textures = []
        self.glyphs = {}

    @classmethod
    def add_font_data(cls, data):