DEF LINE_CAP_NONE = 0
DEF LINE_CAP_SQUARE = 1
DEF LINE_CAP_ROUND = 2

DEF LINE_JOINT_NONE = 0
DEF LINE_JOINT_MITER = 1
DEF LINE_JOINT_BEVEL = 2
DEF LINE_JOINT_ROUND = 3

DEF LINE_MODE_POINTS = 0
DEF LINE_MODE_ELLIPSE = 1
DEF LINE_MODE_CIRCLE = 2
DEF LINE_MODE_RECTANGLE = 3
DEF LINE_MODE_ROUNDED_RECTANGLE = 4
DEF LINE_MODE_BEZIER = 5

from kivy.cache import Cache
from kivy.graphics.stencil_instructions cimport StencilUse, StencilUnUse, StencilPush, StencilPop
import itertools

# register graphics texture cache
Cache.register('kv.graphics.texture')

cdef float PI = <float>3.1415926535

cdef inline int line_intersection(double x1, double y1, double x2, double y2,
        double x3, double y3, double x4, double y4, double *px, double *py):
    cdef double u = (x1 * y2 - y1 * x2)
    cdef double v = (x3 * y4 - y3 * x4)
    cdef double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
    if denom == 0:
        return 0
    px[0] = (u * (x3 - x4) - (x1 - x2) * v) / denom
    py[0] = (u * (y3 - y4) - (y1 - y2) * v) / denom
    return 1

