<gh_stars>10-100
# -*- coding: utf-8 -*-
"""ASCII canvas"""

import sys
import copy
import math
from . import colors
from .style import Style


class AsciiCanvas(object):
    """
    ASCII canvas for drawing in console using ASCII chars
    """

    def __init__(self, cols, lines, style=None):
        """
        Initialize ASCII canvas
        """
        if cols < 1 or cols > 1000 or lines < 1 or lines > 1000:
            raise Exception('Canvas cols/lines must be in range [1..1000]')
        self.cols = cols
        self.lines = lines
        if style is None:
            style = Style(char=' ', fg_color=colors.default, bg_color=colors.default, font_style=colors.normal)
        else:
            style.char = self.__filter_char(style.char, ' ')
        self.style = style
        self.canvas = [[style] * cols for _ in range(lines)]

    def clear(self):
        """
        Fill canvas with default style
        """
        self.canvas = [[self.style] * self.cols for _ in range(self.lines)]

    def print_out(self):
        """
        Print out styled canvas to console
        """
        for line in self.canvas:
            for char_style in line:
                colors.print_style_char(char_style)
            sys.stdout.write('\n')

    def add_point(self, x, y, style=None):
        """
        Add point
        """
        style = self.__prepare_style(style, 'o')
        if self.check_coord_in_range(x, y):
            self.canvas[y][x] = style

    def add_line(self, x0, y0, x1, y1, style=None):
        """
        Add ASCII line (x0, y0 -> x1, y1) to the canvas, fill line with `style`
        """
        style = self.__prepare_style(style, 'o')
        if x0 > x1:
            # swap A and B
            x1, x0 = x0, x1
            y1, y0 = y0, y1
        # get delta x, y
        dx = x1 - x0
        dy = y1 - y0
        # if a length of line is zero just add point
        if dx == 0 and dy == 0:
            if self.check_coord_in_range(x0, y0):
                self.canvas[y0][x0] = style
            return
        # when dx >= dy use fill by x-axis, and use fill by y-axis otherwise
        if abs(dx) >= abs(dy):
            for x in range(x0, x1 + 1):
                y = y0 if dx == 0 else y0 + int(round((x - x0) * dy / float((dx))))
                if self.check_coord_in_range(x, y):
                    self.canvas[y][x] = style
        else:
            if y0 < y1:
                for y in range(y0, y1 + 1):
                    x = x0 if dy == 0 else x0 + int(round((y - y0) * dx / float((dy))))
                    if self.check_coord_in_range(x, y):
                        self.canvas[y][x] = style
            else:
                for y in range(y1, y0 + 1):
                    x = x0 if dy == 0 else x1 + int(round((y - y1) * dx / float((dy))))
                    if self.check_coord_in_range(x, y):
                        self.canvas[y][x] = style

    def add_text(self, x, y, text, style=None):
        """
        Add text to canvas at position (x, y)
        """
        style = self.__prepare_style(style, ' ')
        for i, c in enumerate(text):
            if self.check_coord_in_range(x + i, y):
                text_style = Style(c, style.fg_color, style.bg_color, style.font_style)
                self.canvas[y][x + i] = text_style

    def add_rect(self, x, y, w, h, fill_style=None, outline_style=None):
        """
        Add rectangle filled with `fill_style` and outline with `outline_style`
        """
        fill_style = self.__prepare_style(fill_style, self.style.char)
        outline_style = self.__prepare_style(outline_style, 'o')
        for px in range(x, x + w):
            for py in range(y, y + h):
                if self.check_coord_in_range(px, py):
                    if px == x or px == x + w - 1 or py == y or py == y + h - 1:
                        self.canvas[py][px] = outline_style
                    else:
                        self.canvas[py][px] = fill_style

    def add_nine_patch_rect(self, x, y, w, h, nine_patch_style=None):
        """
        Add nine-patch rectangle
        """
        default_3x3_chars = (
            '.', '-', '.',
            '|', ' ', '|',
            '`', '-', "'"
        )
        if nine_patch_style is None:
            nine_patch_style = [Style(char=c) for c in default_3x3_chars]
        # prepare 3x3 styles
        prepared_3x3_styles = []
        for index, style in enumerate(nine_patch_style[0:9]):
            prepared_3x3_styles.append(self.__prepare_style(style, None))
        for px in range(x, x + w):
            for py in range(y, y + h):
                if self.check_coord_in_range(px, py):
                    if px == x and py == y:
                        self.canvas[py][px] = prepared_3x3_styles[0]
                    elif px == x and y < py < y + h - 1:
                        self.canvas[py][px] = prepared_3x3_styles[3]
                    elif px == x and py == y + h - 1:
                        self.canvas[py][px] = prepared_3x3_styles[6]
                    elif x < px < x + w - 1 and py == y:
                        self.canvas[py][px] = prepared_3x3_styles[1]
                    elif x < px < x + w - 1 and py == y + h - 1:
                        self.canvas[py][px] = prepared_3x3_styles[7]
                    elif px == x + w - 1 and py == y:
                        self.canvas[py][px] = prepared_3x3_styles[2]
                    elif px == x + w - 1 and y < py < y + h - 1:
                        self.canvas[py][px] = prepared_3x3_styles[5]
                    elif px == x + w - 1 and py == y + h - 1:
                        self.canvas[py][px] = prepared_3x3_styles[8]
                    else:
                        self.canvas[py][px] = prepared_3x3_styles[4]

    def add_ellipse(self, x, y, w, h, fill_style=None, outline_style=None):
        """
        Add ellipse inside a rectangle filled with `fill_style` and outline with `outline_style`
        """
        if w < 1 or h < 1:
            return
        fill_style = self.__prepare_style(fill_style, self.style.char)
        outline_style = self.__prepare_style(outline_style, 'o')
        # Bresenham's algorithm to plot ellipse is used
        a = w
        b = h - 1
        eight_a_square = 8 * a * a
        eight_b_square = 8 * b * b
        x_change = 4 * b * b * (1.0 - a)
        y_change = 4 * a * a * ((b & 1) + 1)
        ellipse_error = x_change + y_change + (b & 1) * a * a
        x0 = x
        x1 = x0 + w - 1
        y0 = y + h / 2
        y1 = y0 - (b & 1)
        outline_points = []
        while x0 <= x1:
            # add fill
            if x0 > x and x0 < x + w - 1:
                self.add_line(int(x0), int(y0), int(x0), int(y1), fill_style)
                self.add_line(int(x1), int(y0), int(x1), int(y1), fill_style)
            outline_points.append((int(x1), int(y0)))
            outline_points.append((int(x0), int(y0)))
            outline_points.append((int(x0), int(y1)))
            outline_points.append((int(x1), int(y1)))
            two_ellipse_error = 2 * ellipse_error
            if two_ellipse_error <= y_change:
                y0 += 1
                y1 -= 1
                y_change += eight_a_square
                ellipse_error += y_change
            if two_ellipse_error >= x_change or 2 * ellipse_error > y_change:
                x0 += 1
                x1 -= 1
                x_change += eight_b_square
                ellipse_error += x_change
        while y0 - y1 <= b:
            self.add_point(int(x0 - 1), int(y0), outline_style)
            self.add_point(int(x1 + 1), int(y0), outline_style)
            self.add_point(int(x0 - 1), int(y1), outline_style)
            self.add_point(int(x1 + 1), int(y1), outline_style)
            y0 += 1
            y1 -= 1
        # draw outline over fill
        for outline_point in outline_points:
            px, py = outline_point
            self.add_point(px, py, outline_style)

    def check_coord_in_range(self, x, y):
        """
        Check that coordinate (x, y) is in range, to prevent out of range error
        """
        return 0 <= x < self.cols and 0 <= y < self.lines

    def get_canvas_as_str(self):
        """
        Return canvas as a string
        """
        return '\n'.join(''.join(char_style.char for char_style in line) for line in self.canvas)

    def __str__(self):
        """
        Return canvas as a string
        """
        return self.get_canvas_as_str()

    def __filter_char(self, char, default_char):
        """
        Filter input char
        """
        if not char:
            return default_char[0] if default_char else 'o'
        elif len(char) > 1:
            return char[0]
        return char

    def __prepare_style(self, style, default_char):
        """
        Prepare style
        """
        if style is None:
            default_char = self.__filter_char(default_char, self.style.char)
            style = Style(default_char, self.style.fg_color, self.style.bg_color, self.style.font_style)
            return style
        new_style = copy.deepcopy(style)
        if not style.char:
            new_style.char = self.__filter_char(default_char, self.style.char)
        else:
            new_style.char = self.__filter_char(style.char, ' ')
        if style.fg_color is None:
            new_style.fg_color = self.style.fg_color
        if style.bg_color is None:
            new_style.bg_color = self.style.bg_color
        return new_style

