Renders a board with pieces and/or selected squares as an SVG image.

    :param board: A :class:`chess.BaseBoard` for a chessboard with pieces or
        ``None`` (the default) for a chessboard without pieces.
    :param squares: A :class:`chess.SquareSet` with selected squares.
    :param flipped: Pass ``True`` to flip the board.
    :param coordinates: Pass ``False`` to disable coordinates in the margin.
    :param lastmove: A :class:`chess.Move` to be highlighted.
    :param check: A square to be marked as check.
    :param arrows: A list of :class:`~chess.svg.Arrow` objects like
        ``[chess.svg.Arrow(chess.E2, chess.E4)]`` or a list of tuples like
        ``[(chess.E2, chess.E4)]``. An arrow from a square pointing to the same
        square is drawn as a circle, like ``[(chess.E2, chess.E2)]``.
    :param size: The size of the image in pixels (e.g., ``400`` for a 400 by
        400 board) or ``None`` (the default) for no size limit.
    :param style: A CSS stylesheet to include in the SVG image.

    >>> import chess
    >>> import chess.svg
    >>>
    >>> board = chess.Board("8/8/8/8/4N3/8/8/8 w - - 0 1")
    >>> squares = board.attacks(chess.E4)
    >>> chess.svg.board(board=board, squares=squares)  # doctest: +SKIP

    .. image:: ../docs/Ne4.svg