cdef class Line(VertexInstruction):
    '''A 2d line.

    Drawing a line can be done easily::

        with self.canvas:
            Line(points=[100, 100, 200, 100, 100, 200], width=10)

    The line has 3 internal drawing modes that you should be aware of
    for optimal results:

    #. If the :attr:`width` is 1.0 and :attr:`force_custom_drawing_method` is False, then the
       standard GL_LINE drawing from OpenGL will be used. :attr:`dash_length`,
       :attr:`dash_offset`, and :attr:`dashes` will work, while properties for
       cap and joint have no meaning here.
    #. If the :attr:`width` is greater than 1.0 or :attr:`force_custom_drawing_method`
       is True, then a custom drawing method, based on triangulation,
       will be used. :attr:`dash_length`, :attr:`dash_offset`,
       and :attr:`dashes` do not work in this mode.
       Additionally, if the current color has an alpha less than 1.0, a
       stencil will be used internally to draw the line.

    .. image:: images/line-instruction.png
        :align: center

    :Parameters:
        `points`: list
            List of points in the format (x1, y1, x2, y2...)
        `dash_length`: int
            Length of a segment (if dashed), defaults to 1.
        `dash_offset`: int
            Offset between the end of a segment and the beginning of the
            next one, defaults to 0. Changing this makes it dashed.
        `dashes`: list of ints
            List of [ON length, offset, ON length, offset, ...]. E.g. ``[2,4,1,6,8,2]``
            would create a line with the first dash length 2 then an offset of 4 then
            a dash length of 1 then an offset of 6 and so on. Defaults to ``[]``.
            Changing this makes it dashed and overrides `dash_length` and `dash_offset`.
        `width`: float
            Width of the line, defaults to 1.0.
        `cap`: str, defaults to 'round'
            See :attr:`cap` for more information.
        `joint`: str, defaults to 'round'
            See :attr:`joint` for more information.
        `cap_precision`: int, defaults to 10
            See :attr:`cap_precision` for more information
        `joint_precision`: int, defaults to 10
            See :attr:`joint_precision` for more information
            See :attr:`cap_precision` for more information.
        `joint_precision`: int, defaults to 10
            See :attr:`joint_precision` for more information.
        `close`: bool, defaults to False
            If True, the line will be closed.
        `circle`: list
            If set, the :attr:`points` will be set to build a circle. See
            :attr:`circle` for more information.
        `ellipse`: list
            If set, the :attr:`points` will be set to build an ellipse. See
            :attr:`ellipse` for more information.
        `rectangle`: list
            If set, the :attr:`points` will be set to build a rectangle. See
            :attr:`rectangle` for more information.
        `bezier`: list
            If set, the :attr:`points` will be set to build a bezier line. See
            :attr:`bezier` for more information.
        `bezier_precision`: int, defaults to 180
            Precision of the Bezier drawing.
        `force_custom_drawing_method`: bool, defaults to False
            Should the custom drawing method be used, instead of it depending on :attr:`width`
            being equal to 1.o or not.

    .. versionchanged:: 1.0.8
        `dash_offset` and `dash_length` have been added.

    .. versionchanged:: 1.4.1
        `width`, `cap`, `joint`, `cap_precision`, `joint_precision`, `close`,
        `ellipse`, `rectangle` have been added.

    .. versionchanged:: 1.4.1
        `bezier`, `bezier_precision` have been added.

    .. versionchanged:: 1.11.0
        `dashes` have been added

    .. versionchanged:: 2.3.0
        `force_custom_drawing_method` has been added

    '''
    cdef int _cap
    cdef int _cap_precision
    cdef int _joint_precision
    cdef int _bezier_precision
    cdef int _joint
    cdef list _points
    cdef list _dash_list
    cdef float _width
    cdef int _dash_offset, _dash_length
    cdef int _use_stencil
    cdef int _close
    cdef str _close_mode
    cdef int _force_custom_drawing_method
    cdef int _mode
    cdef Instruction _stencil_rect
    cdef Instruction _stencil_push
    cdef Instruction _stencil_use
    cdef Instruction _stencil_unuse
    cdef Instruction _stencil_pop
    cdef double _bxmin, _bxmax, _bymin, _bymax
    cdef tuple _mode_args
    cdef tuple _rounded_rectangle, _rectangle, _ellipse, _circle

    def __init__(self, **kwargs):
        super(Line, self).__init__(**kwargs)
        v = kwargs.get('points')
        self.points = v if v is not None else []
        self.dashes = kwargs.get('dashes', [])
        self.batch.set_mode('line_strip')
        self._dash_length = kwargs.get('dash_length') or 1
        self._dash_offset = kwargs.get('dash_offset') or 0
        self._width = kwargs.get('width') or 1.0
        self.joint = kwargs.get('joint') or 'round'
        self.cap = kwargs.get('cap') or 'round'
        self._cap_precision = kwargs.get('cap_precision') or 10
        self._joint_precision = kwargs.get('joint_precision') or 10
        self._bezier_precision = kwargs.get('bezier_precision') or 180
        self._close = int(bool(kwargs.get('close', 0)))
        self._close_mode = kwargs.get('close_mode', 'straight-line')
        self._force_custom_drawing_method = int(bool(kwargs.get('force_custom_drawing_method', 0)))
        self._stencil_rect = None
        self._stencil_push = None
        self._stencil_use = None
        self._stencil_unuse = None
        self._stencil_pop = None
        self._use_stencil = 0

        if 'ellipse' in kwargs:
            self.ellipse = kwargs['ellipse']
        if 'circle' in kwargs:
            self.circle = kwargs['circle']
        if 'rectangle' in kwargs:
            self.rectangle = kwargs['rectangle']
        if 'rounded_rectangle' in kwargs:
            self.rounded_rectangle = kwargs['rounded_rectangle']
        if 'bezier' in kwargs:
            self.bezier = kwargs['bezier']

    cdef void build(self):
        if self._mode == LINE_MODE_ELLIPSE:
            self.prebuild_ellipse()
        elif self._mode == LINE_MODE_CIRCLE:
            self.prebuild_circle()
        elif self._mode == LINE_MODE_RECTANGLE:
            self.prebuild_rectangle()
        elif self._mode == LINE_MODE_ROUNDED_RECTANGLE:
            self.prebuild_rounded_rectangle()
        elif self._mode == LINE_MODE_BEZIER:
            self.prebuild_bezier()
        if self._width == 1.0 and self._force_custom_drawing_method == 0:
            self.build_legacy()
        else:
            self.build_extended()

    cdef void ensure_stencil(self):
        if self._stencil_rect == None:
            self._stencil_rect = Rectangle()
            self._stencil_push = StencilPush()
            self._stencil_pop = StencilPop()
            self._stencil_use = StencilUse(op='lequal')
            self._stencil_unuse = StencilUnUse()

    cdef int apply(self) except -1:
        if self._width == 1. and self._force_custom_drawing_method == 0:
            VertexInstruction.apply(self)
            return 0

        cdef double alpha = getActiveContext()['color'][-1]
        self._use_stencil = alpha < 1
        if self._use_stencil:
            self.ensure_stencil()

            self._stencil_push.apply()
            VertexInstruction.apply(self)
            self._stencil_use.apply()
            self._stencil_rect.pos = self._bxmin, self._bymin
            self._stencil_rect.size = self._bxmax - self._bxmin, self._bymax - self._bymin
            self._stencil_rect.apply()
            self._stencil_unuse.apply()
            VertexInstruction.apply(self)
            self._stencil_pop.apply()
        else:
            VertexInstruction.apply(self)
        return 0

    cdef void build_legacy(self):
        cdef int i
        cdef long count = <int>int(len(self.points) / 2.)
        cdef list p = self.points
        cdef vertex_t *vertices = NULL
        cdef unsigned short *indices = NULL
        cdef float tex_x
        cdef char *buf = NULL
        cdef Texture texture = self.texture
        cdef int length = 0
        cdef int position = 0
        cdef int val

        if count < 2:
            self.batch.clear_data()
            return

        if self._close and self._close_mode == 'straight-line':
            p = p + [p[0], p[1]]
            count += 1

        self.batch.set_mode('line_strip')

        if self._dash_list:
            length = sum(self._dash_list)
            if texture is None or texture._width != length \
                or texture._height != 1:
                self.texture = texture = Texture.create(size=(length, 1))
                texture.wrap = 'repeat'

            # create a buffer to fill our texture
            buf = <char *>malloc(4 * length)
            memset(buf, 0, 4 * length)

            for idx, val in enumerate(self._dash_list):
                if idx % 2 == 0:
                    memset(buf + position, 255, val * 4)
                position += val * 4

            p_str = buf[:position]
            try:
                self.texture.blit_buffer(p_str, colorfmt='rgba', bufferfmt='ubyte')
            finally:
                free(buf)
        elif self._dash_offset != 0:
            length = self._dash_length + self._dash_offset
            if texture is None or texture._width != \
                (self._dash_length + self._dash_offset) or \
                texture._height != 1:

                self.texture = texture = Texture.create(
                        size=(self._dash_length + self._dash_offset, 1))
                texture.wrap = 'repeat'

            # create a buffer to fill our texture
            buf = <char *>malloc(4 * (self._dash_length + self._dash_offset))
            memset(buf, 255, self._dash_length * 4)
            memset(buf + self._dash_length * 4, 0, self._dash_offset * 4)
            p_str = buf[:(self._dash_length + self._dash_offset) * 4]

            try:
                self.texture.blit_buffer(p_str, colorfmt='rgba', bufferfmt='ubyte')
            finally:
                free(buf)

        elif texture is not None:
            self.texture = None

        vertices = <vertex_t *>malloc(count * sizeof(vertex_t))
        if vertices == NULL:
            raise MemoryError('vertices')

        indices = <unsigned short *>malloc(count * sizeof(unsigned short))
        if indices == NULL:
            free(vertices)
            raise MemoryError('indices')

        tex_x = 0
        for i in range(count):
            if (self._dash_offset != 0 or self._dash_list) and i > 0:
                tex_x += <float>(sqrt(
                        pow(p[i * 2]     - p[(i - 1) * 2], 2)  +
                        pow(p[i * 2 + 1] - p[(i - 1) * 2 + 1], 2)) / length)

                vertices[i].s0 = tex_x
                vertices[i].t0 = 0

            vertices[i].x = p[i * 2]
            vertices[i].y = p[i * 2 + 1]
            indices[i] = i

        self.batch.set_data(vertices, <int>count, indices, <int>count)

        free(vertices)
        free(indices)

    cdef void build_extended(self):
        cdef int i, j
        cdef long count = <int>int(len(self.points) / 2.)
        cdef list p = self.points
        cdef vertex_t *vertices = NULL
        cdef unsigned short *indices = NULL
        cdef float tex_x
        cdef int cap
        cdef char *buf = NULL
        self.texture = None

        self._bxmin = 999999999
        self._bymin = 999999999
        self._bxmax = -999999999
        self._bymax = -999999999

        if count < 2:
            self.batch.clear_data()
            return

        cap = self._cap
        if self._close and self._close_mode == 'straight-line' and count > 2:
            p = p + p[0:4]
            count += 2
            cap = LINE_CAP_NONE

        self.batch.set_mode('triangles')
        cdef unsigned long vertices_count = (count - 1) * 4
        cdef unsigned long indices_count = (count - 1) * 6
        cdef unsigned int iv = 0, ii = 0, siv = 0

        if self._joint == LINE_JOINT_BEVEL:
            indices_count += (count - 2) * 3
            vertices_count += (count - 2)
        elif self._joint == LINE_JOINT_ROUND:
            indices_count += (self._joint_precision * 3) * (count - 2)
            vertices_count += (self._joint_precision) * (count - 2)
        elif self._joint == LINE_JOINT_MITER:
            indices_count += (count - 2) * 6
            vertices_count += (count - 2) * 2

        if cap == LINE_CAP_SQUARE:
            indices_count += 12
            vertices_count += 4
        elif cap == LINE_CAP_ROUND:
            indices_count += (self._cap_precision * 3) * 2
            vertices_count += (self._cap_precision) * 2

        vertices = <vertex_t *>malloc(vertices_count * sizeof(vertex_t))
        if vertices == NULL:
            raise MemoryError('vertices')

        indices = <unsigned short *>malloc(indices_count * sizeof(unsigned short))
        if indices == NULL:
            free(vertices)
            raise MemoryError('indices')

        cdef double ax, ay, bx, _by, cx, cy, angle, a1, a2
        cdef double x1, y1, x2, y2, x3, y3, x4, y4
        cdef double sx1, sy1, sx4, sy4, sangle
        cdef double pcx, pcy, px1, py1, px2, py2, px3, py3, px4, py4, pangle = 0, pangle2
        cdef double w = self._width
        cdef double ix, iy
        cdef unsigned int piv, pii2, piv2, skip = 0
        cdef double jangle
        angle = sangle = 0
        piv = pcx = pcy = cx = cy = ii = iv = ix = iy = 0
        px1 = px2 = px3 = px4 = py1 = py2 = py3 = py4 = 0
        sx1 = sy1 = sx4 = sy4 = 0
        x1 = x2 = x3 = x4 = y1 = y2 = y3 = y4 = 0
        cdef double cos1 = 0, cos2 = 0, sin1 = 0, sin2 = 0
        for i in range(0, count - 1):
            ax = p[i * 2]
            ay = p[i * 2 + 1]
            bx = p[i * 2 + 2]
            _by = p[i * 2 + 3]

            if (ax, ay) == (bx, _by):
                skip += 1
                continue

            if i - skip > 0 and self._joint != LINE_JOINT_NONE:
                pcx = cx
                pcy = cy
                px1 = x1
                px2 = x2
                px3 = x3
                px4 = x4
                py1 = y1
                py2 = y2
                py3 = y3
                py4 = y4

            piv2 = piv
            piv = iv
            pangle2 = pangle
            pangle = angle

            # calculate the orientation of the segment, between pi and -pi
            cx = bx - ax
            cy = _by - ay
            angle = atan2(cy, cx)
            a1 = angle - PI2
            a2 = angle + PI2

            # calculate the position of the segment
            cos1 = cos(a1) * w
            sin1 = sin(a1) * w
            cos2 = cos(a2) * w
            sin2 = sin(a2) * w
            x1 = ax + cos1
            y1 = ay + sin1
            x4 = ax + cos2
            y4 = ay + sin2
            x2 = bx + cos1
            y2 = _by + sin1
            x3 = bx + cos2
            y3 = _by + sin2

            if i - skip == 0:
                sx1 = x1
                sy1 = y1
                sx4 = x4
                sy4 = y4
                sangle = angle

            indices[ii    ] = iv
            indices[ii + 1] = iv + 1
            indices[ii + 2] = iv + 2
            indices[ii + 3] = iv
            indices[ii + 4] = iv + 2
            indices[ii + 5] = iv + 3
            ii += 6

            vertices[iv].x = <float>x1
            vertices[iv].y = <float>y1
            vertices[iv].s0 = 0
            vertices[iv].t0 = 0
            iv += 1
            vertices[iv].x = <float>x2
            vertices[iv].y = <float>y2
            vertices[iv].s0 = 1
            vertices[iv].t0 = 0
            iv += 1
            vertices[iv].x = <float>x3
            vertices[iv].y = <float>y3
            vertices[iv].s0 = 1
            vertices[iv].t0 = 1
            iv += 1
            vertices[iv].x = <float>x4
            vertices[iv].y = <float>y4
            vertices[iv].s0 = 0
            vertices[iv].t0 = 1
            iv += 1

            # joint generation
            if i - skip == 0 or self._joint == LINE_JOINT_NONE:
                continue

            # calculate the angle of the previous and current segment
            jangle = atan2(
                cx * pcy - cy * pcx,
                cx * pcx + cy * pcy)

            # in case of the angle is NULL, avoid the generation
            if jangle == 0:
                if self._joint == LINE_JOINT_ROUND:
                    vertices_count -= self._joint_precision
                    indices_count -= self._joint_precision * 3
                elif self._joint == LINE_JOINT_BEVEL:
                    vertices_count -= 1
                    indices_count -= 3
                elif self._joint == LINE_JOINT_MITER:
                    vertices_count -= 2
                    indices_count -= 6
                continue

            if self._joint == LINE_JOINT_BEVEL:
                vertices[iv].x = <float>ax
                vertices[iv].y = <float>ay
                vertices[iv].s0 = 0
                vertices[iv].t0 = 0
                if jangle < 0:
                    indices[ii] = piv2 + 1
                    indices[ii + 1] = piv
                    indices[ii + 2] = iv
                else:
                    indices[ii] = piv2 + 2
                    indices[ii + 1] = piv + 3
                    indices[ii + 2] = iv
                ii += 3
                iv += 1

            elif self._joint == LINE_JOINT_MITER:
                vertices[iv].x = <float>ax
                vertices[iv].y = <float>ay
                vertices[iv].s0 = 0
                vertices[iv].t0 = 0
                if jangle < 0:
                    if line_intersection(px1, py1, px2, py2, x1, y1, x2, y2, &ix, &iy) == 0:
                        vertices_count -= 2
                        indices_count -= 6
                        continue
                    vertices[iv + 1].x = <float>ix
                    vertices[iv + 1].y = <float>iy
                    vertices[iv + 1].s0 = 0
                    vertices[iv + 1].t0 = 0
                    indices[ii] = iv
                    indices[ii + 1] = iv + 1
                    indices[ii + 2] = piv2 + 1
                    indices[ii + 3] = iv
                    indices[ii + 4] = piv
                    indices[ii + 5] = iv + 1
                    ii += 6
                    iv += 2
                else:
                    if line_intersection(px3, py3, px4, py4, x3, y3, x4, y4, &ix, &iy) == 0:
                        vertices_count -= 2
                        indices_count -= 6
                        continue
                    vertices[iv + 1].x = <float>ix
                    vertices[iv + 1].y = <float>iy
                    vertices[iv + 1].s0 = 0
                    vertices[iv + 1].t0 = 0
                    indices[ii] = iv
                    indices[ii + 1] = iv + 1
                    indices[ii + 2] = piv2 + 2
                    indices[ii + 3] = iv
                    indices[ii + 4] = piv + 3
                    indices[ii + 5] = iv + 1
                    ii += 6
                    iv += 2

            elif self._joint == LINE_JOINT_ROUND:

                # cap end
                if jangle < 0:
                    a1 = pangle2 - PI2
                    a2 = angle + PI2
                    a0 = a2
                    step = (abs(jangle)) / float(self._joint_precision)
                    pivstart = piv + 3
                    pivend = piv2 + 1
                else:
                    a1 = angle - PI2
                    a2 = pangle2 + PI2
                    a0 = a1
                    step = -(abs(jangle)) / float(self._joint_precision)
                    pivstart = piv
                    pivend = piv2 + 2
                siv = iv
                vertices[iv].x = <float>ax
                vertices[iv].y = <float>ay
                vertices[iv].s0 = 0
                vertices[iv].t0 = 0
                iv += 1
                for j in xrange(0, self._joint_precision - 1):
                    vertices[iv].x = <float>(ax - cos(a0 - step * j) * w)
                    vertices[iv].y = <float>(ay - sin(a0 - step * j) * w)
                    vertices[iv].s0 = 0
                    vertices[iv].t0 = 0
                    if j == 0:
                        indices[ii] = siv
                        indices[ii + 1] = <unsigned short>pivstart
                        indices[ii + 2] = iv
                    else:
                        indices[ii] = siv
                        indices[ii + 1] = iv - 1
                        indices[ii + 2] = iv
                    iv += 1
                    ii += 3
                indices[ii] = siv
                indices[ii + 1] = iv - 1
                indices[ii + 2] = <unsigned short>pivend
                ii += 3

        # caps
        if cap == LINE_CAP_SQUARE:
            vertices[iv].x = <float>(x2 + cos(angle) * w)
            vertices[iv].y = <float>(y2 + sin(angle) * w)
            vertices[iv].s0 = 0
            vertices[iv].t0 = 0
            vertices[iv + 1].x = <float>(x3 + cos(angle) * w)
            vertices[iv + 1].y = <float>(y3 + sin(angle) * w)
            vertices[iv + 1].s0 = 0
            vertices[iv + 1].t0 = 0
            indices[ii] = piv + 1
            indices[ii + 1] = piv + 2
            indices[ii + 2] = iv + 1
            indices[ii + 3] = piv + 1
            indices[ii + 4] = iv
            indices[ii + 5] = iv + 1
            ii += 6
            iv += 2
            vertices[iv].x = <float>(sx1 - cos(sangle) * w)
            vertices[iv].y = <float>(sy1 - sin(sangle) * w)
            vertices[iv].s0 = 0
            vertices[iv].t0 = 0
            vertices[iv + 1].x = <float>(sx4 - cos(sangle) * w)
            vertices[iv + 1].y = <float>(sy4 - sin(sangle) * w)
            vertices[iv + 1].s0 = 0
            vertices[iv + 1].t0 = 0
            indices[ii] = 0
            indices[ii + 1] = 3
            indices[ii + 2] = iv + 1
            indices[ii + 3] = 0
            indices[ii + 4] = iv
            indices[ii + 5] = iv + 1
            ii += 6
            iv += 2

        elif cap == LINE_CAP_ROUND:

            # cap start
            a1 = sangle - PI2
            a2 = sangle + PI2
            step = (a1 - a2) / float(self._cap_precision)
            siv = iv
            cx = p[0]
            cy = p[1]
            vertices[iv].x = <float>cx
            vertices[iv].y = <float>cy
            vertices[iv].s0 = 0
            vertices[iv].t0 = 0
            iv += 1
            for i in xrange(0, self._cap_precision - 1):
                vertices[iv].x = <float>(cx + cos(a1 + step * i) * w)
                vertices[iv].y = <float>(cy + sin(a1 + step * i) * w)
                vertices[iv].s0 = 1
                vertices[iv].t0 = 1
                if i == 0:
                    indices[ii] = siv
                    indices[ii + 1] = 0
                    indices[ii + 2] = iv
                else:
                    indices[ii] = siv
                    indices[ii + 1] = iv - 1
                    indices[ii + 2] = iv
                iv += 1
                ii += 3
            indices[ii] = siv
            indices[ii + 1] = iv - 1
            indices[ii + 2] = 3
            ii += 3

            # cap end
            a1 = angle - PI2
            a2 = angle + PI2
            step = (a2 - a1) / float(self._cap_precision)
            siv = iv
            cx = p[-2]
            cy = p[-1]
            vertices[iv].x = <float>cx
            vertices[iv].y = <float>cy
            vertices[iv].s0 = 0
            vertices[iv].t0 = 0
            iv += 1
            for i in xrange(0, self._cap_precision - 1):
                vertices[iv].x = <float>(cx + cos(a1 + step * i) * w)
                vertices[iv].y = <float>(cy + sin(a1 + step * i) * w)
                vertices[iv].s0 = 0
                vertices[iv].t0 = 0
                if i == 0:
                    indices[ii] = siv
                    indices[ii + 1] = piv + 1
                    indices[ii + 2] = iv
                else:
                    indices[ii] = siv
                    indices[ii + 1] = iv - 1
                    indices[ii + 2] = iv
                iv += 1
                ii += 3
            indices[ii] = siv
            indices[ii + 1] = iv - 1
            indices[ii + 2] = piv + 2
            ii += 3

        while ii < indices_count:
            # make all the remaining indices point to the last vertice
            indices[ii] = siv
            ii += 1

        # compute bbox
        cdef unsigned long iul
        for iul in xrange(vertices_count):
            if vertices[iul].x < self._bxmin:
                self._bxmin = vertices[iul].x
            if vertices[iul].x > self._bxmax:
                self._bxmax = vertices[iul].x
            if vertices[iul].y < self._bymin:
                self._bymin = vertices[iul].y
            if vertices[iul].y > self._bymax:
                self._bymax = vertices[iul].y

        self.batch.set_data(vertices, <int>vertices_count,
                           indices, <int>indices_count)

        free(vertices)
        free(indices)

    property points:
        '''Property for getting/settings points of the line

        .. warning::

            This will always reconstruct the whole graphics from the new points
            list. It can be very CPU expensive.
        '''
        def __get__(self):
            return self._points

        def __set__(self, points):
            if points and isinstance(points[0], (list, tuple)):
                self._points = list(itertools.chain(*points))
            else:
                self._points = list(points)

            self._mode = LINE_MODE_POINTS
            self.flag_data_update()

    property dash_length:
        '''Property for getting/setting the length of the dashes in the curve

        .. versionadded:: 1.0.8
        '''
        def __get__(self):
            return self._dash_length

        def __set__(self, value):
            if value < 0:
                raise GraphicException('Invalid dash_length value, must be >= 0')
            self._dash_length = value
            self.flag_data_update()

    property dash_offset:
        '''Property for getting/setting the offset between the dashes in the curve

        .. versionadded:: 1.0.8
        '''
        def __get__(self):
            return self._dash_offset

        def __set__(self, value):
            if value < 0:
                raise GraphicException('Invalid dash_offset value, must be >= 0')
            self._dash_offset = value
            self.flag_data_update()

    property dashes:
        '''Property for getting/setting ``dashes``.

        List of [ON length, offset, ON length, offset, ...]. E.g. ``[2,4,1,6,8,2]``
        would create a line with the first dash length 2 then an offset of 4 then
        a dash length of 1 then an offset of 6 and so on.

        .. versionadded:: 1.11.0
        '''
        def __get__(self):
            return self._dash_list

        def __set__(self, value):
            self._dash_list = list(value)
            self.flag_data_update()

    property width:
        '''Determine the width of the line, defaults to 1.0.

        .. versionadded:: 1.4.1
        '''
        def __get__(self):
            return self._width

        def __set__(self, value):
            if value <= 0:
                raise GraphicException('Invalid width value, must be > 0')
            self._width = value
            self.flag_data_update()

    property cap:
        '''Determine the cap of the line, defaults to 'round'. Can be one of
        'none', 'square' or 'round'

        .. versionadded:: 1.4.1
        '''
        def __get__(self):
            if self._cap == LINE_CAP_SQUARE:
                return 'square'
            elif self._cap == LINE_CAP_ROUND:
                return 'round'
            return 'none'

        def __set__(self, value):
            if value not in ('none', 'square', 'round'):
                raise GraphicException('Invalid cap, must be one of '
                        '"none", "square", "round"')
            if value == 'square':
                self._cap = LINE_CAP_SQUARE
            elif value == 'round':
                self._cap = LINE_CAP_ROUND
            else:
                self._cap = LINE_CAP_NONE
            self.flag_data_update()

    property joint:
        '''Determine the join of the line, defaults to 'round'. Can be one of
        'none', 'round', 'bevel', 'miter'.

        .. versionadded:: 1.4.1
        '''

        def __get__(self):
            if self._joint == LINE_JOINT_ROUND:
                return 'round'
            elif self._joint == LINE_JOINT_BEVEL:
                return 'bevel'
            elif self._joint == LINE_JOINT_MITER:
                return 'miter'
            return 'none'

        def __set__(self, value):
            if value not in ('none', 'miter', 'bevel', 'round'):
                raise GraphicException('Invalid joint, must be one of '
                    '"none", "miter", "bevel", "round"')
            if value == 'round':
                self._joint = LINE_JOINT_ROUND
            elif value == 'bevel':
                self._joint = LINE_JOINT_BEVEL
            elif value == 'miter':
                self._joint = LINE_JOINT_MITER
            else:
                self._joint = LINE_JOINT_NONE
            self.flag_data_update()

    property cap_precision:
        '''Number of iteration for drawing the "round" cap, defaults to 10.
        The cap_precision must be at least 1.

        .. versionadded:: 1.4.1
        '''

        def __get__(self):
            return self._cap_precision

        def __set__(self, value):
            if value < 1:
                raise GraphicException('Invalid cap_precision value, must be >= 1')
            self._cap_precision = int(value)
            self.flag_data_update()

    property joint_precision:
        '''Number of iteration for drawing the "round" joint, defaults to 10.
        The joint_precision must be at least 1.

        .. versionadded:: 1.4.1
        '''

        def __get__(self):
            return self._joint_precision

        def __set__(self, value):
            if value < 1:
                raise GraphicException('Invalid joint_precision value, must be >= 1')
            self._joint_precision = int(value)
            self.flag_data_update()

    property close:
        '''If True, the line will be closed by joining the two ends, according to :attr:`close_mode`.

        .. versionadded:: 1.4.1
        '''

        def __get__(self):
            return self._close

        def __set__(self, value):
            self._close = int(bool(value))
            self.flag_data_update()

    @property
    def close_mode(self):
        '''Defines how the ends of the line will be connected.
        Defaults to ``"straight-line"``.

        .. note::
            Support for the different closing modes depends on drawing shapes.

        Available modes:

        - ``"straight-line"`` (all drawing shapes): the ends will be closed by a straight line.
        - ``"center-connected"`` (:attr:`ellipse` specific): the ends will be closed by a line passing through the center of the ellipse.

        .. versionadded:: 2.2.0
        '''
        return self._close_mode

    @close_mode.setter
    def close_mode(self, value):
        if value not in ("straight-line", "center-connected"):
            raise GraphicException(f'{self.__class__.__name__} - Invalid close_mode, must be one of "straight-line" or "center-connected".')
        self._close_mode = value
        self.flag_data_update()

    property force_custom_drawing_method:
           '''If True, the line will be drawn using the custom drawing method, no matter what the width is.

           .. versionadded:: 2.3.0
           '''

           def __get__(self):
               return self._force_custom_drawing_method

           def __set__(self, value):
               self._force_custom_drawing_method = int(bool(value))
               self.flag_data_update()

    property ellipse:
        '''Use this property to build an ellipse, without calculating the
        :attr:`points`.

        The argument must be a tuple of (x, y, width, height, angle_start,
        angle_end, segments):

        * x and y represent the bottom left of the ellipse
        * width and height represent the size of the ellipse
        * (optional) angle_start and angle_end are in degree. The default
          value is 0 and 360.
        * (optional) segments is the precision of the ellipse. The default
          value is calculated from the range between angle. You can use this
          property to create polygons with 3 or more sides. Values smaller than
          3 will not be represented and the number of segments will be
          automatically calculated.

        Note that it's up to you to :attr:`close` or not.
        If you choose to close, use :attr:`close_mode` to define how the figure
        will be closed. Whether it will be by closed by a ``"straight-line"``
        or by ``"center-connected"``.

        For example, for building a simple ellipse, in python::

            # simple ellipse
            Line(ellipse=(0, 0, 150, 150))

            # only from 90 to 180 degrees
            Line(ellipse=(0, 0, 150, 150, 90, 180))

            # only from 90 to 180 degrees, with few segments
            Line(ellipse=(0, 0, 150, 150, 90, 180, 20))

        .. versionadded:: 1.4.1

        .. versionchanged:: 2.2.0
            Now you can get the ellipse generated through the property.

            The minimum number of segments allowed is 3. Smaller values will be
            ignored and the number of segments will be automatically calculated.
        '''

        def __get__(self):
            return self._ellipse

        def __set__(self, args):
            if args == None:
                raise GraphicException(
                        'Invalid ellipse value: {0!r}'.format(args))
            if len(args) not in (4, 6, 7):
                raise GraphicException('Invalid number of arguments: '
                        '{0} instead of 4, 6 or 7.'.format(len(args)))
            self._mode_args = tuple(args)
            self._mode = LINE_MODE_ELLIPSE
            self.flag_data_update()

    cdef void prebuild_ellipse(self):
        cdef double x, y, w, h, angle_start = 0, angle_end = 360
        cdef int angle_dir, segments = 0, extra_segments = 0
        cdef double angle_range
        cdef tuple args = self._mode_args
        cdef bint center_connected = self._close and self._close_mode == "center-connected"

        extra_segments = 3 if center_connected else 1

        if len(args) == 4:
            x, y, w, h = args
        elif len(args) == 6:
            x, y, w, h, angle_start, angle_end = args
        elif len(args) == 7:
            x, y, w, h, angle_start, angle_end, segments = args
        else:
            x = y = w = h = 0
            assert 0

        if 0 in (w, h):
            return

        if segments < 3:
            if segments != 0:
                Logger.warning(f'{self.__class__.__name__} - ellipse: A minimum of 3 segments is required. The default value will be used instead.')
            segments = int(abs(angle_end - angle_start) / 2) + extra_segments

        segments += extra_segments
        segments *= 2

        if angle_end > angle_start:
            angle_dir = 1
        else:
            angle_dir = -1

        # Resulting ellipse
        self._ellipse = (x, y, w, h, angle_start, angle_end, segments)
        # Reset other properties
        self._rounded_rectangle = self._rectangle = self._circle = None

        # rad = deg * (pi / 180), where pi/180 = 0.0174...
        angle_start = angle_start * 0.017453292519943295
        angle_end = angle_end * 0.017453292519943295
        angle_range = abs(angle_end - angle_start) / (segments - extra_segments * 2)


        cdef list points = [0, ] * segments
        cdef double angle
        cdef double rx = w * 0.5
        cdef double ry = h * 0.5
        cdef int inc_x = 0, inc_y = 1

        if center_connected and angle_start != angle_end:
            points[0] = points[segments - 2] = x + rx
            points[1] = points[segments - 1] = y + ry

            inc_x = 2
            inc_y = 3
            segments -= 4

        for i in xrange(0, segments, 2):
            angle = angle_start + (angle_dir * i * angle_range)
            points[i + inc_x] = (x + rx) + (rx * sin(angle))
            points[i + inc_y] = (y + ry) + (ry * cos(angle))

        self._points = points

    property circle:
        '''Use this property to build a circle, without calculating the
        :attr:`points`.

        The argument must be a tuple of (center_x, center_y, radius, angle_start,
        angle_end, segments):

        * center_x and center_y represent the center of the circle
        * radius represent the radius of the circle
        * (optional) angle_start and angle_end are in degree. The default
          value is 0 and 360.
        * (optional) segments is the precision of the ellipse. The default
          value is calculated from the range between angle.

        Note that it's up to you to :attr:`close` the circle or not.

        For example, for building a simple ellipse, in python::

            # simple circle
            Line(circle=(150, 150, 50))

            # only from 90 to 180 degrees
            Line(circle=(150, 150, 50, 90, 180))

            # only from 90 to 180 degrees, with few segments
            Line(circle=(150, 150, 50, 90, 180, 20))

        .. versionadded:: 1.4.1

        .. versionchanged:: 2.2.0
            Now you can get the circle generated through the property.

        '''

        def __get__(self):
            return self._circle

        def __set__(self, args):
            if args == None:
                raise GraphicException(
                        'Invalid circle value: {0!r}'.format(args))
            if len(args) not in (3, 5, 6):
                raise GraphicException('Invalid number of arguments: '
                        '{0} instead of 3, 5 or 6.'.format(len(args)))
            self._mode_args = tuple(args)
            self._mode = LINE_MODE_CIRCLE
            self.flag_data_update()

    cdef void prebuild_circle(self):
        cdef double x, y, r, angle_start = 0, angle_end = 360
        cdef int angle_dir, segments = 0
        cdef double angle_range
        cdef tuple args = self._mode_args

        if len(args) == 3:
            x, y, r = args
        elif len(args) == 5:
            x, y, r, angle_start, angle_end = args
        elif len(args) == 6:
            x, y, r, angle_start, angle_end, segments = args
            segments += 1
        else:
            x = y = r = 0
            assert 0

        if angle_end > angle_start:
            angle_dir = 1
        else:
            angle_dir = -1
        if segments == 0:
            segments = int(abs(angle_end - angle_start) / 2) + 3

        # Resulting circle
        self._circle = (x, y, r, angle_start, angle_end, segments)
        # Reset other properties
        self._rounded_rectangle = self._rectangle = self._ellipse = None

        segmentpoints = segments * 2

        # rad = deg * (pi / 180), where pi/180 = 0.0174...
        angle_start = angle_start * 0.017453292519943295
        angle_end = angle_end * 0.017453292519943295
        angle_range = abs(angle_end - angle_start) / (segmentpoints - 2)

        cdef list points = [0, ] * segmentpoints
        cdef double angle
        for i in xrange(0, segmentpoints, 2):
            angle = angle_start + (angle_dir * i * angle_range)
            points[i] = x + (r * sin(angle))
            points[i + 1] = y + (r * cos(angle))
        self._points = points

    property rectangle:
        '''Use this property to build a rectangle, without calculating the
        :attr:`points`.

        The argument must be a tuple of (x, y, width, height):

        * x and y represent the bottom-left position of the rectangle
        * width and height represent the size

        The line is automatically closed.

        Usage::

            Line(rectangle=(0, 0, 200, 200))

        .. versionadded:: 1.4.1

        .. versionchanged:: 2.2.0
            Now you can get the rectangle generated through the property.

        '''

        def __get__(self):
            return self._rectangle

        def __set__(self, args):
            if args == None:
                raise GraphicException(
                        'Invalid rectangle value: {0!r}'.format(args))
            if len(args) != 4:
                raise GraphicException('Invalid number of arguments: '
                        '{0} instead of 4.'.format(len(args)))
            self._mode_args = tuple(args)
            self._mode = LINE_MODE_RECTANGLE
            self.flag_data_update()

    cdef void prebuild_rectangle(self):
        cdef double x, y, width, height
        cdef int angle_dir, segments = 0
        cdef double angle_range
        cdef tuple args = self._mode_args

        if args == None:
            raise GraphicException(
                    'Invalid ellipse value: {0!r}'.format(args))

        if len(args) == 4:
            x, y, width, height = args
        else:
            x = y = width = height = 0
            assert 0

        # Resulting rectangle
        self._rectangle = (x, y, width, height)
        # Reset other properties
        self._rounded_rectangle = self._circle = self._ellipse = None

        self._points = [x, y, x + width, y, x + width, y + height, x, y + height]
        self._close = 1

    property rounded_rectangle:
        '''Use this property to build a rectangle, without calculating the
        :attr:`points`.

        The argument must be a tuple of one of the following forms:

        * (x, y, width, height, corner_radius)
        * (x, y, width, height, corner_radius, resolution)
        * (x, y, width, height, corner_radius1, corner_radius2, corner_radius3, corner_radius4)
        * (x, y, width, height, corner_radius1, corner_radius2, corner_radius3, corner_radius4, resolution)

        * `x` and `y` represent the bottom-left position of the rectangle.
        * `width` and `height` represent the size.
        * `corner_radius` specifies the radius used for the rounded corners clockwise: top-left, top-right, bottom-right, bottom-left.
        * `resolution` is the number of line segment that will be used to draw the circle arc at each corner (defaults to 45).

        The line is automatically closed.

        Usage::

            Line(rounded_rectangle=(0, 0, 200, 200, 10, 20, 30, 40, 100))

        .. versionadded:: 1.9.0

        .. versionchanged:: 2.2.0
            Default value of `resolution` changed from 30 to 45.

            Now you can get the rounded rectangle generated through the property.

            The order of `corner_radius` has been changed to match the RoundedRectangle radius property (clockwise).
            It was bottom-left, bottom-right, top-right, top-left in previous versions.
            Now both are clockwise: top-left, top-right, bottom-right, bottom-left.
            To keep the corner radius order without changing the order manually, you can use python's built-in method `reversed` or `[::-1]`,
            to reverse the order of the corner radius.

        '''

        def __get__(self):
            return self._rounded_rectangle

        def __set__(self, args):
            if args == None:
                raise GraphicException(
                    'Invalid rounded rectangle value: {0!r}'.format(args))
            if len(args) not in (5, 6, 8, 9):
                raise GraphicException('invalid number of arguments:'
                        '{0} not in (5, 6, 8, 9)'.format(len(args)))
            self._mode_args = tuple(args)
            self._mode = LINE_MODE_ROUNDED_RECTANGLE
            self.flag_data_update()

    cdef void prebuild_rounded_rectangle(self):
        cdef float a, max_a, px, py, x, y, w, h, c1, c2, c3, c4, step, min_dimension, half_min_dimension
        cdef resolution = 45
        cdef int l = <int>len(self._mode_args)

        self._points = []
        x, y, w, h = self._mode_args [:4]

        # zero size of the figure + avoid rendering issue with SmoothLine
        if w <= 0 or h <= 0 or isinstance(self, SmoothLine) and (w < 2 or h < 2):
            return

        if l == 5:
            c1 = c2 = c3 = c4 = self._mode_args[4]
        elif l == 6:
            c1 = c2 = c3 = c4 = self._mode_args[4]
            resolution = self._mode_args[5]
        elif l == 8:
            c1, c2, c3, c4 = self._mode_args[4:]
        else:  # l == 9, but else make the compiler happy about uninitialization
            c1, c2, c3, c4 = self._mode_args[4:8]
            resolution = self._mode_args[8]

        if resolution <= 4:
            resolution = 4

        # The minimum radius needs to be limited to 1px.
        # This avoid some known rendering issues with Line/SmoothLine.
        c1 = max(c1, 1.0)
        c2 = max(c2, 1.0)
        c3 = max(c3, 1.0)
        c4 = max(c4, 1.0)
        min_dimension = min(w, h)
        half_min_dimension = min_dimension / 2.0

        # If larger values are passed for each corner, we will have to make some adjustments.
        if c1 > half_min_dimension:
            c2 = min(c2, half_min_dimension)
            c4 = min(c4, half_min_dimension)
            c1 = min(c1, min_dimension - c2, min_dimension - c4)

        if c2 > half_min_dimension:
            c1 = min(c1, half_min_dimension)
            c3 = min(c3, half_min_dimension)
            c2 = min(c2, min_dimension - c1, min_dimension - c3)

        if c3 > half_min_dimension:
            c2 = min(c2, half_min_dimension)
            c4 = min(c4, half_min_dimension)
            c3 = min(c3, min_dimension - c2, min_dimension - c4)

        if c4 > half_min_dimension:
            c3 = min(c3, half_min_dimension)
            c1 = min(c1, half_min_dimension)
            c4 = min(c4, min_dimension - c3, min_dimension - c1)

        # Resulting rounded_rectangle
        self._rounded_rectangle = (x, y, w, h, c1, c2, c3, c4, resolution)
        # Reset other properties
        self._rectangle = self._ellipse = self._circle = None

        step = PI / resolution
        max_a = PI / 2.0 - step

        # top-left
        a = 0.0
        px = x + c1
        py = y + h - c1

        while a < max_a:
            a += step
            self._points.extend([
                px - cos(a) * c1,
                py + sin(a) * c1
            ])

        # top-right
        a = 0.0
        px = x + w - c2
        py = y + h - c2

        while a < max_a:
            a += step
            self._points.extend([
                px + sin(a) * c2,
                py + cos(a) * c2
            ])

        # bottom-right
        a = 0.0
        px = x + w - c3
        py = y + c3

        while a < max_a:
            a += step
            self._points.extend([
                px + cos(a) * c3,
                py - sin(a) * c3
            ])

        # bottom-left
        a = 0.0
        px = x + c4
        py = y + c4

        while a < max_a:
            a += step
            self._points.extend([
                px - sin(a) * c4,
                py - cos(a) * c4
            ])

        self._close = 1

    property bezier:
        '''Use this property to build a bezier line, without calculating the
        :attr:`points`. You can only set this property, not get it.

        The argument must be a tuple of 2n elements, n being the number of points.

        Usage::

            Line(bezier=(x1, y1, x2, y2, x3, y3)

        .. versionadded:: 1.4.2

        .. note:: Bezier lines calculations are inexpensive for a low number of
            points, but complexity is quadratic, so lines with a lot of points
            can be very expensive to build, use with care!
        '''

        def __set__(self, args):
            if args == None or len(args) % 2:
                raise GraphicException(
                        'Invalid bezier value: {0!r}'.format(args))
            self._mode_args = tuple(args)
            self._mode = LINE_MODE_BEZIER
            self.flag_data_update()

    cdef void prebuild_bezier(self):
        cdef double x, y, l
        cdef int segments = self._bezier_precision
        cdef list T = list(self._mode_args)[:]

        self._points = []
        for x in xrange(segments):
            l = x / (1.0 * segments)
            # http://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm
            # as the list is in the form of (x1, y1, x2, y2...) iteration is
            # done on each item and the current item (xn or yn) in the list is
            # replaced with a calculation of "xn + x(n+1) - xn" x(n+1) is
            # placed at n+2. Each iteration makes the list one item shorter
            for i in range(1, len(T)):
                for j in xrange(len(T) - 2*i):
                    T[j] = T[j] + (T[j+2] - T[j]) * l

            # we got the coordinates of the point in T[0] and T[1]
            self._points.append(T[0])
            self._points.append(T[1])

        # add one last point to join the curve to the end
        self._points.append(T[-2])
        self._points.append(T[-1])

    property bezier_precision:
        '''Number of iteration for drawing the bezier between 2 segments,
        defaults to 180. The bezier_precision must be at least 1.

        .. versionadded:: 1.4.2
        '''

        def __get__(self):
            return self._bezier_precision

        def __set__(self, value):
            if value < 1:
                raise GraphicException('Invalid bezier_precision value, must be >= 1')
            self._bezier_precision = int(value)
            self.flag_data_update()


