Files
stepanalyser/.venv/lib/python3.12/site-packages/ezdxf/addons/tablepainter.py
Christian Anetzberger a197de9456 initial
2026-01-22 20:23:51 +01:00

928 lines
32 KiB
Python

# Copyright (c) 2010-2022, Manfred Moitzi
# License: MIT License
from __future__ import annotations
from typing import (
Any,
Callable,
Iterable,
Iterator,
Optional,
Sequence,
TYPE_CHECKING,
TypeVar,
)
from copy import deepcopy
from ezdxf.lldxf import const
from ezdxf.enums import (
MTextEntityAlignment,
MAP_MTEXT_ALIGN_TO_FLAGS,
)
from ezdxf.addons import MTextSurrogate
from ezdxf.math import UVec, Vec2
if TYPE_CHECKING:
from ezdxf.layouts import BlockLayout
from ezdxf.eztypes import GenericLayoutType
DEFAULT_TABLE_BG_LAYER = "TABLEBACKGROUND"
DEFAULT_TABLE_FG_LAYER = "TABLECONTENT"
DEFAULT_TABLE_GRID_LAYER = "TABLEGRID"
DEFAULT_TEXT_STYLE = "STANDARD"
DEFAULT_CELL_HEIGHT = 1.0
DEFAULT_CELL_WIDTH = 2.5
DEFAULT_CELL_CHAR_HEIGHT = 0.7
DEFAULT_CELL_LINE_SPACING = 1.5
DEFAULT_CELL_X_SCALE = 1.0
DEFAULT_CELL_Y_SCALE = 1.0
DEFAULT_CELL_TEXTCOLOR = const.BYLAYER
DEFAULT_CELL_BG_COLOR = None
DEFAULT_CELL_X_MARGIN = 0.1
DEFAULT_CELL_Y_MARGIN = 0.1
DEFAULT_BORDER_COLOR = 5
DEFAULT_BORDER_LINETYPE = "BYLAYER"
DEFAULT_BORDER_STATUS = True
DEFAULT_BORDER_PRIORITY = 50
T = TypeVar("T", bound="Cell")
class TablePainter:
"""The TablePainter class renders tables build from DXF primitives.
The TablePainter instance contains all the data cells.
Args:
insert: insert location as or :class:`~ezdxf.math.UVec`
nrows: row count
ncols: column count
cell_width: default cell width in drawing units
cell_height: default cell height in drawing units
default_grid: draw a grid of solid lines if ``True``, otherwise
draw only explicit defined borders, the default grid has a
priority of 50.
"""
def __init__(
self,
insert: UVec,
nrows: int,
ncols: int,
cell_width=DEFAULT_CELL_WIDTH,
cell_height=DEFAULT_CELL_HEIGHT,
default_grid=True,
):
self.insert = Vec2(insert)
self.nrows: int = nrows
self.ncols: int = ncols
self.row_heights: list[float] = [cell_height] * nrows
self.col_widths: list[float] = [cell_width] * ncols
self.bg_layer_name: str = DEFAULT_TABLE_BG_LAYER
self.fg_layer_name: str = DEFAULT_TABLE_FG_LAYER
self.grid_layer_name: str = DEFAULT_TABLE_GRID_LAYER
self.styles: dict[str, CellStyle] = {"default": CellStyle()}
if not default_grid:
default_style = self.get_cell_style("default")
default_style.set_border_status(False, False, False, False)
self._cells: dict[tuple[int, int], Cell] = {} # data cells
self.frames: list[Frame] = [] # border frame objects
self.empty_cell = Cell(self) # represents all empty cells
def set_col_width(self, index: int, value: float):
"""Set column width in drawing units of the given column index.
Args:
index: zero based column index
value: new column width in drawing units
"""
self.col_widths[index] = float(value)
def set_row_height(self, index: int, value: float):
"""Set row height in drawing units of the given row index.
Args:
index: zero based row index
value: new row height in drawing units
"""
self.row_heights[index] = float(value)
def text_cell(
self,
row: int,
col: int,
text: str,
span: tuple[int, int] = (1, 1),
style="default",
) -> TextCell:
"""Factory method to create a new text cell at location (row, col),
with `text` as content, the `text` can be a line breaks ``'\\n'``.
The final cell can spread over several cells defined by the argument
`span`.
"""
cell = TextCell(self, text, style=style, span=span)
return self.set_cell(row, col, cell)
def block_cell(
self,
row: int,
col: int,
blockdef: BlockLayout,
span: tuple[int, int] = (1, 1),
attribs=None,
style="default",
) -> BlockCell:
"""Factory method to Create a new block cell at position (row, col).
Content is a block reference inserted by an INSERT entity,
attributes will be added if the block definition contains ATTDEF.
Assignments are defined by attribs-key to attdef-tag association.
Example: attribs = {'num': 1} if an ATTDEF with tag=='num' in
the block definition exists, an attrib with text=str(1) will be
created and added to the insert entity.
The cell spans over 'span' cells and has the cell style with the
name 'style'.
"""
if attribs is None:
attribs = {}
cell = BlockCell(
self, blockdef, style=style, attribs=attribs, span=span
)
return self.set_cell(row, col, cell)
@property
def table_width(self) -> float:
"""Returns the total table width."""
return sum(self.col_widths)
@property
def table_height(self) -> float:
"""Returns the total table height."""
return sum(self.row_heights)
def set_cell(self, row: int, col: int, cell: T) -> T:
"""Insert a cell at position (row, col)."""
row, col = self.validate_index(row, col)
self._cells[row, col] = cell
return cell
def get_cell(self, row: int, col: int) -> Cell:
"""Get cell at location (row, col)."""
row, col = self.validate_index(row, col)
try:
return self._cells[row, col]
except KeyError:
return self.empty_cell # empty cell with default style
def validate_index(self, row: int, col: int) -> tuple[int, int]:
row = int(row)
col = int(col)
if row < 0 or row >= self.nrows or col < 0 or col >= self.ncols:
raise IndexError("cell index out of range")
return row, col
def frame(
self,
row: int,
col: int,
width: int = 1,
height: int = 1,
style="default",
) -> Frame:
"""Creates a frame around the give cell area, starting at (row, col) and
covering `width` columns and `height` rows. The `style` argument is the
name of a :class:`CellStyle`.
"""
frame = Frame(self, pos=(row, col), span=(height, width), style=style)
self.frames.append(frame)
return frame
def new_cell_style(self, name: str, **kwargs) -> CellStyle:
"""Factory method to create a new :class:`CellStyle` object, overwrites
an already existing cell style.
Args:
name: style name as string
kwargs: see attributes of class :class:`CellStyle`
"""
assert (
isinstance(name, str) and name != ""
), "name has to be a non-empty string"
style: CellStyle = deepcopy(self.get_cell_style("default"))
style.update(kwargs)
self.styles[name] = style
return style
@staticmethod
def new_border_style(
color: int = const.BYLAYER,
status=True,
priority: int = 100,
linetype: str = "BYLAYER",
lineweight: int = const.LINEWEIGHT_BYLAYER,
) -> BorderStyle:
"""Factory method to create a new border style.
Args:
status: ``True`` for visible, ``False`` for invisible
color: :ref:`ACI`
linetype: linetype name, default is "BYLAYER"
lineweight: lineweight as int, default is by layer
priority: drawing priority, higher priorities cover lower priorities
"""
border_style = BorderStyle()
border_style.color = color
border_style.linetype = linetype
border_style.lineweight = lineweight
border_style.status = status
border_style.priority = priority
return border_style
def get_cell_style(self, name: str) -> CellStyle:
"""Get cell style by name."""
return self.styles[name]
def iter_visible_cells(
self, visibility_map: VisibilityMap
) -> Iterator[tuple[int, int, Cell]]:
"""Iterate over all visible cells"""
return (
(row, col, self.get_cell(row, col)) for row, col in visibility_map
)
def render(self, layout: GenericLayoutType, insert: Optional[UVec] = None):
"""Render table to layout."""
insert_backup = self.insert
if insert is not None:
self.insert = Vec2(insert)
visibility_map = VisibilityMap(self)
grid = Grid(self)
for row, col, cell in self.iter_visible_cells(visibility_map):
grid.render_cell_background(layout, row, col, cell)
grid.render_cell_content(layout, row, col, cell)
grid.render_lines(layout, visibility_map)
self.insert = insert_backup
class VisibilityMap:
"""Stores the visibility of the table cells."""
def __init__(self, table: TablePainter):
"""Create the visibility map for table."""
self.table: TablePainter = table
self._hidden_cells: set[tuple[int, int]] = set()
self._create_visibility_map()
def _create_visibility_map(self):
"""Set visibility for all existing cells."""
for row, col in iter(self):
cell = self.table.get_cell(row, col)
self._set_span_visibility(row, col, cell.span)
def _set_span_visibility(self, row: int, col: int, span: tuple[int, int]):
"""Set the visibility of the given cell.
The cell itself is visible, all other cells in the span-range
(tuple: width, height) are invisible, they are covered by the
main cell (row, col).
"""
if span != (1, 1):
nrows, ncols = span
for rowx in range(nrows):
for colx in range(ncols):
# switch all cells in span range to invisible
self.hide(row + rowx, col + colx)
# switch content cell visible
self.show(row, col)
def show(self, row: int, col: int):
"""Show cell (row, col)."""
try:
self._hidden_cells.remove((row, col))
except KeyError:
pass
def hide(self, row: int, col: int) -> None:
"""Hide cell (row, col)."""
self._hidden_cells.add((row, col))
def iter_all_cells(self) -> Iterator[tuple[int, int]]:
"""Iterate over all cell indices, yields (row, col) tuples."""
for row in range(self.table.nrows):
for col in range(self.table.ncols):
yield row, col
def is_visible_cell(self, row: int, col: int) -> bool:
"""True if cell (row, col) is visible, else False."""
return (row, col) not in self._hidden_cells
def __iter__(self) -> Iterator[tuple[int, int]]:
"""Iterate over all visible cells."""
return (
(row, col)
for (row, col) in self.iter_all_cells()
if self.is_visible_cell(row, col)
)
class CellStyle:
"""Cell style object.
.. important::
Always instantiate new styles by the factory method:
:meth:`TablePainter.new_cell_style`
"""
def __init__(self, data: Optional[dict[str, Any]] = None):
# text style is ignored by block cells
self.text_style = "STANDARD"
# text height in drawing units, ignored by block cells
self.char_height = DEFAULT_CELL_CHAR_HEIGHT
# line spacing in percent = char_height * line_spacing, ignored by block cells
self.line_spacing = DEFAULT_CELL_LINE_SPACING
# text stretching factor (width factor) or block reference x-scaling factor
self.scale_x = DEFAULT_CELL_X_SCALE
# block reference y-axis scaling factor, ignored by text cells
self.scale_y = DEFAULT_CELL_Y_SCALE
# dxf color index, ignored by block cells
self.text_color = DEFAULT_CELL_TEXTCOLOR
# text or block rotation in degrees
self.rotation = 0.0
# Letters are stacked top-to-bottom, but not rotated
self.stacked = False
# text and block alignment, see ezdxf.enums.MTextEntityAlignment
self.align = MTextEntityAlignment.TOP_CENTER
# left and right cell margin in drawing units
self.margin_x = DEFAULT_CELL_X_MARGIN
# top and bottom cell margin in drawing units
self.margin_y = DEFAULT_CELL_Y_MARGIN
# background color, dxf color index, ignored by block cells
self.bg_color = DEFAULT_CELL_BG_COLOR
# left border style
self.left = BorderStyle()
# top border style
self.top = BorderStyle()
# right border style
self.right = BorderStyle()
# bottom border style
self.bottom = BorderStyle()
if data:
self.update(data)
def __getitem__(self, k: str) -> Any:
return self.__dict__[k]
def __setitem__(self, k: str, v: Any):
if k in self.__dict__:
self.__dict__.__setitem__(k, v)
else:
raise KeyError(f"invalid attribute name: {k}")
def update(self, data: dict[str, Any]):
for k, v in data.items():
self.__setitem__(k, v)
assert isinstance(
self.align, MTextEntityAlignment
), "enum ezdxf.enums.MTextEntityAlignment for text alignments required"
def set_border_status(self, left=True, right=True, top=True, bottom=True):
"""Set status of all cell borders at once."""
self.left.status = left
self.right.status = right
self.top.status = top
self.bottom.status = bottom
def set_border_style(
self, style: BorderStyle, left=True, right=True, top=True, bottom=True
):
"""Set border styles of all cell borders at once."""
for border, status in (
("left", left),
("right", right),
("top", top),
("bottom", bottom),
):
if status:
self[border] = style
@staticmethod
def get_default_border_style() -> BorderStyle:
return BorderStyle()
def get_text_align_flags(self) -> tuple[int, int]:
return MAP_MTEXT_ALIGN_TO_FLAGS[self.align]
class BorderStyle:
"""Border style class.
.. important::
Always instantiate new border styles by the factory method:
:meth:`TablePainter.new_border_style`
"""
def __init__(
self,
status: bool = DEFAULT_BORDER_STATUS,
color: int = DEFAULT_BORDER_COLOR,
linetype: str = DEFAULT_BORDER_LINETYPE,
lineweight=const.LINEWEIGHT_BYLAYER,
priority: int = DEFAULT_BORDER_PRIORITY,
):
# border status, True for visible, False for hidden
self.status = status
# ACI
self.color = color
# linetype name, BYLAYER if None
self.linetype = linetype
# lineweight
self.lineweight = lineweight
# drawing priority, higher values cover lower values
self.priority = priority
class Grid:
"""Grid contains the graphical representation of the table."""
def __init__(self, table: TablePainter):
self.table: TablePainter = table
# contains the x-axis coords of the grid lines between the data columns.
self.col_pos: list[float] = self._calc_col_pos()
# contains the y-axis coords of the grid lines between the data rows.
self.row_pos: list[float] = self._calc_row_pos()
# _x_borders contains the horizontal border elements, list of border styles
# get index with _border_index(row, col), which means the border element
# above row, col, and row-indices are [0 .. nrows+1], nrows+1 for the
# grid line below the last row; list contains only the border style with
# the highest priority.
self._x_borders: list[BorderStyle] = [] # created in _init_borders
# _y_borders: same as _x_borders but for the vertical borders,
# col-indices are [0 .. ncols+1], ncols+1 for the last grid line right
# of the last column
self._y_borders: list[BorderStyle] = [] # created in _init_borders
# border style to delete borders inside of merged cells
self.no_border = BorderStyle(
status=False, priority=999, linetype="BYLAYER", color=0
)
def _init_borders(self, x_border: BorderStyle, y_border: BorderStyle):
"""Init the _hborders with <hborder> and _vborders with <vborder>."""
# <border_count> has more elements than necessary, but it unifies the
# index calculation for _vborders and _hborders.
# exact values are:
# x_border_count = ncols * (nrows+1), hindex = ncols * <row> + <col>
# y_border_count = nrows * (ncols+1), vindex = (ncols+1) * <row> + <col>
border_count: int = (self.table.nrows + 1) * (self.table.ncols + 1)
self._x_borders = [x_border] * border_count
self._y_borders = [y_border] * border_count
def _border_index(self, row: int, col: int) -> int:
"""Calculate linear index for border arrays _x_borders and _y_borders."""
return row * (self.table.ncols + 1) + col
def set_x_border(self, row: int, col: int, border_style: BorderStyle):
"""Set <border_style> for the horizontal border element above
<row>, <col>.
"""
return self._set_border_style(self._x_borders, row, col, border_style)
def set_y_border(self, row: int, col: int, border_style: BorderStyle):
"""Set <border_style> for the vertical border element left of
<row>, <col>.
"""
return self._set_border_style(self._y_borders, row, col, border_style)
def _set_border_style(
self,
borders: list[BorderStyle],
row: int,
col: int,
border_style: BorderStyle,
):
"""Set <border_style> for <row>, <col> in <borders>."""
border_index = self._border_index(row, col)
actual_borderstyle = borders[border_index]
if border_style.priority >= actual_borderstyle.priority:
borders[border_index] = border_style
def get_x_border(self, row: int, col: int) -> BorderStyle:
"""Get the horizontal border element above <row>, <col>.
Last grid line (below <nrows>) is the element above of <nrows+1>.
"""
return self._get_border(self._x_borders, row, col)
def get_y_border(self, row: int, col: int) -> BorderStyle:
"""Get the vertical border element left of <row>, <col>.
Last grid line (right of <ncols>) is the element left of <ncols+1>.
"""
return self._get_border(self._y_borders, row, col)
def _get_border(
self, borders: list[BorderStyle], row: int, col: int
) -> BorderStyle:
"""Get border element at <row>, <col> from <borders>."""
return borders[self._border_index(row, col)]
def _calc_col_pos(self) -> list[float]:
"""Calculate the x-axis coords of the grid lines between the columns."""
col_pos: list[float] = []
start_x: float = self.table.insert.x
sum_fields(start_x, self.table.col_widths, col_pos.append)
return col_pos
def _calc_row_pos(self) -> list[float]:
"""Calculate the y-axis coords of the grid lines between the rows."""
row_pos: list[float] = []
start_y: float = self.table.insert.y
sum_fields(start_y, self.table.row_heights, row_pos.append, -1.0)
return row_pos
def cell_coords(
self, row: int, col: int, span: tuple[int, int]
) -> tuple[float, float, float, float]:
"""Get the coordinates of the cell <row>,<col> as absolute drawing units.
:return: a tuple (left, right, top, bottom)
"""
top = self.row_pos[row]
bottom = self.row_pos[row + span[0]]
left = self.col_pos[col]
right = self.col_pos[col + span[1]]
return left, right, top, bottom
def render_cell_background(
self, layout: GenericLayoutType, row: int, col: int, cell: Cell
):
"""Render the cell background for (row, col) as SOLID entity."""
style = cell.style
if style.bg_color is None:
return
# get cell coords in absolute drawing units
left, right, top, bottom = self.cell_coords(row, col, cell.span)
layout.add_solid(
points=((left, top), (left, bottom), (right, top), (right, bottom)),
dxfattribs={
"color": style.bg_color,
"layer": self.table.bg_layer_name,
},
)
def render_cell_content(
self, layout: GenericLayoutType, row: int, col: int, cell: Cell
):
"""Render the cell content for <row>,<col> into layout object."""
# get cell coords in absolute drawing units
coords = self.cell_coords(row, col, cell.span)
cell.render(layout, coords, self.table.fg_layer_name)
def render_lines(self, layout: GenericLayoutType, vm: VisibilityMap):
"""Render all grid lines into layout object."""
# Init borders with default_style top- and left border.
default_style = self.table.get_cell_style("default")
x_border = default_style.top
y_border = default_style.left
self._init_borders(x_border, y_border)
self._set_frames(self.table.frames)
self._set_borders(self.table.iter_visible_cells(vm))
self._render_borders(layout, self.table)
def _set_borders(self, visible_cells: Iterable[tuple[int, int, Cell]]):
"""Set borders of the visible cells."""
for row, col, cell in visible_cells:
bottom_row = row + cell.span[0]
right_col = col + cell.span[1]
self._set_rect_borders(row, bottom_row, col, right_col, cell.style)
self._set_inner_borders(
row, bottom_row, col, right_col, self.no_border
)
def _set_inner_borders(
self,
top_row: int,
bottom_row: int,
left_col: int,
right_col: int,
border_style: BorderStyle,
):
"""Set `border_style` to the inner borders of the rectangle (top_row,
bottom_row, ...)
"""
if bottom_row - top_row > 1:
for col in range(left_col, right_col):
for row in range(top_row + 1, bottom_row):
self.set_x_border(row, col, border_style)
if right_col - left_col > 1:
for row in range(top_row, bottom_row):
for col in range(left_col + 1, right_col):
self.set_y_border(row, col, border_style)
def _set_rect_borders(
self,
top_row: int,
bottom_row: int,
left_col: int,
right_col: int,
style: CellStyle,
):
"""Set border `style` to the rectangle (top_row, bottom_row, ...)
The values describing the grid lines between the cells, see doc-strings
for methods set_x_border() and set_y_border() and see comments for
self._x_borders and self._y_borders.
"""
for col in range(left_col, right_col):
self.set_x_border(top_row, col, style.top)
self.set_x_border(bottom_row, col, style.bottom)
for row in range(top_row, bottom_row):
self.set_y_border(row, left_col, style.left)
self.set_y_border(row, right_col, style.right)
def _set_frames(self, frames: Iterable[Frame]):
"""Set borders for all defined frames."""
for frame in frames:
top_row = frame.pos[0]
left_col = frame.pos[1]
bottom_row = top_row + frame.span[0]
right_col = left_col + frame.span[1]
self._set_rect_borders(
top_row, bottom_row, left_col, right_col, frame.style
)
def _render_borders(self, layout: GenericLayoutType, table: TablePainter):
"""Render the grid lines as LINE entities into layout object."""
def render_line(start: Vec2, end: Vec2, style: BorderStyle):
"""Render the LINE entity into layout object."""
if style.status:
layout.add_line(
start=start,
end=end,
dxfattribs={
"layer": layer,
"color": style.color,
"linetype": style.linetype,
"lineweight": style.lineweight,
},
)
def render_x_borders():
"""Draw the horizontal grid lines."""
for row in range(table.nrows + 1):
y = self.row_pos[row]
for col in range(table.ncols):
left = self.col_pos[col]
right = self.col_pos[col + 1]
style = self.get_x_border(row, col)
render_line(Vec2(left, y), Vec2(right, y), style)
def render_y_borders():
"""Draw the vertical grid lines."""
for col in range(table.ncols + 1):
x = self.col_pos[col]
for row in range(table.nrows):
top = self.row_pos[row]
bottom = self.row_pos[row + 1]
style = self.get_y_border(row, col)
render_line(Vec2(x, top), Vec2(x, bottom), style)
layer = table.grid_layer_name
render_x_borders()
render_y_borders()
class Frame:
"""Represent a rectangle cell area enclosed by borderlines.
Args:
table: the assigned data table
pos: tuple (row, col), border goes left and top of pos
span: count of cells that Frame covers, border goes right and below of this cells
style: style name as string
"""
def __init__(
self,
table: TablePainter,
pos: tuple[int, int] = (0, 0),
span: tuple[int, int] = (1, 1),
style="default",
):
self.table = table
self.pos = pos
self.span = span
self.stylename = style
@property
def style(self) -> CellStyle:
return self.table.get_cell_style(self.stylename)
class Cell:
"""Base class for table cells.
Args:
table: assigned data table
style: style name as string
span: tuple(spanrows, spancols), count of cells that cell covers
A cell doesn't know its own position in the data table, because a cell can
be used multiple times in the same or in different tables, therefore the
cell itself can not determine if the cell-range reaches beyond the table
borders.
"""
def __init__(
self,
table: TablePainter,
style="default",
span: tuple[int, int] = (1, 1),
):
self.table = table
self.stylename = style
# span values has to be >= 1
self.span = span
@property
def span(self) -> tuple[int, int]:
"""Get/set table span parameters."""
return self._span
@span.setter
def span(self, value: tuple[int, int]):
"""Ensures that span values are >= 1 in each direction."""
self._span = (max(1, value[0]), max(1, value[1]))
@property
def style(self) -> CellStyle:
"""Returns the associated :class:`CellStyle`."""
return self.table.get_cell_style(self.stylename)
def render(
self, layout: GenericLayoutType, coords: Sequence[float], layer: str
):
"""Renders the cell content into the given `layout`."""
pass
def get_workspace_coords(self, coords: Sequence[float]) -> Sequence[float]:
"""Reduces the cell-coords about the margin_x and the margin_y values."""
margin_x = self.style.margin_x
margin_y = self.style.margin_y
return (
coords[0] + margin_x, # left
coords[1] - margin_x, # right
coords[2] - margin_y, # top
coords[3] + margin_y, # bottom
)
CustomCell = Cell
class TextCell(Cell):
"""Implements a cell type containing a multi-line text. Uses the
:class:`~ezdxf.addons.MTextSurrogate` add-on to render the multi-line
text, therefore the content of these cells is compatible to DXF R12.
Args:
table: assigned data table
text: multi line text, lines separated by the new line character ``"\\n"``
style: cell style name as string
span: tuple(rows, cols) area of cells to cover
"""
def __init__(
self,
table: TablePainter,
text: str,
style="default",
span: tuple[int, int] = (1, 1),
):
super(TextCell, self).__init__(table, style, span)
self.text = text
def render(
self, layout: GenericLayoutType, coords: Sequence[float], layer: str
):
"""Text cell.
Args:
layout: target layout
coords: tuple of border-coordinates: left, right, top, bottom
layer: target layer name as string
"""
if not len(self.text):
return
left, right, top, bottom = self.get_workspace_coords(coords)
style = self.style
h_align, v_align = style.get_text_align_flags()
rotated = self.style.rotation
text = self.text
if style.stacked:
rotated = 0.0
text = "\n".join((char for char in self.text.replace("\n", " ")))
xpos = (left, float(left + right) / 2.0, right)[h_align]
ypos = (bottom, float(bottom + top) / 2.0, top)[v_align - 1]
mtext = MTextSurrogate(
text,
(xpos, ypos),
line_spacing=self.style.line_spacing,
style=self.style.text_style,
char_height=self.style.char_height,
rotation=rotated,
width_factor=self.style.scale_x,
align=style.align,
color=self.style.text_color,
layer=layer,
)
mtext.render(layout)
class BlockCell(Cell):
"""Implements a cell type containing a block reference.
Args:
table: table object
blockdef: :class:`ezdxf.layouts.BlockLayout` instance
attribs: BLOCK attributes as (tag, value) dictionary
style: cell style name as string
span: tuple(rows, cols) area of cells to cover
"""
def __init__(
self,
table: TablePainter,
blockdef: BlockLayout,
style="default",
attribs=None,
span: tuple[int, int] = (1, 1),
):
if attribs is None:
attribs = {}
super(BlockCell, self).__init__(table, style, span)
self.block_name = blockdef.name # dxf block name!
self.attribs = attribs
def render(
self, layout: GenericLayoutType, coords: Sequence[float], layer: str
):
"""Create the cell content as INSERT-entity with trailing ATTRIB-Entities.
Args:
layout: target layout
coords: tuple of border-coordinates : left, right, top, bottom
layer: target layer name as string
"""
left, right, top, bottom = self.get_workspace_coords(coords)
style = self.style
h_align, v_align = style.get_text_align_flags()
xpos = (left, float(left + right) / 2.0, right)[h_align]
ypos = (bottom, float(bottom + top) / 2.0, top)[v_align - 1]
layout.add_auto_blockref(
name=self.block_name,
insert=(xpos, ypos),
values=self.attribs,
dxfattribs={
"xscale": style.scale_x,
"yscale": style.scale_y,
"rotation": style.rotation,
"layer": layer,
},
)
def sum_fields(
start_value: float,
fields: list[float],
append: Callable[[float], None],
sign: float = 1.0,
):
"""Adds step-by-step the fields-values, starting with <start_value>,
and appends the resulting values to another object with the
append-method.
"""
position = start_value
append(position)
for element in fields:
position += element * sign
append(position)