# -*- coding: utf-8 -*-
"""ASCII canvas"""

import sys
import copy
import math
from . import colors
from .style import Style


class AsciiCanvas(object):
    """
    ASCII canvas for drawing in console using ASCII chars
    """

    def __init__(self, cols, lines, style=None):
        """
        Initialize ASCII canvas
        """
        if cols < 1 or cols > 1000 or lines < 1 or lines > 1000:
            raise Exception('Canvas cols/lines must be in range [1..1000]')
        self.cols = cols
        self.lines = lines
        if style is None:
            style = Style(char=' ', fg_color=colors.default, bg_color=colors.default, font_style=colors.normal)
        else:
            style.char = self.__filter_char(style.char, ' ')
        self.style = style
        self.canvas = [[style] * cols for _ in range(lines)]

    def clear(self):
        """
        Fill canvas with default style
        """
        self.canvas = [[self.style] * self.cols for _ in range(self.lines)]

    def print_out(self):
        """
        Print out styled canvas to console
        """
        for line in self.canvas:
            for char_style in line:
                colors.print_style_char(char_style)
            sys.stdout.write('\n')

    def add_point(self, x, y, style=None):
        """
        Add point
        """
        style = self.__prepare_style(style, 'o')
        if self.check_coord_in_range(x, y):
            self.canvas[y][x] = style

    def add_line(self, x0, y0, x1, y1, style=None):
        """
        Add ASCII line (x0, y0 -> x1, y1) to the canvas, fill line with `style`
        """
        style = self.__prepare_style(style, 'o')
        if x0 > x1:
            # swap A and B
            x1, x0 = x0, x1
            y1, y0 = y0, y1
        # get delta x, y
        dx = x1 - x0
        dy = y1 - y0
        # if a length of line is zero just add point
        if dx == 0 and dy == 0:
            if self.check_coord_in_range(x0, y0):
                self.canvas[y0][x0] = style
            return
        # when dx >= dy use fill by x-axis, and use fill by y-axis otherwise
        if abs(dx) >= abs(dy):
            for x in range(x0, x1 + 1):
                y = y0 if dx == 0 else y0 + int(round((x - x0) * dy / float((dx))))
                if self.check_coord_in_range(x, y):
                    self.canvas[y][x] = style
        else:
            if y0 < y1:
                for y in range(y0, y1 + 1):
                    x = x0 if dy == 0 else x0 + int(round((y - y0) * dx / float((dy))))
                    if self.check_coord_in_range(x, y):
                        self.canvas[y][x] = style
            else:
                for y in range(y1, y0 + 1):
                    x = x0 if dy == 0 else x1 + int(round((y - y1) * dx / float((dy))))
                    if self.check_coord_in_range(x, y):
                        self.canvas[y][x] = style

    def add_text(self, x, y, text, style=None):
        """
        Add text to canvas at position (x, y)
        """
        style = self.__prepare_style(style, ' ')
        for i, c in enumerate(text):
            if self.check_coord_in_range(x + i, y):
                text_style = Style(c, style.fg_color, style.bg_color, style.font_style)
                self.canvas[y][x + i] = text_style

    def add_rect(self, x, y, w, h, fill_style=None, outline_style=None):
        """
        Add rectangle filled with `fill_style` and outline with `outline_style`
        """
        fill_style = self.__prepare_style(fill_style, self.style.char)
        outline_style = self.__prepare_style(outline_style, 'o')
        for px in range(x, x + w):
            for py in range(y, y + h):
                if self.check_coord_in_range(px, py):
                    if px == x or px == x + w - 1 or py == y or py == y + h - 1:
                        self.canvas[py][px] = outline_style
                    else:
                        self.canvas[py][px] = fill_style

    def add_nine_patch_rect(self, x, y, w, h, nine_patch_style=None):
        """
        Add nine-patch rectangle
        """
        default_3x3_chars = (
            '.', '-', '.',
            '|', ' ', '|',
            '`', '-', "'"
        )
        if nine_patch_style is None:
            nine_patch_style = [Style(char=c) for c in default_3x3_chars]
        # prepare 3x3 styles
        prepared_3x3_styles = []
        for index, style in enumerate(nine_patch_style[0:9]):
            prepared_3x3_styles.append(self.__prepare_style(style, None))
        for px in range(x, x + w):
            for py in range(y, y + h):
                if self.check_coord_in_range(px, py):
                    if px == x and py == y:
                        self.canvas[py][px] = prepared_3x3_styles[0]
                    elif px == x and y < py < y + h - 1:
                        self.canvas[py][px] = prepared_3x3_styles[3]
                    elif px == x and py == y + h - 1:
                        self.canvas[py][px] = prepared_3x3_styles[6]
                    elif x < px < x + w - 1 and py == y:
                        self.canvas[py][px] = prepared_3x3_styles[1]
                    elif x < px < x + w - 1 and py == y + h - 1:
                        self.canvas[py][px] = prepared_3x3_styles[7]
                    elif px == x + w - 1 and py == y:
                        self.canvas[py][px] = prepared_3x3_styles[2]
                    elif px == x + w - 1 and y < py < y + h - 1:
                        self.canvas[py][px] = prepared_3x3_styles[5]
                    elif px == x + w - 1 and py == y + h - 1:
                        self.canvas[py][px] = prepared_3x3_styles[8]
                    else:
                        self.canvas[py][px] = prepared_3x3_styles[4]

    def add_ellipse(self, x, y, w, h, fill_style=None, outline_style=None):
        """
        Add ellipse inside a rectangle filled with `fill_style` and outline with `outline_style`
        """
        if w < 1 or h < 1:
            return
        fill_style = self.__prepare_style(fill_style, self.style.char)
        outline_style = self.__prepare_style(outline_style, 'o')
        # Bresenham's algorithm to plot ellipse is used
        a = w
        b = h - 1
        eight_a_square = 8 * a * a
        eight_b_square = 8 * b * b
        x_change = 4 * b * b * (1.0 - a)
        y_change = 4 * a * a * ((b & 1) + 1)
        ellipse_error = x_change + y_change + (b & 1) * a * a
        x0 = x
        x1 = x0 + w - 1
        y0 = y + h / 2
        y1 = y0 - (b & 1)
        outline_points = []
        while x0 <= x1:
            # add fill
            if x0 > x and x0 < x + w - 1:
                self.add_line(int(x0), int(y0), int(x0), int(y1), fill_style)
                self.add_line(int(x1), int(y0), int(x1), int(y1), fill_style)
            outline_points.append((int(x1), int(y0)))
            outline_points.append((int(x0), int(y0)))
            outline_points.append((int(x0), int(y1)))
            outline_points.append((int(x1), int(y1)))
            two_ellipse_error = 2 * ellipse_error
            if two_ellipse_error <= y_change:
                y0 += 1
                y1 -= 1
                y_change += eight_a_square
                ellipse_error += y_change
            if two_ellipse_error >= x_change or 2 * ellipse_error > y_change:
                x0 += 1
                x1 -= 1
                x_change += eight_b_square
                ellipse_error += x_change
        while y0 - y1 <= b:
            self.add_point(int(x0 - 1), int(y0), outline_style)
            self.add_point(int(x1 + 1), int(y0), outline_style)
            self.add_point(int(x0 - 1), int(y1), outline_style)
            self.add_point(int(x1 + 1), int(y1), outline_style)
            y0 += 1
            y1 -= 1
        # draw outline over fill
        for outline_point in outline_points:
            px, py = outline_point
            self.add_point(px, py, outline_style)

    def check_coord_in_range(self, x, y):
        """
        Check that coordinate (x, y) is in range, to prevent out of range error
        """
        return 0 <= x < self.cols and 0 <= y < self.lines

    def get_canvas_as_str(self):
        """
        Return canvas as a string
        """
        return '\n'.join(''.join(char_style.char for char_style in line) for line in self.canvas)

    def __str__(self):
        """
        Return canvas as a string
        """
        return self.get_canvas_as_str()

    def __filter_char(self, char, default_char):
        """
        Filter input char
        """
        if not char:
            return default_char[0] if default_char else 'o'
        elif len(char) > 1:
            return char[0]
        return char

    def __prepare_style(self, style, default_char):
        """
        Prepare style
        """
        if style is None:
            default_char = self.__filter_char(default_char, self.style.char)
            style = Style(default_char, self.style.fg_color, self.style.bg_color, self.style.font_style)
            return style
        new_style = copy.deepcopy(style)
        if not style.char:
            new_style.char = self.__filter_char(default_char, self.style.char)
        else:
            new_style.char = self.__filter_char(style.char, ' ')
        if style.fg_color is None:
            new_style.fg_color = self.style.fg_color
        if style.bg_color is None:
            new_style.bg_color = self.style.bg_color
        return new_style
# -*- coding: utf-8 -*- ASCII canvas ASCII canvas for drawing in console using ASCII chars Initialize ASCII canvas Fill canvas with default style Print out styled canvas to console Add point Add ASCII line (x0, y0 -> x1, y1) to the canvas, fill line with `style` # swap A and B # get delta x, y # if a length of line is zero just add point # when dx >= dy use fill by x-axis, and use fill by y-axis otherwise Add text to canvas at position (x, y) Add rectangle filled with `fill_style` and outline with `outline_style` Add nine-patch rectangle # prepare 3x3 styles Add ellipse inside a rectangle filled with `fill_style` and outline with `outline_style` # Bresenham's algorithm to plot ellipse is used # add fill # draw outline over fill Check that coordinate (x, y) is in range, to prevent out of range error Return canvas as a string Return canvas as a string Filter input char Prepare style