table.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852
  1. # Purpose: table, consisting of basic R12 entities
  2. # Created: 18.03.2010, 2018 adapted for ezdxf
  3. # Copyright (c) 2010-2018, Manfred Moitzi
  4. # License: MIT License
  5. """
  6. Table object like a HTML-Table, as composite pf basic DXF R12 entities.
  7. Cells can contain MText or BLOCK references, or you can create your own
  8. cell type by extending the CustomCell() class.
  9. Cells can span over columns and rows.
  10. Text cells can contain text with an arbitrary rotation angle, or letters can be
  11. stacked from top to bottom.
  12. BlockCells contains block references (INSERT) created from a block
  13. definition (BLOCK), if the block definition contains attribute definitions
  14. (ATTDEF), attribs will be added to the block reference (ATTRIB).
  15. """
  16. from copy import deepcopy
  17. from abc import abstractmethod
  18. from ezdxf.lldxf import const
  19. from .mtext import MText
  20. DEFAULT_TABLE_BGLAYER = 'TABLEBACKGROUND'
  21. DEFAULT_TABLE_FGLAYER = 'TABLECONTENT'
  22. DEFAULT_TABLE_GRIDLAYER = 'TABLEGRID'
  23. DEFAULT_TABLE_HEIGHT = 1.0
  24. DEFAULT_TABLE_WIDTH = 2.5
  25. DEFAULT_TEXTSTYLE = 'STANDARD'
  26. DEFAULT_CELL_TEXT_HEIGHT = 0.7
  27. DEFAULT_CELL_LINESPACING = 1.5
  28. DEFAULT_CELL_XSCALE = 1.0
  29. DEFAULT_CELL_YSCALE = 1.0
  30. DEFAULT_CELL_TEXTCOLOR = const.BYLAYER
  31. DEFAULT_CELL_BG_COLOR = None
  32. DEFAULT_CELL_HMARGIN = 0.1
  33. DEFAULT_CELL_VMARGIN = 0.1
  34. DEFAULT_BORDER_COLOR = 5
  35. DEFAULT_BORDER_LINETYPE = "BYLAYER"
  36. DEFAULT_BORDER_STATUS = True
  37. DEFAULT_BORDER_PRIORITY = 50
  38. VISIBLE = 1
  39. HIDDEN = 0
  40. class Table(object):
  41. """A HTML-table like object.
  42. The table object contains the table data cells.
  43. """
  44. name = 'TABLE'
  45. def __init__(self, insert, nrows, ncols, default_grid=True):
  46. """
  47. Args:
  48. insert: insert point as tuple (x, y[, z])
  49. nrows: row count
  50. ncols: column count
  51. default_grid: draw a solid line grid if True, else draw only explicit defined borders, default grid has a
  52. priority of 50.
  53. """
  54. self.insert = insert
  55. self.nrows = nrows
  56. self.ncols = ncols
  57. self.row_heights = [DEFAULT_TABLE_HEIGHT] * nrows
  58. self.col_widths = [DEFAULT_TABLE_WIDTH] * ncols
  59. self.bglayer = DEFAULT_TABLE_BGLAYER
  60. self.fglayer = DEFAULT_TABLE_FGLAYER
  61. self.gridlayer = DEFAULT_TABLE_GRIDLAYER
  62. self.styles = {'default': Style.get_default_cell_style()}
  63. if not default_grid:
  64. default_style = self.get_cell_style('default')
  65. default_style.set_border_status(False, False, False, False)
  66. self._cells = {} # data cells
  67. self.frames = [] # border frame objects
  68. # data contains the resulting dxf entities
  69. self.data = None
  70. self.empty_cell = Cell(self) # represents all empty cells
  71. def set_col_width(self, column, value):
  72. """
  73. Set column width to value (in drawing units).
  74. Args:
  75. column: zero based column index
  76. value: new column width in drawing units
  77. """
  78. self.col_widths[column] = float(value)
  79. def set_row_height(self, row, value):
  80. """
  81. Set row height to value (in drawing units).
  82. Args:
  83. row: zero based row index
  84. value: new row height in drawing units
  85. """
  86. self.row_heights[row] = float(value)
  87. def text_cell(self, row, col, text, span=(1, 1), style='default'):
  88. """
  89. Create a new text cell at position (row, col), with 'text' as
  90. content, text can be a multi-line text, use ``'\\n'`` as line
  91. separator.
  92. The cell spans over **span** cells and has the cell style with the
  93. name **style**.
  94. """
  95. cell = TextCell(self, text, style=style, span=span)
  96. return self.set_cell(row, col, cell)
  97. def block_cell(self, row, col, blockdef, span=(1, 1), attribs=None, style='default'):
  98. """
  99. Create a new block cell at position (row, col).
  100. Content is a block reference inserted by an INSERT entity,
  101. attributes will be added if the block definition contains ATTDEF. Assignments
  102. are defined by attribs-key to attdef-tag association.
  103. Example: attribs = {'num': 1} if an ATTDEF with tag=='num' in
  104. the block definition exists, an attrib with text=str(1) will be
  105. created and added to the insert entity.
  106. The cell spans over 'span' cells and has the cell style with the
  107. name 'style'.
  108. """
  109. if attribs is None:
  110. attribs = {}
  111. cell = BlockCell(self, blockdef, style=style, attribs=attribs, span=span)
  112. return self.set_cell(row, col, cell)
  113. def set_cell(self, row, col, cell):
  114. """
  115. Insert a cell at position (row, col).
  116. """
  117. row, col = self.validate_index(row, col)
  118. self._cells[row, col] = cell
  119. return cell
  120. def get_cell(self, row, col):
  121. """
  122. Get cell at position (row, col).
  123. """
  124. row, col = self.validate_index(row, col)
  125. try:
  126. return self._cells[row, col]
  127. except KeyError:
  128. return self.empty_cell # empty cell with default style
  129. def validate_index(self, row, col):
  130. row = int(row)
  131. col = int(col)
  132. if row < 0 or row >= self.nrows or \
  133. col < 0 or col >= self.ncols:
  134. raise IndexError('cell index out of range')
  135. return row, col
  136. def frame(self, row, col, width=1, height=1, style='default'):
  137. """
  138. Create a Frame object which frames the cell area starting at(row, col) covering 'width' columns and 'height' rows.
  139. """
  140. frame = Frame(self, pos=(row, col), span=(height, width),
  141. style=style)
  142. self.frames.append(frame)
  143. return frame
  144. def new_cell_style(self, name, **kwargs):
  145. """
  146. Create a new Style object 'name'.
  147. Args:
  148. kwargs: see Style.get_default_cell_style()
  149. """
  150. style = deepcopy(self.get_cell_style('default'))
  151. style.update(kwargs)
  152. if 'align' in kwargs:
  153. align = kwargs.get('align')
  154. halign, valign = const.TEXT_ALIGN_FLAGS.get(align)
  155. style['halign'] = halign
  156. style['valign'] = valign
  157. else:
  158. halign = kwargs.get('halign')
  159. valign = kwargs.get('valign')
  160. style['align'] = const.TEXT_ALIGNMENT_BY_FLAGS.get(halign, valign)
  161. self.styles[name] = style
  162. return style
  163. def new_border_style(self, color=const.BYLAYER, status=True, priority=100, linetype="BYLAYER"):
  164. """
  165. Create a new border style.
  166. Args:
  167. status: True for visible, else False
  168. color: dxf color index
  169. linetype: linetype name, BYLAYER if None
  170. priority: drawing priority - higher values covers lower values
  171. """
  172. border_style = Style.get_default_border_style()
  173. border_style['color'] = color
  174. border_style['linetype'] = linetype
  175. border_style['status'] = status
  176. border_style['priority'] = priority
  177. return border_style
  178. def get_cell_style(self, name):
  179. """
  180. Get cell style by name.
  181. """
  182. return self.styles[name]
  183. def iter_visible_cells(self, visibility_map):
  184. """
  185. Iterate over all visible cells.
  186. Returns: a generator which yields all visible cells as tuples: (row , col, cell)
  187. """
  188. return ((row, col, self.get_cell(row, col)) for row, col in visibility_map)
  189. def render(self, layout, insert=None):
  190. """
  191. Render table to layout object.
  192. """
  193. _insert = self.insert
  194. if insert is not None:
  195. self.insert = insert
  196. visibility_map = VisibilityMap(self)
  197. grid = Grid(self)
  198. grid.render_lines(layout, visibility_map)
  199. for row, col, cell in self.iter_visible_cells(visibility_map):
  200. grid.render_cell_background(layout, row, col, cell)
  201. grid.render_cell_content(layout, row, col, cell)
  202. self.insert = _insert
  203. class VisibilityMap(object):
  204. """
  205. Stores the visibility of the table cells.
  206. """
  207. def __init__(self, table):
  208. """
  209. Create the visibility map for table.
  210. """
  211. self.table = table
  212. self._hidden_cells = {}
  213. self._create_visibility_map()
  214. def _create_visibility_map(self):
  215. """
  216. Set visibility for all existing cells.
  217. """
  218. for row, col in iter(self):
  219. cell = self.table.get_cell(row, col)
  220. self._set_span_visibility(row, col, cell.span)
  221. def _set_span_visibility(self, row, col, span):
  222. """
  223. Set the visibility of the given cell.
  224. The cell itself is visible, all other cells in the span-range
  225. (tuple: width, height) are invisible, they are covered by the
  226. main cell (row, col).
  227. """
  228. if span != (1, 1):
  229. nrows, ncols = span
  230. for rowx in range(nrows):
  231. for colx in range(ncols):
  232. # switch all cells in span range to invisible
  233. self.hide(row+rowx, col+colx)
  234. # switch content cell visible
  235. self.show(row, col)
  236. def show(self, row, col):
  237. """
  238. Show cell (row, col).
  239. """
  240. try:
  241. del self._hidden_cells[(row, col)]
  242. except KeyError:
  243. pass
  244. def hide(self, row, col):
  245. """
  246. Hide cell (row, col).
  247. """
  248. self._hidden_cells[(row, col)] = HIDDEN
  249. def iter_all_cells(self):
  250. """
  251. Iterate over all cell indices, yields (row, col) tuples.
  252. """
  253. for row in range(self.table.nrows):
  254. for col in range(self.table.ncols):
  255. yield row, col
  256. def is_visible_cell(self, row, col):
  257. """
  258. True if cell (row, col) is visible, else False.
  259. """
  260. return (row, col) not in self._hidden_cells
  261. def __iter__(self):
  262. """
  263. Iterate over all visible cells.
  264. """
  265. return ((row, col) for (row, col) in self.iter_all_cells() if self.is_visible_cell(row, col))
  266. class Style(dict):
  267. """
  268. Cell style object.
  269. """
  270. @staticmethod
  271. def get_default_cell_style():
  272. return Style({
  273. # textstyle is ignored by block cells
  274. 'textstyle': 'STANDARD',
  275. # text height in drawing units, ignored by block cells
  276. 'textheight': DEFAULT_CELL_TEXT_HEIGHT,
  277. # line spacing in percent = <textheight>*<linespacing>, ignored by block cells
  278. 'linespacing': DEFAULT_CELL_LINESPACING,
  279. # text stretch or block reference x-axis scaling factor
  280. 'xscale': DEFAULT_CELL_XSCALE,
  281. # block reference y-axis scaling factor, ignored by text cells
  282. 'yscale': DEFAULT_CELL_YSCALE,
  283. # dxf color index, ignored by block cells
  284. 'textcolor': DEFAULT_CELL_TEXTCOLOR,
  285. # text or block rotation in degrees
  286. 'rotation': 0.,
  287. # Letters are stacked top-to-bottom, but not rotated
  288. 'stacked': False,
  289. # simple combined align parameter, like 'TOP_CENTER', see also MText.VALID_ALIGN
  290. 'align': 'TOP_CENTER', # higher priority than 'haling' and 'valign'
  291. # horizontal alignment (const.LEFT, const.CENTER, const.RIGHT)
  292. 'halign': const.CENTER,
  293. # vertical alignment (const.TOP, const.MIDDLE, const.BOTTOM)
  294. 'valign': const.TOP,
  295. # left and right margin in drawing units
  296. 'hmargin': DEFAULT_CELL_HMARGIN,
  297. # top and bottom margin
  298. 'vmargin': DEFAULT_CELL_VMARGIN,
  299. # background color, dxf color index, ignored by block cells
  300. 'bgcolor': DEFAULT_CELL_BG_COLOR,
  301. # left border style
  302. 'left': Style.get_default_border_style(),
  303. # top border style
  304. 'top': Style.get_default_border_style(),
  305. # right border style
  306. 'right': Style.get_default_border_style(),
  307. # bottom border style
  308. 'bottom': Style.get_default_border_style(),
  309. })
  310. @staticmethod
  311. def get_default_border_style():
  312. return {
  313. # border status, True for visible, False for hidden
  314. 'status': DEFAULT_BORDER_STATUS,
  315. # dxf color index
  316. 'color': DEFAULT_BORDER_COLOR,
  317. # linetype name, BYLAYER if None
  318. 'linetype': DEFAULT_BORDER_LINETYPE,
  319. # drawing priority, higher values cover lower values
  320. 'priority': DEFAULT_BORDER_PRIORITY,
  321. }
  322. def set_border_status(self, left=True, right=True, top=True, bottom=True):
  323. """
  324. Set status of all cell borders at once.
  325. """
  326. for border, status in (('left', left),
  327. ('right', right),
  328. ('top', top),
  329. ('bottom', bottom)):
  330. self[border]['status'] = status
  331. def set_border_style(self, style, left=True, right=True, top=True, bottom=True):
  332. """
  333. Set border styles of all cell borders at once.
  334. """
  335. for border, status in (('left', left),
  336. ('right', right),
  337. ('top', top),
  338. ('bottom', bottom)):
  339. if status:
  340. self[border] = style
  341. class Grid(object):
  342. """
  343. Grid contains the graphical representation of the table.
  344. """
  345. def __init__(self, table):
  346. self.table = table
  347. # contains the x-axis coords of the grid lines between the data columns.
  348. self.col_pos = self._calc_col_pos()
  349. # contains the y-axis coords of the grid lines between the data rows.
  350. self.row_pos = self._calc_row_pos()
  351. # contains the horizontal border elements, list of border styles
  352. # get index with _border_index(row, col), which means the border element
  353. # above row, col, and row-indices are [0 .. nrows+1], nrows+1 for the
  354. # grid line below the last row; list contains only the border style with
  355. # the highest priority.
  356. self._hborders = None # created in _init_borders
  357. # same as _hborders but for the vertical borders,
  358. # col-indices are [0 .. ncols+1], ncols+1 for the last grid line right
  359. # of the last column
  360. self._vborders = None # created in _init_borders
  361. # border style to delete borders inside of merged cells
  362. self.noborder = dict(status=False, priority=999, linetype="BYLAYER", color=0)
  363. def _init_borders(self, hborder, vborder):
  364. """
  365. Init the _hborders with <hborder> and _vborders with <vborder>.
  366. """
  367. # <border_count> has more elements than necessary, but it unifies the
  368. # index calculation for _vborders and _hborders.
  369. # exact values are:
  370. # hborder_count = ncols * (nrows+1), hindex = ncols * <row> + <col>
  371. # vborder_count = nrows * (ncols+1), vindex = (ncols+1) * <row> + <col>
  372. border_count = (self.table.nrows+1) * (self.table.ncols+1)
  373. self._hborders = [hborder] * border_count
  374. self._vborders = [vborder] * border_count
  375. def _border_index(self, row, col):
  376. """
  377. Calculate linear index for border arrays _hborders and _vborders.
  378. """
  379. return row * (self.table.ncols+1) + col
  380. def set_hborder(self, row, col, border_style):
  381. """
  382. Set <border_style> for the horizontal border element above <row>, <col>.
  383. """
  384. return self._set_border_style(self._hborders, row, col, border_style)
  385. def set_vborder(self, row, col, border_style):
  386. """
  387. Set <border_style> for the vertical border element left of <row>, <col>.
  388. """
  389. return self._set_border_style(self._vborders, row, col, border_style)
  390. def _set_border_style(self, borders, row, col, border_style):
  391. """
  392. Set <border_style> for <row>, <col> in <borders>.
  393. """
  394. border_index = self._border_index(row, col)
  395. actual_borderstyle = borders[border_index]
  396. if border_style['priority'] >= actual_borderstyle['priority']:
  397. borders[border_index] = border_style
  398. def get_hborder(self, row, col):
  399. """
  400. Get the horizontal border element above <row>, <col>.
  401. Last grid line (below <nrows>) is the element above of <nrows+1>.
  402. """
  403. return self._get_border(self._hborders, row, col)
  404. def get_vborder(self, row, col):
  405. """
  406. Get the vertical border element left of <row>, <col>.
  407. Last grid line (right of <ncols>) is the element left of <ncols+1>.
  408. """
  409. return self._get_border(self._vborders, row, col)
  410. def _get_border(self, borders, row, col):
  411. """
  412. Get border element at <row>, <col> from <borders>.
  413. """
  414. return borders[self._border_index(row, col)]
  415. def _sum_fields(self, start_value, fields, append, sign=1.):
  416. """
  417. Adds step-by-step the fields-values, starting with <start_value>,
  418. and appends the resulting values to an other object with the
  419. append-method.
  420. """
  421. position = start_value
  422. append(position)
  423. for element in fields:
  424. position += element * sign
  425. append(position)
  426. def _calc_col_pos(self):
  427. """ Calculate the x-axis coords of the grid lines between the columns.
  428. """
  429. col_pos = []
  430. start_x = self.table.insert[0]
  431. self._sum_fields(start_x,
  432. self.table.col_widths,
  433. col_pos.append)
  434. return col_pos
  435. def _calc_row_pos(self):
  436. """ Calculate the y-axis coords of the grid lines between the rows.
  437. """
  438. row_pos = []
  439. start_y = self.table.insert[1]
  440. self._sum_fields(start_y,
  441. self.table.row_heights,
  442. row_pos.append, -1.)
  443. return row_pos
  444. def cell_coords(self, row, col, span):
  445. """ Get the coordinates of the cell <row>,<col> as absolute drawing units.
  446. :return: a tuple (left, right, top, bottom)
  447. """
  448. top = self.row_pos[row]
  449. bottom = self.row_pos[row+span[0]]
  450. left = self.col_pos[col]
  451. right = self.col_pos[col+span[1]]
  452. return left, right, top, bottom
  453. def render_cell_background(self, layout, row, col, cell):
  454. """
  455. Render the cell background for <row>, <col> as SOLID entity.
  456. """
  457. style = cell.style
  458. if style['bgcolor'] is None:
  459. return
  460. # get cell coords in absolute drawing units
  461. left, right, top, bottom = self.cell_coords(row, col, cell.span)
  462. ltop = (left, top)
  463. lbot = (left, bottom)
  464. rtop = (right, top)
  465. rbot = (right, bottom)
  466. layout.add_solid(
  467. points=(ltop, lbot, rtop, rbot),
  468. dxfattribs={
  469. 'color': style['bgcolor'],
  470. 'layer': self.table.bglayer,
  471. })
  472. def render_cell_content(self, layout, row, col, cell):
  473. """
  474. Render the cell content for <row>,<col> into layout object.
  475. """
  476. # get cell coords in absolute drawing units
  477. coords = self.cell_coords(row, col, cell.span)
  478. cell.render(layout, coords, self.table.fglayer)
  479. def render_lines(self, layout, visibility_map):
  480. """
  481. Render all grid lines into layout object.
  482. """
  483. # Init borders with default_style top- and left border.
  484. default_style = self.table.get_cell_style('default')
  485. hborder = default_style['top']
  486. vborder = default_style['left']
  487. self._init_borders(hborder, vborder)
  488. self._set_frames(self.table.frames)
  489. self._set_borders(self.table.iter_visible_cells(visibility_map))
  490. self._render_borders(layout, self.table)
  491. def _set_borders(self, visible_cells):
  492. """
  493. Set borders of the visible cells.
  494. """
  495. for row, col, cell in visible_cells:
  496. bottom_row = row + cell.span[0]
  497. right_col = col + cell.span[1]
  498. self._set_rect_borders(row, bottom_row, col, right_col, cell.style)
  499. self._set_inner_borders(row, bottom_row, col, right_col,
  500. self.noborder)
  501. def _set_inner_borders(self, top_row, bottom_row, left_col, right_col, border_style):
  502. """
  503. Set <border_style> to the inner borders of the rectangle <top_row...
  504. """
  505. if bottom_row - top_row > 1:
  506. for col in range(left_col, right_col):
  507. for row in range(top_row+1, bottom_row):
  508. self.set_hborder(row, col, border_style)
  509. if right_col - left_col > 1:
  510. for row in range(top_row, bottom_row):
  511. for col in range(left_col+1, right_col):
  512. self.set_vborder(row, col, border_style)
  513. def _set_rect_borders(self, top_row, bottom_row, left_col, right_col, style):
  514. """
  515. Set border <style> to the rectangle <top_row><bottom_row...
  516. The values describing the grid lines between the cells, see doc-strings for set_hborder and set_vborder and see
  517. comments for self._hborders and self._vborders.
  518. """
  519. for col in range(left_col, right_col):
  520. self.set_hborder(top_row, col, style['top'])
  521. self.set_hborder(bottom_row, col, style['bottom'])
  522. for row in range(top_row, bottom_row):
  523. self.set_vborder(row, left_col, style['left'])
  524. self.set_vborder(row, right_col, style['right'])
  525. def _set_frames(self, frames):
  526. """
  527. Set borders for all defined frames.
  528. """
  529. for frame in frames:
  530. top_row = frame.pos[0]
  531. left_col = frame.pos[1]
  532. bottom_row = top_row + frame.span[0]
  533. right_col = left_col + frame.span[1]
  534. self._set_rect_borders(top_row, bottom_row, left_col, right_col, frame.style)
  535. def _render_borders(self, layout, table):
  536. """
  537. Render the grid lines as LINE entities into layout object.
  538. """
  539. def render_line(start, end, style):
  540. """
  541. Render the LINE entity into layout object.
  542. """
  543. if style['status']:
  544. layout.add_line(
  545. start=start,
  546. end=end,
  547. dxfattribs={
  548. 'layer': layer,
  549. 'color': style['color'],
  550. 'linetype': style['linetype']
  551. }
  552. )
  553. def render_hborders():
  554. """
  555. Draw the horizontal grid lines.
  556. """
  557. for row in range(table.nrows+1):
  558. yrow = self.row_pos[row]
  559. for col in range(table.ncols):
  560. xleft = self.col_pos[col]
  561. xright = self.col_pos[col+1]
  562. style = self.get_hborder(row, col)
  563. render_line((xleft, yrow), (xright, yrow), style)
  564. def render_vborders():
  565. """
  566. Draw the vertical grid lines.
  567. """
  568. for col in range(table.ncols+1):
  569. xcol = self.col_pos[col]
  570. for row in range(table.nrows):
  571. ytop = self.row_pos[row]
  572. ybottom = self.row_pos[row+1]
  573. style = self.get_vborder(row, col)
  574. render_line((xcol, ytop), (xcol, ybottom), style)
  575. layer = table.gridlayer
  576. render_hborders()
  577. render_vborders()
  578. class Frame(object):
  579. """
  580. Represent a rectangle cell area enclosed by border lines.
  581. """
  582. def __init__(self, table, pos=(0, 0), span=(1, 1), style='default'):
  583. """
  584. Constructor
  585. Args:
  586. table: the assigned data table
  587. pos: tuple (row, col), border goes left and top of pos
  588. span: count of cells that Frame covers, border goes right and below of this cells
  589. style: style name as string
  590. """
  591. self.table = table
  592. self.pos = pos
  593. self.span = span
  594. self.stylename = style
  595. @property
  596. def style(self):
  597. return self.table.get_cell_style(self.stylename)
  598. class Cell(object):
  599. """
  600. Cell represents the table cell data.
  601. """
  602. def __init__(self, table, style='default', span=(1, 1)):
  603. """
  604. Constructor
  605. Args:
  606. table: assigned data table
  607. style: style name as string
  608. span: tuple(spanrows, spancols), count of cells that cell covers
  609. Cell does not know its own position in the data table, because a cell can be used multiple times in the same or
  610. in different tables. Therefore the cell itself can not determine if the cell-range reaches beyond the table
  611. borders.
  612. """
  613. self.table = table
  614. self.stylename = style
  615. # span values has to be >= 1
  616. self.span = span
  617. @property
  618. def span(self):
  619. return self._span
  620. @span.setter
  621. def span(self, value):
  622. """
  623. Ensures that span values are >= 1 in each direction.
  624. """
  625. self._span = (max(1, value[0]), max(1, value[1]))
  626. @property
  627. def style(self):
  628. """
  629. Returns: Style() object of the associated table.
  630. """
  631. return self.table.get_cell_style(self.stylename)
  632. @abstractmethod
  633. def render(self, layout, coords, layer):
  634. pass
  635. def get_workspace_coords(self, coords):
  636. """
  637. Reduces the cell-coords about the hmargin and the vmargin values.
  638. """
  639. hmargin = self.style['hmargin']
  640. vmargin = self.style['vmargin']
  641. return (coords[0] + hmargin, # left
  642. coords[1] - hmargin, # right
  643. coords[2] - vmargin, # top
  644. coords[3] + vmargin) # bottom
  645. CustomCell = Cell
  646. class TextCell(Cell):
  647. """
  648. Represents a multi line text. Text lines are separated by '\n'.
  649. """
  650. def __init__(self, table, text, style='default', span=(1, 1)):
  651. """
  652. Constructor
  653. Args:
  654. table: assigned data table
  655. text: multi line text, lines separated by '\n'
  656. style: style-name as string
  657. span: tuple(spanrows, spancols), count of cells that cell covers
  658. see Cell.__init__()
  659. """
  660. super(TextCell, self).__init__(table, style, span)
  661. self.text = text
  662. def render(self, layout, coords, layer):
  663. """
  664. Create the cell content as MText() object.
  665. Args:
  666. layout: target layout
  667. coords: tuple of border-coordinates : left, right, top, bottom
  668. layer: layer, which should be used for dxf entities
  669. """
  670. if not len(self.text):
  671. return
  672. left, right, top, bottom = self.get_workspace_coords(coords)
  673. style = self.style
  674. halign = style['halign']
  675. valign = style['valign']
  676. rotated = self.style['rotation']
  677. text = self.text
  678. if style['stacked']:
  679. rotated = 0.
  680. text = '\n'.join((char for char in self.text.replace('\n', ' ')))
  681. xpos = (left, float(left+right)/2., right)[halign]
  682. ypos = (bottom, float(bottom+top)/2., top)[valign-1]
  683. mtext = MText( # using dxfwrite MText() composite, because it works
  684. text,
  685. (xpos, ypos),
  686. linespacing=self.style['linespacing'],
  687. style=self.style['textstyle'],
  688. height=self.style['textheight'],
  689. rotation=rotated,
  690. xscale=self.style['xscale'],
  691. halign=halign,
  692. valign=valign,
  693. color=self.style['textcolor'],
  694. layer=layer,
  695. )
  696. mtext.render(layout)
  697. class BlockCell(Cell):
  698. """
  699. Cell that contains a block reference.
  700. """
  701. def __init__(self, table, blockdef, style='default', attribs=None, span=(1, 1)):
  702. """
  703. Constructor
  704. Args:
  705. table: assigned data table
  706. blockdef: block definition
  707. attribs: dict, with ATTRIB-Tags as keys
  708. style: style name as string
  709. span: tuple(spanrows, spancols), count of cells that cell covers
  710. see also Cell.__init__()
  711. """
  712. if attribs is None:
  713. attribs = {}
  714. super(BlockCell, self).__init__(table, style, span)
  715. self.blockdef = blockdef # dxf block definition!
  716. self.attribs = attribs
  717. def render(self, layout, coords, layer):
  718. """
  719. Create the cell content as INSERT-entity with trailing ATTRIB-Entities.
  720. Args:
  721. layout: target layout
  722. coords: tuple of border-coordinates : left, right, top, bottom
  723. layer: layer for cell content
  724. """
  725. left, right, top, bottom = self.get_workspace_coords(coords)
  726. style = self.style
  727. halign = style['halign']
  728. valign = style['valign']
  729. xpos = (left, float(left + right) / 2., right)[halign]
  730. ypos = (bottom, float(bottom + top) / 2., top)[valign-1]
  731. layout.add_auto_blockref(
  732. name=self.blockdef.name,
  733. insert=(xpos, ypos),
  734. values=self.attribs,
  735. dxfattribs={
  736. 'xscale': style['xscale'],
  737. 'yscale': style['yscale'],
  738. 'rotation': style['rotation'],
  739. 'layer': layer,
  740. }
  741. )