def board(board: Optional[chess.BaseBoard] = None, *,
          squares: Optional[chess.IntoSquareSet] = None,
          flipped: bool = False,
          coordinates: bool = True,
          lastmove: Optional[chess.Move] = None,
          check: Optional[chess.Square] = None,
          arrows: Iterable[Union[Arrow, Tuple[chess.Square, chess.Square]]] = (),
          size: Optional[int] = None,
          style: Optional[str] = None) -> str:
    """
    Renders a board with pieces and/or selected squares as an SVG image.

    :param board: A :class:`chess.BaseBoard` for a chessboard with pieces or
        ``None`` (the default) for a chessboard without pieces.
    :param squares: A :class:`chess.SquareSet` with selected squares.
    :param flipped: Pass ``True`` to flip the board.
    :param coordinates: Pass ``False`` to disable coordinates in the margin.
    :param lastmove: A :class:`chess.Move` to be highlighted.
    :param check: A square to be marked as check.
    :param arrows: A list of :class:`~chess.svg.Arrow` objects like
        ``[chess.svg.Arrow(chess.E2, chess.E4)]`` or a list of tuples like
        ``[(chess.E2, chess.E4)]``. An arrow from a square pointing to the same
        square is drawn as a circle, like ``[(chess.E2, chess.E2)]``.
    :param size: The size of the image in pixels (e.g., ``400`` for a 400 by
        400 board) or ``None`` (the default) for no size limit.
    :param style: A CSS stylesheet to include in the SVG image.

    >>> import chess
    >>> import chess.svg
    >>>
    >>> board = chess.Board("8/8/8/8/4N3/8/8/8 w - - 0 1")
    >>> squares = board.attacks(chess.E4)
    >>> chess.svg.board(board=board, squares=squares)  # doctest: +SKIP

    .. image:: ../docs/Ne4.svg
    """
    margin = MARGIN if coordinates else 0
    svg = _svg(8 * SQUARE_SIZE + 2 * margin, size)

    if style:
        ET.SubElement(svg, "style").text = style

    defs = ET.SubElement(svg, "defs")
    if board:
        for piece_color in chess.COLORS:
            for piece_type in chess.PIECE_TYPES:
                if board.pieces_mask(piece_type, piece_color):
                    defs.append(ET.fromstring(PIECES[chess.Piece(piece_type, piece_color).symbol()]))

    squares = chess.SquareSet(squares) if squares else chess.SquareSet()
    if squares:
        defs.append(ET.fromstring(XX))

    if check is not None:
        defs.append(ET.fromstring(CHECK_GRADIENT))

    for square, bb in enumerate(chess.BB_SQUARES):
        file_index = chess.square_file(square)
        rank_index = chess.square_rank(square)

        x = (file_index if not flipped else 7 - file_index) * SQUARE_SIZE + margin
        y = (7 - rank_index if not flipped else rank_index) * SQUARE_SIZE + margin

        cls = ["square", "light" if chess.BB_LIGHT_SQUARES & bb else "dark"]
        if lastmove and square in [lastmove.from_square, lastmove.to_square]:
            cls.append("lastmove")
        fill_color = DEFAULT_COLORS[" ".join(cls)]

        cls.append(chess.SQUARE_NAMES[square])

        ET.SubElement(svg, "rect", {
            "x": str(x),
            "y": str(y),
            "width": str(SQUARE_SIZE),
            "height": str(SQUARE_SIZE),
            "class": " ".join(cls),
            "stroke": "none",
            "fill": fill_color,
        })

        if square == check:
            ET.SubElement(svg, "rect", {
                "x": str(x),
                "y": str(y),
                "width": str(SQUARE_SIZE),
                "height": str(SQUARE_SIZE),
                "class": "check",
                "fill": "url(#check_gradient)",
            })

        # Render pieces.
        if board is not None:
            piece = board.piece_at(square)
            if piece:
                ET.SubElement(svg, "use", {
                    "xlink:href": "#{}-{}".format(chess.COLOR_NAMES[piece.color], chess.PIECE_NAMES[piece.piece_type]),
                    "transform": "translate({:d}, {:d})".format(x, y),
                })

        # Render selected squares.
        if squares is not None and square in squares:
            ET.SubElement(svg, "use", {
                "xlink:href": "#xx",
                "x": str(x),
                "y": str(y),
            })

    if coordinates:
        for file_index, file_name in enumerate(chess.FILE_NAMES):
            x = (file_index if not flipped else 7 - file_index) * SQUARE_SIZE + margin
            svg.append(_text(file_name, x, 0, SQUARE_SIZE, margin))
            svg.append(_text(file_name, x, margin + 8 * SQUARE_SIZE, SQUARE_SIZE, margin))
        for rank_index, rank_name in enumerate(chess.RANK_NAMES):
            y = (7 - rank_index if not flipped else rank_index) * SQUARE_SIZE + margin
            svg.append(_text(rank_name, 0, y, margin, SQUARE_SIZE))
            svg.append(_text(rank_name, margin + 8 * SQUARE_SIZE, y, margin, SQUARE_SIZE))

    for arrow in arrows:
        try:
            tail, head, color = arrow.tail, arrow.head, arrow.color  # type: ignore
        except AttributeError:
            tail, head = arrow  # type: ignore
            color = "#888"

        tail_file = chess.square_file(tail)
        tail_rank = chess.square_rank(tail)
        head_file = chess.square_file(head)
        head_rank = chess.square_rank(head)

        xtail = margin + (tail_file + 0.5 if not flipped else 7.5 - tail_file) * SQUARE_SIZE
        ytail = margin + (7.5 - tail_rank if not flipped else tail_rank + 0.5) * SQUARE_SIZE
        xhead = margin + (head_file + 0.5 if not flipped else 7.5 - head_file) * SQUARE_SIZE
        yhead = margin + (7.5 - head_rank if not flipped else head_rank + 0.5) * SQUARE_SIZE

        if (head_file, head_rank) == (tail_file, tail_rank):
            ET.SubElement(svg, "circle", {
                "cx": str(xhead),
                "cy": str(yhead),
                "r": str(SQUARE_SIZE * 0.9 / 2),
                "stroke-width": str(SQUARE_SIZE * 0.1),
                "stroke": color,
                "fill": "none",
                "opacity": "0.5",
            })
        else:
            marker_size = 0.75 * SQUARE_SIZE
            marker_margin = 0.1 * SQUARE_SIZE

            dx, dy = xhead - xtail, yhead - ytail
            hypot = math.hypot(dx, dy)

            shaft_x = xhead - dx * (marker_size + marker_margin) / hypot
            shaft_y = yhead - dy * (marker_size + marker_margin) / hypot

            xtip = xhead - dx * marker_margin / hypot
            ytip = yhead - dy * marker_margin / hypot

            ET.SubElement(svg, "line", {
                "x1": str(xtail),
                "y1": str(ytail),
                "x2": str(shaft_x),
                "y2": str(shaft_y),
                "stroke": color,
                "stroke-width": str(SQUARE_SIZE * 0.2),
                "opacity": "0.5",
                "stroke-linecap": "butt",
                "class": "arrow",
            })

            marker = [(xtip, ytip),
                      (shaft_x + dy * 0.5 * marker_size / hypot,
                       shaft_y - dx * 0.5 * marker_size / hypot),
                      (shaft_x - dy * 0.5 * marker_size / hypot,
                       shaft_y + dx * 0.5 * marker_size / hypot)]

            ET.SubElement(svg, "polygon", {
                "points": " ".join(str(x) + "," + str(y) for x, y in marker),
                "fill": color,
                "opacity": "0.5",
                "class": "arrow",
            })

    return SvgWrapper(ET.tostring(svg).decode("utf-8"))