cdef class SmoothLine(Line):
    '''Experimental line using over-draw methods to get better anti-aliasing
    results. It has few drawbacks:

    - drawing a line with alpha will probably not have the intended result if
      the line crosses itself.
    - :attr:`~Line.cap`, :attr:`~Line.joint` and :attr:`~Line.dash` properties
      are not supported.
    - it uses a custom texture with a premultiplied alpha.
    - lines under 1px in width are not supported: they will look the same.

    .. warning::

        This is an unfinished work, experimental, and subject to crashes.

    .. versionadded:: 1.9.0
    '''

    cdef float _owidth

    def __init__(self, **kwargs):
        Line.__init__(self, **kwargs)
        self._owidth = kwargs.get("overdraw_width") or <float>1.2
        self.batch.set_mode("triangles")
        self.texture = self.premultiplied_texture()

    def premultiplied_texture(self):
        texture = Cache.get('kv.graphics.texture', 'smoothline')
        if not texture:
            texture = Texture.create(size=(4, 1), colorfmt="rgba")
            texture.add_reload_observer(self._smooth_reload_observer)
            self._smooth_reload_observer(texture)
            Cache.append('kv.graphics.texture', 'smoothline', texture)
        return texture

    cpdef _smooth_reload_observer(self, texture):
        cdef bytes GRADIENT_DATA = (
            b"\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00")
        texture.blit_buffer(GRADIENT_DATA, colorfmt="rgba")

    cdef void build(self):
        if self._mode == LINE_MODE_ELLIPSE:
            self.prebuild_ellipse()
        elif self._mode == LINE_MODE_CIRCLE:
            self.prebuild_circle()
        elif self._mode == LINE_MODE_RECTANGLE:
            self.prebuild_rectangle()
        elif self._mode == LINE_MODE_ROUNDED_RECTANGLE:
            self.prebuild_rounded_rectangle()
        elif self._mode == LINE_MODE_BEZIER:
            self.prebuild_bezier()

        self.build_smooth()

    cdef int apply(self) except -1:
        VertexInstruction.apply(self)
        return 0

    # FIXME: Some artifacts can be observed, depending on the line width,
    # overdraw_width and radius. This occurs due to the way the vertices are
    # built. It is not noticeable when the alpha value of the color of the
    # active context is equal to 1. One solution would be to avoid overlapping
    # vertices.
    cdef void build_smooth(self):
        cdef:
            list p = self.points
            int must_close_line = self._close
            double width = max(0, (self._width - 1.))
            double owidth = width + self._owidth
            vertex_t *vertices = NULL
            unsigned short *indices = NULL
            unsigned short *tindices = NULL
            float min_distance_threshold = 0.1
            double min_angle_threshold = 0.017453292519943295  # 1 degree in radians, determined empirically.
            double ax, ay, bx = 0., by = 0., rx = 0., ry = 0., last_angle = 0., angle, av_angle, ad_angle, angle_diff
            double cos1, sin1, cos2, sin2, ocos1, ocos2, osin1, osin2
            long index, icount, iv, ii, max_vindex, count
            unsigned short i0, i1, i2, i3, i4, i5, i6, i7, vindex, vcount


        # Points that are very close (with a distance less than 0.1) will
        # be discarded. This increases the reliability of line rendering.
        p = self._remove_too_nearby_points(p, min_distance_threshold)

        # If it is just a line segment, there will be no support to close the line.
        if len(p) <= 4:
            must_close_line = False

        # A new point needs to meet a minimum distance threshold before being added.
        if must_close_line and not (abs(p[-2] - p[0]) < min_distance_threshold and abs(p[-1] - p[1]) < min_distance_threshold):
            p = p + p[:2]

        iv = vindex = 0
        count = <long>int(len(p) / 2.)
        if count < 2:
            self.batch.clear_data()
            return

        vcount = <unsigned short>(count * 4)
        icount = (count - 1) * 18

        vertices = <vertex_t *>malloc(vcount * sizeof(vertex_t))
        if vertices == NULL:
            raise MemoryError("vertices")

        indices = <unsigned short *>malloc(icount * sizeof(unsigned short))
        if indices == NULL:
            free(vertices)
            raise MemoryError("indices")

        if must_close_line and self._close_mode == 'straight-line':
            ax = p[-4]
            ay = p[-3]
            bx = p[0]
            by = p[1]
            rx = bx - ax
            ry = by - ay
            last_angle = atan2(ry, rx)

        max_index = len(p)
        for index in range(0, max_index, 2):
            ax = p[index]
            ay = p[index + 1]

            if index < max_index - 2:
                bx = p[index + 2]
                by = p[index + 3]
                rx = bx - ax
                ry = by - ay
                angle = atan2(ry, rx)

            elif must_close_line and index == max_index - 2:
                ax = p[0]
                ay = p[1]
                bx = p[2]
                by = p[3]
                rx = bx - ax
                ry = by - ay
                angle = atan2(ry, rx)

            else:
                angle = last_angle

            if index == 0 and (not must_close_line or self._close_mode != 'straight-line'):
                av_angle = angle
                ad_angle = pi
            else:
                av_angle = atan2(
                        sin(angle) + sin(last_angle),
                        cos(angle) + cos(last_angle))

                ad_angle = abs(pi - abs(angle - last_angle))

            a1 = av_angle - PI2
            a2 = av_angle + PI2


            if (index == 0 or index >= max_index - 2) and (not must_close_line or self._close_mode != 'straight-line'):
                l = width
                ol = owidth
            else:
                if index == 0:
                    ox = p[- 4]
                    oy = p[- 3]
                else:
                    ox = p[index - 2]
                    oy = p[index - 1]

                la1 = last_angle - PI2
                la2 = angle - PI2
                ra1 = last_angle + PI2
                ra2 = angle + PI2


                angle_diff = self._get_angle_diff(
                    ox + cos(ra1) * owidth,
                    oy + sin(ra1) * owidth,
                    ax + cos(ra1) * owidth,
                    ay + sin(ra1) * owidth,
                    ax + cos(ra2) * owidth,
                    ay + sin(ra2) * owidth,
                    bx + cos(ra2) * owidth,
                    by + sin(ra2) * owidth,
                )

                # If the angle difference is too small it is not safe to use
                # the calculated values of l or l. Otherwise, l and ol will
                # have extremely high values, causing the line to become
                # excessively thick at some intersections (in specific cases).
                if angle_diff < min_angle_threshold or ad_angle < min_angle_threshold:
                    l = width
                    ol = owidth
                else:
                    if line_intersection(
                        ox + cos(la1) * width,
                        oy + sin(la1) * width,
                        ax + cos(la1) * width,
                        ay + sin(la1) * width,
                        ax + cos(la2) * width,
                        ay + sin(la2) * width,
                        bx + cos(la2) * width,
                        by + sin(la2) * width,
                        &rx, &ry) == 0:
                        # print('ERROR LINE INTERSECTION 1')
                        pass

                    l = <float>sqrt((ax - rx) ** 2 + (ay - ry) ** 2)

                    if line_intersection(
                        ox + cos(ra1) * owidth,
                        oy + sin(ra1) * owidth,
                        ax + cos(ra1) * owidth,
                        ay + sin(ra1) * owidth,
                        ax + cos(ra2) * owidth,
                        ay + sin(ra2) * owidth,
                        bx + cos(ra2) * owidth,
                        by + sin(ra2) * owidth,
                        &rx, &ry) == 0:
                        # print('ERROR LINE INTERSECTION 2')
                        pass

                    ol = <float>sqrt((ax - rx) ** 2 + (ay - ry) ** 2)


            last_angle = angle

            cos1 = cos(a1) * l
            sin1 = sin(a1) * l
            cos2 = cos(a2) * l
            sin2 = sin(a2) * l

            ocos1 = cos(a1) * ol
            osin1 = sin(a1) * ol
            ocos2 = cos(a2) * ol
            osin2 = sin(a2) * ol

            x1 = ax + cos1
            y1 = ay + sin1
            x2 = ax + cos2
            y2 = ay + sin2

            ox1 = ax + ocos1
            oy1 = ay + osin1
            ox2 = ax + ocos2
            oy2 = ay + osin2

            vertices[iv].x = <float>x1
            vertices[iv].y = <float>y1
            vertices[iv].s0 = 0.5
            vertices[iv].t0 = 0.25
            iv += 1
            vertices[iv].x = <float>x2
            vertices[iv].y = <float>y2
            vertices[iv].s0 = 0.5
            vertices[iv].t0 = 0.75
            iv += 1
            vertices[iv].x = <float>ox1
            vertices[iv].y = <float>oy1
            vertices[iv].s0 = 1
            vertices[iv].t0 = 0
            iv += 1
            vertices[iv].x = <float>ox2
            vertices[iv].y = <float>oy2
            vertices[iv].s0 = 1
            vertices[iv].t0 = 1
            iv += 1

        tindices = indices
        for vindex in range(0, vcount - 4, 4):
            tindices[0] = vindex
            tindices[1] = vindex + 2
            tindices[2] = vindex + 6
            tindices[3] = vindex
            tindices[4] = vindex + 6
            tindices[5] = vindex + 4
            tindices[6] = vindex + 1
            tindices[7] = vindex
            tindices[8] = vindex + 4
            tindices[9] = vindex + 1
            tindices[10] = vindex + 4
            tindices[11] = vindex + 5
            tindices[12] = vindex + 3
            tindices[13] = vindex + 1
            tindices[14] = vindex + 5
            tindices[15] = vindex + 3
            tindices[16] = vindex + 5
            tindices[17] = vindex + 7
            tindices = tindices + 18

        # print('tindices', <long>tindices, <long>indices, (<long>tindices - <long>indices) / sizeof(unsigned short))

        self.batch.set_data(vertices, <int>vcount, indices, <int>icount)

        free(vertices)
        free(indices)
    
    cdef list _remove_too_nearby_points(self, list p, float min_distance_threshold):
        cdef int index = 0
        cdef double x1, y1, x2, y2

        while index < len(p) - 2:
            x1, y1 = p[index], p[index + 1]
            x2, y2 = p[index + 2], p[index + 3]
            if abs(x2 - x1) < min_distance_threshold and abs(y2 - y1) < min_distance_threshold:
                del p[index + 2: index + 4]
            else:
                index += 2
        return p

    cdef double _get_angle_diff(self, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4):
        cdef list vector_1, vector_2

        vector_1 = [x2 - x1, y2 - y1]
        vector_2 = [x4 - x3, y4 - y3]

        angle_1 = atan2(vector_1[1], vector_1[0])
        angle_2 = atan2(vector_2[1], vector_2[0])

        angle_diff = abs(angle_1 - angle_2)

        if angle_diff > pi:
            angle_diff = 2 * pi - angle_diff

        return angle_diff


    property overdraw_width:
        '''Determine the overdraw width of the line, defaults to 1.2.
        '''
        def __get__(self):
            return self._owidth

        def __set__(self, value):
            if value <= 0:
                raise GraphicException('Invalid width value, must be > 0')
            self._owidth = value
            self.flag_data_update()
