drawlib 0.2.3.dev2__py3-none-any.whl → 0.2.3.dev3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of drawlib might be problematic. Click here for more details.

@@ -0,0 +1,683 @@
1
+ # Copyright (c) 2024 Yuichi Ito (yuichi@yuichi.com)
2
+ #
3
+ # This software is licensed under the Apache License, Version 2.0.
4
+ # For more information, please visit: https://github.com/yuichi110/drawlib
5
+ #
6
+ # This software is provided "as is", without warranty of any kind,
7
+ # express or implied, including but not limited to the warranties of
8
+ # merchantability, fitness for a particular purpose and noninfringement.
9
+
10
+
11
+ """Table implementation module."""
12
+
13
+ import dataclasses
14
+ from typing import Any, List, Literal, Optional, Tuple, Union
15
+
16
+ from drawlib.v0_2.private.core.colors import Colors, ColorsThemeEssentials
17
+ from drawlib.v0_2.private.core.fonts import Font
18
+ from drawlib.v0_2.private.core.model import LineStyle, ShapeStyle, ShapeTextStyle
19
+ from drawlib.v0_2.private.core.theme import dtheme
20
+ from drawlib.v0_2.private.core_canvas.canvas import line, rectangle
21
+ from drawlib.v0_2.private.util import error_handler
22
+
23
+
24
+ @dataclasses.dataclass
25
+ class _CellStyleOrder:
26
+ order: Literal["range", "even_odd"]
27
+ textstyle1: ShapeTextStyle
28
+ background_color1: Union[Tuple[int, int, int], Tuple[int, int, int, float]]
29
+ textstyle2: Optional[ShapeTextStyle] = None
30
+ background_color2: Union[Tuple[int, int, int], Tuple[int, int, int, float], None] = None
31
+ rows: Optional[List[int]] = None
32
+ columns: Optional[List[int]] = None
33
+
34
+
35
+ @dataclasses.dataclass
36
+ class _CellInfo:
37
+ xy: Tuple[float, float]
38
+ width: float
39
+ height: float
40
+ background_color: Union[Tuple[int, int, int], Tuple[int, int, int, float]]
41
+ textstyle: ShapeTextStyle
42
+ text: str
43
+
44
+
45
+ class Table:
46
+ """A class used to create and manage the style and drawing of a table."""
47
+
48
+ @error_handler
49
+ def __init__(self) -> None:
50
+ """Initialize instance"""
51
+ self._bs_top: Optional[LineStyle] = None
52
+ self._bs_top2: Optional[LineStyle] = None
53
+ self._bs_bottom: Optional[LineStyle] = None
54
+ self._bs_left: Optional[LineStyle] = None
55
+ self._bs_left2: Optional[LineStyle] = None
56
+ self._bs_right: Optional[LineStyle] = None
57
+ self._bs_between_columns: Optional[LineStyle] = None
58
+ self._bs_between_rows: Optional[LineStyle] = None
59
+ self._cell_style_orders: List[_CellStyleOrder] = []
60
+
61
+ self.set_predefined_style("default")
62
+
63
+ @error_handler
64
+ def clear_styles(self) -> None:
65
+ """Clears all cell and border styles, resetting the table to have no styles."""
66
+ self._bs_top = None
67
+ self._bs_top2 = None
68
+ self._bs_bottom = None
69
+ self._bs_left = None
70
+ self._bs_left2 = None
71
+ self._bs_right = None
72
+ self._bs_between_columns = None
73
+ self._bs_between_rows = None
74
+ self._cell_style_orders = []
75
+
76
+ @error_handler
77
+ def set_predefined_style(
78
+ self,
79
+ name: Literal[
80
+ "default",
81
+ "none",
82
+ "monochrome",
83
+ "border_simple",
84
+ ],
85
+ ) -> None:
86
+ """Sets a predefined style for the table based on the given name.
87
+
88
+ Args:
89
+ name (Literal["default", "monochrome", "white"]): The name of the predefined style.
90
+
91
+ Raises:
92
+ ValueError: If the provided style name is not recognized.
93
+ """
94
+ self.clear_styles()
95
+
96
+ if name == "default":
97
+ """
98
+ header: background light blue, bold white font
99
+ even_odd: even snow color, odd white color
100
+ border: bottom only
101
+ """
102
+ self.set_style_cell_evenodd(
103
+ even_color=ColorsThemeEssentials.Snow,
104
+ even_textstyle=ShapeTextStyle(color=ColorsThemeEssentials.Charcoal),
105
+ odd_color=ColorsThemeEssentials.White,
106
+ odd_textstyle=ShapeTextStyle(color=ColorsThemeEssentials.Charcoal),
107
+ )
108
+ self.set_style_cell_header(
109
+ background_color=ColorsThemeEssentials.LightBlue,
110
+ textstyle=ShapeTextStyle(color=ColorsThemeEssentials.White, font=Font.SANSSERIF_BOLD),
111
+ )
112
+ self.set_style_border(
113
+ bottom=LineStyle(color=ColorsThemeEssentials.Charcoal, width=1),
114
+ )
115
+
116
+ elif name == "none":
117
+ """
118
+ header: background transparent
119
+ even_odd: both background transparent
120
+ border: no border
121
+ """
122
+ self.set_style_cell(
123
+ background_color=Colors.Transparent,
124
+ textstyle=ShapeTextStyle(color=ColorsThemeEssentials.Charcoal),
125
+ )
126
+
127
+ elif name == "monochrome":
128
+ """
129
+ header: background gray, bold font
130
+ even_odd: even snow color, odd white color
131
+ border: bottom only
132
+ """
133
+ self.set_style_cell_evenodd(
134
+ even_color=ColorsThemeEssentials.Snow,
135
+ even_textstyle=ShapeTextStyle(color=ColorsThemeEssentials.Charcoal),
136
+ odd_color=ColorsThemeEssentials.White,
137
+ odd_textstyle=ShapeTextStyle(color=ColorsThemeEssentials.Charcoal),
138
+ )
139
+ self.set_style_cell_header(
140
+ background_color=ColorsThemeEssentials.Graphite,
141
+ textstyle=ShapeTextStyle(color=ColorsThemeEssentials.White, font=Font.SANSSERIF_BOLD),
142
+ )
143
+ self.set_style_border(
144
+ bottom=LineStyle(color=ColorsThemeEssentials.Charcoal, width=1),
145
+ )
146
+
147
+ elif name == "border_simple":
148
+ """
149
+ header: background white, bold font
150
+ even_odd: both background white
151
+ border: header1, header2(light), bottom
152
+ """
153
+ self.set_style_cell(
154
+ background_color=ColorsThemeEssentials.White,
155
+ textstyle=ShapeTextStyle(color=ColorsThemeEssentials.Charcoal),
156
+ )
157
+ self.set_style_cell_header(
158
+ background_color=ColorsThemeEssentials.White,
159
+ textstyle=ShapeTextStyle(color=ColorsThemeEssentials.Charcoal, font=Font.SANSSERIF_BOLD),
160
+ )
161
+ self.set_style_border(
162
+ top=LineStyle(color=ColorsThemeEssentials.Charcoal, width=1.5),
163
+ top2=LineStyle(color=ColorsThemeEssentials.Charcoal, width=0.75),
164
+ bottom=LineStyle(color=ColorsThemeEssentials.Charcoal, width=1.5),
165
+ )
166
+
167
+ else:
168
+ raise ValueError(f'Provided pre defined name "{name}" is not supported.')
169
+
170
+ # cell styles
171
+
172
+ @error_handler
173
+ def set_style_cell_headers(
174
+ self,
175
+ background_color: Union[Tuple[int, int, int], Tuple[int, int, int, float]],
176
+ textstyle: Union[str, ShapeTextStyle],
177
+ ) -> None:
178
+ """Sets the style for both column and row headers.
179
+
180
+ Args:
181
+ background_color (Union[Tuple[int, int, int], Tuple[int, int, int, float]]):
182
+ The background color of the headers.
183
+ textstyle (Union[str, ShapeTextStyle]):
184
+ The text style of the headers. Can be a string key for predefined styles or a ShapeTextStyle object.
185
+ """
186
+ if isinstance(textstyle, str):
187
+ textstyle = dtheme.shapetextstyles.get(textstyle)
188
+
189
+ self.set_style_cell_rowheader(background_color=background_color, textstyle=textstyle)
190
+ self.set_style_cell_header(background_color=background_color, textstyle=textstyle)
191
+
192
+ @error_handler
193
+ def set_style_cell_header(
194
+ self,
195
+ background_color: Union[Tuple[int, int, int], Tuple[int, int, int, float]],
196
+ textstyle: Union[str, ShapeTextStyle],
197
+ ) -> None:
198
+ """
199
+ Sets the style for the column header.
200
+
201
+ Args:
202
+ background_color (Union[Tuple[int, int, int], Tuple[int, int, int, float]]):
203
+ The background color of the column header.
204
+ textstyle (Union[str, ShapeTextStyle]):
205
+ The text style of the column header.
206
+ Can be a string key for predefined styles or a ShapeTextStyle object.
207
+ """
208
+ if isinstance(textstyle, str):
209
+ textstyle = dtheme.shapetextstyles.get(textstyle)
210
+
211
+ self.set_style_cell(
212
+ background_color=background_color,
213
+ textstyle=textstyle,
214
+ rows=[0],
215
+ )
216
+
217
+ @error_handler
218
+ def set_style_cell_rowheader(
219
+ self,
220
+ background_color: Union[Tuple[int, int, int], Tuple[int, int, int, float]],
221
+ textstyle: Union[str, ShapeTextStyle],
222
+ ) -> None:
223
+ """
224
+ Sets the style for the row header.
225
+
226
+ Args:
227
+ background_color (Union[Tuple[int, int, int], Tuple[int, int, int, float]]):
228
+ The background color of the row header.
229
+ textstyle (Union[str, ShapeTextStyle]):
230
+ The text style of the row header.
231
+ Can be a string key for predefined styles or a ShapeTextStyle object.
232
+ """
233
+ if isinstance(textstyle, str):
234
+ textstyle = dtheme.shapetextstyles.get(textstyle)
235
+
236
+ self.set_style_cell(
237
+ background_color=background_color,
238
+ textstyle=textstyle,
239
+ columns=[0],
240
+ )
241
+
242
+ @error_handler
243
+ def set_style_cell_evenodd(
244
+ self,
245
+ even_color: Union[Tuple[int, int, int], Tuple[int, int, int, float]],
246
+ even_textstyle: Union[str, ShapeTextStyle],
247
+ odd_color: Union[Tuple[int, int, int], Tuple[int, int, int, float]],
248
+ odd_textstyle: Union[str, ShapeTextStyle],
249
+ ) -> None:
250
+ """
251
+ Sets alternating styles for even and odd rows.
252
+
253
+ Args:
254
+ even_color (Union[Tuple[int, int, int], Tuple[int, int, int, float]]):
255
+ The background color for even rows.
256
+ even_textstyle (Union[str, ShapeTextStyle]):
257
+ The text style for even rows.
258
+ Can be a string key for predefined styles or a ShapeTextStyle object.
259
+ odd_color (Union[Tuple[int, int, int], Tuple[int, int, int, float]]):
260
+ The background color for odd rows.
261
+ odd_textstyle (Union[str, ShapeTextStyle]):
262
+ The text style for odd rows.
263
+ Can be a string key for predefined styles or a ShapeTextStyle object.
264
+ """
265
+ if isinstance(even_textstyle, str):
266
+ even_textstyle = dtheme.shapetextstyles.get(even_textstyle)
267
+ if isinstance(odd_textstyle, str):
268
+ odd_textstyle = dtheme.shapetextstyles.get(odd_textstyle)
269
+
270
+ self._cell_style_orders.append(
271
+ _CellStyleOrder(
272
+ "even_odd",
273
+ background_color1=even_color,
274
+ background_color2=odd_color,
275
+ textstyle1=even_textstyle,
276
+ textstyle2=odd_textstyle,
277
+ )
278
+ )
279
+
280
+ @error_handler
281
+ def set_style_cell(
282
+ self,
283
+ background_color: Union[Tuple[int, int, int], Tuple[int, int, int, float]],
284
+ textstyle: Union[str, ShapeTextStyle],
285
+ rows: Optional[List[int]] = None,
286
+ columns: Optional[List[int]] = None,
287
+ ) -> None:
288
+ """
289
+ Sets the style for specific cells.
290
+
291
+ Args:
292
+ background_color (Union[Tuple[int, int, int], Tuple[int, int, int, float]]):
293
+ The background color of the cells.
294
+ textstyle (Union[str, ShapeTextStyle]):
295
+ The text style of the cells.
296
+ Can be a string key for predefined styles or a ShapeTextStyle object.
297
+ rows (Optional[List[int]]):
298
+ A list of row indices to apply the style to. If None, applies to all rows.
299
+ columns (Optional[List[int]]):
300
+ A list of column indices to apply the style to. If None, applies to all columns.
301
+ """
302
+ if isinstance(textstyle, str):
303
+ textstyle = dtheme.shapetextstyles.get(textstyle)
304
+
305
+ self._cell_style_orders.append(
306
+ _CellStyleOrder(
307
+ "range",
308
+ background_color1=background_color,
309
+ textstyle1=textstyle,
310
+ rows=rows,
311
+ columns=columns,
312
+ )
313
+ )
314
+
315
+ # border style
316
+
317
+ @error_handler
318
+ def set_style_border( # noqa: C901
319
+ self,
320
+ top: Union[str, LineStyle, None] = None,
321
+ top2: Union[str, LineStyle, None] = None,
322
+ bottom: Union[str, LineStyle, None] = None,
323
+ left: Union[str, LineStyle, None] = None,
324
+ left2: Union[str, LineStyle, None] = None,
325
+ right: Union[str, LineStyle, None] = None,
326
+ between_columns: Union[str, LineStyle, None] = None,
327
+ between_rows: Union[str, LineStyle, None] = None,
328
+ ) -> None:
329
+ """Sets the style for table borders.
330
+
331
+ Args:
332
+ top (Union[str, LineStyle, None]):
333
+ Style for the top border.
334
+ Can be a string key for predefined styles or a LineStyle object.
335
+ top2 (Union[str, LineStyle, None]):
336
+ Style for the secondary top border.
337
+ Can be a string key for predefined styles or a LineStyle object.
338
+ bottom (Union[str, LineStyle, None]):
339
+ Style for the bottom border.
340
+ Can be a string key for predefined styles or a LineStyle object.
341
+ left (Union[str, LineStyle, None]):
342
+ Style for the left border.
343
+ Can be a string key for predefined styles or a LineStyle object.
344
+ left2 (Union[str, LineStyle, None]):
345
+ Style for the secondary left border.
346
+ Can be a string key for predefined styles or a LineStyle object.
347
+ right (Union[str, LineStyle, None]):
348
+ Style for the right border.
349
+ Can be a string key for predefined styles or a LineStyle object.
350
+ between_columns (Union[str, LineStyle, None]):
351
+ Style for borders between columns.
352
+ Can be a string key for predefined styles or a LineStyle object.
353
+ between_rows (Union[str, LineStyle, None]):
354
+ Style for borders between rows.
355
+ Can be a string key for predefined styles or a LineStyle object.
356
+ """
357
+ if isinstance(top, str):
358
+ self._bs_top = dtheme.linestyles.get(top)
359
+ elif isinstance(top, LineStyle):
360
+ self._bs_top = top.copy()
361
+
362
+ if isinstance(top2, str):
363
+ self._bs_top2 = dtheme.linestyles.get(top2)
364
+ elif isinstance(top2, LineStyle):
365
+ self._bs_top2 = top2.copy()
366
+
367
+ if isinstance(bottom, str):
368
+ self._bs_bottom = dtheme.linestyles.get(bottom)
369
+ elif isinstance(bottom, LineStyle):
370
+ self._bs_bottom = bottom.copy()
371
+
372
+ if isinstance(left, str):
373
+ self._bs_left = dtheme.linestyles.get(left)
374
+ elif isinstance(left, LineStyle):
375
+ self._bs_left = left.copy()
376
+
377
+ if isinstance(left2, str):
378
+ self._bs_left2 = dtheme.linestyles.get(left2)
379
+ elif isinstance(left2, LineStyle):
380
+ self._bs_left2 = left2.copy()
381
+
382
+ if isinstance(right, str):
383
+ self._bs_right = dtheme.linestyles.get(right)
384
+ elif isinstance(right, LineStyle):
385
+ self._bs_right = right.copy()
386
+
387
+ if isinstance(between_columns, str):
388
+ self._bs_between_columns = dtheme.linestyles.get(between_columns)
389
+ elif isinstance(between_columns, LineStyle):
390
+ self._bs_between_columns = between_columns.copy()
391
+
392
+ if isinstance(between_rows, str):
393
+ self._bs_between_rows = dtheme.linestyles.get(between_rows)
394
+ elif isinstance(between_rows, LineStyle):
395
+ self._bs_between_rows = between_rows.copy()
396
+
397
+ # draw
398
+
399
+ @error_handler
400
+ def draw(
401
+ self,
402
+ xy: Tuple[float, float],
403
+ width: float,
404
+ height: float,
405
+ data: List[List[Any]],
406
+ ) -> None:
407
+ """Draws the table with equal-sized cells.
408
+
409
+ Args:
410
+ xy (Tuple[float, float]): The coordinates where the table should be drawn.
411
+ width (float): The total width of the table.
412
+ height (float): The total height of the table.
413
+ data (List[List[Any]]): The data to be displayed in the table.
414
+ """
415
+ num_rows = len(data)
416
+ num_cols = len(data[0])
417
+
418
+ column_widths = [width / num_cols] * num_cols
419
+ row_heights = [height / num_rows] * num_rows
420
+ self.draw_flexible(
421
+ xy=xy,
422
+ column_widths=column_widths,
423
+ row_heights=row_heights,
424
+ data=data,
425
+ )
426
+
427
+ @error_handler
428
+ def draw_flexible(
429
+ self,
430
+ xy: Tuple[float, float],
431
+ column_widths: List[float],
432
+ row_heights: List[float],
433
+ data: List[List[Any]],
434
+ ) -> None:
435
+ """Draws the table with flexible cell sizes.
436
+
437
+ Args:
438
+ xy (Tuple[float, float]): The coordinates where the table should be drawn.
439
+ column_widths (List[float]): A list of widths for each column.
440
+ row_heights (List[float]): A list of heights for each row.
441
+ data (List[List[Any]]): The data to be displayed in the table.
442
+ """
443
+ # create blank matrix
444
+ default_textstyle = dtheme.shapetextstyles.get()
445
+ matrix: List[List[_CellInfo]] = []
446
+ for row_data in data:
447
+ row: List[_CellInfo] = []
448
+ for column in row_data:
449
+ cell = _CellInfo(
450
+ xy=(0, 0),
451
+ width=0,
452
+ height=0,
453
+ background_color=Colors.White,
454
+ textstyle=default_textstyle,
455
+ text=str(column),
456
+ )
457
+ row.append(cell)
458
+ matrix.append(row)
459
+
460
+ # update matrix
461
+ self._update_cell_xy_size(
462
+ xy,
463
+ column_widths=column_widths,
464
+ row_heights=row_heights,
465
+ matrix=matrix,
466
+ )
467
+ self._update_cell_style(matrix)
468
+
469
+ # draw matrix
470
+ self._draw_cells(matrix=matrix)
471
+
472
+ # draw border line
473
+ self._draw_border_lines(
474
+ xy=xy,
475
+ column_widths=column_widths,
476
+ row_heights=row_heights,
477
+ )
478
+
479
+ # private
480
+
481
+ @staticmethod
482
+ def _update_cell_xy_size(
483
+ xy: Tuple[float, float],
484
+ column_widths: List[float],
485
+ row_heights: List[float],
486
+ matrix: List[List[_CellInfo]],
487
+ ) -> None:
488
+ x = xy[0]
489
+ y = xy[1]
490
+ for i, row_hight in enumerate(row_heights):
491
+ for j, column_width in enumerate(column_widths):
492
+ cell = matrix[i][j]
493
+ cell.xy = (x + column_width / 2, y - row_hight / 2)
494
+ cell.width = column_width
495
+ cell.height = row_hight
496
+ x += column_width
497
+ x = xy[0]
498
+ y -= row_hight
499
+
500
+ def _update_cell_style( # noqa: C901
501
+ self,
502
+ matrix: List[List[_CellInfo]],
503
+ ) -> None:
504
+ def style_even_odd(
505
+ even_background_color: Union[Tuple[int, int, int], Tuple[int, int, int, float]],
506
+ even_textstyle: ShapeTextStyle,
507
+ odd_background_color: Union[Tuple[int, int, int], Tuple[int, int, int, float]],
508
+ odd_textstyle: ShapeTextStyle,
509
+ ) -> None:
510
+ for i, row in enumerate(matrix):
511
+ if i % 2 == 0:
512
+ for c in row:
513
+ c.background_color = even_background_color
514
+ c.textstyle = even_textstyle
515
+ else:
516
+ for c in row:
517
+ c.background_color = odd_background_color
518
+ c.textstyle = odd_textstyle
519
+
520
+ def style_range(
521
+ background_color: Union[Tuple[int, int, int], Tuple[int, int, int, float]],
522
+ textstyle: ShapeTextStyle,
523
+ rows: Optional[List[int]],
524
+ columns: Optional[List[int]],
525
+ ) -> None:
526
+ if rows is None:
527
+ rows = list(range(len(matrix)))
528
+ if columns is None:
529
+ columns = list(range(len(matrix[0])))
530
+
531
+ for i, row in enumerate(matrix):
532
+ for j, col in enumerate(row):
533
+ if i not in rows:
534
+ continue
535
+ if j not in columns:
536
+ continue
537
+ col.background_color = background_color
538
+ col.textstyle = textstyle
539
+
540
+ for cso in self._cell_style_orders:
541
+ if cso.order == "even_odd":
542
+ if cso.background_color2 is None:
543
+ raise ValueError("Drawlib Internal error.")
544
+ if cso.textstyle2 is None:
545
+ raise ValueError("Drawlib Internal error.")
546
+
547
+ style_even_odd(
548
+ cso.background_color1,
549
+ cso.textstyle1,
550
+ cso.background_color2,
551
+ cso.textstyle2,
552
+ )
553
+
554
+ else:
555
+ style_range(
556
+ cso.background_color1,
557
+ cso.textstyle1,
558
+ cso.rows,
559
+ cso.columns,
560
+ )
561
+
562
+ @staticmethod
563
+ def _draw_cells(
564
+ matrix: List[List[_CellInfo]],
565
+ ) -> None:
566
+ for row in matrix:
567
+ for col in row:
568
+ cell_xy = col.xy
569
+ width = col.width
570
+ height = col.height
571
+ bg_color = col.background_color
572
+ textstyle = col.textstyle
573
+ text = col.text
574
+
575
+ rectangle(
576
+ xy=cell_xy,
577
+ width=width,
578
+ height=height,
579
+ style=ShapeStyle(
580
+ lwidth=0,
581
+ lcolor=Colors.Transparent,
582
+ fcolor=bg_color,
583
+ ),
584
+ text=text,
585
+ textstyle=textstyle,
586
+ )
587
+
588
+ def _draw_border_lines( # noqa: C901
589
+ self,
590
+ xy: Tuple[float, float],
591
+ column_widths: List[float],
592
+ row_heights: List[float],
593
+ ) -> None:
594
+ bs_top1: Optional[LineStyle] = None
595
+ if self._bs_top is not None:
596
+ bs_top1 = self._bs_top
597
+ elif self._bs_between_columns is not None:
598
+ bs_top1 = self._bs_between_columns
599
+
600
+ bs_top2: Optional[LineStyle] = None
601
+ if self._bs_top2 is not None:
602
+ bs_top2 = self._bs_top2
603
+ elif self._bs_between_columns is not None:
604
+ bs_top2 = self._bs_between_columns
605
+
606
+ bs_bottom: Optional[LineStyle] = None
607
+ if self._bs_bottom is not None:
608
+ bs_bottom = self._bs_bottom
609
+ elif self._bs_between_columns is not None:
610
+ bs_bottom = self._bs_between_columns
611
+
612
+ bs_left1: Optional[LineStyle] = None
613
+ if self._bs_left is not None:
614
+ bs_left1 = self._bs_left
615
+ elif self._bs_between_rows is not None:
616
+ bs_left1 = self._bs_between_rows
617
+
618
+ bs_left2: Optional[LineStyle] = None
619
+ if self._bs_left2 is not None:
620
+ bs_left2 = self._bs_left2
621
+ elif self._bs_between_rows is not None:
622
+ bs_left2 = self._bs_between_rows
623
+
624
+ bs_right: Optional[LineStyle] = None
625
+ if self._bs_right is not None:
626
+ bs_right = self._bs_right
627
+ elif self._bs_between_rows is not None:
628
+ bs_right = self._bs_between_rows
629
+
630
+ # draw rows
631
+ x = xy[0]
632
+ y1 = xy[1]
633
+ y2 = xy[1] - sum(row_heights)
634
+ for i, col_width in enumerate(column_widths):
635
+ if i == 0:
636
+ if bs_left1 is not None:
637
+ line((x, y1), (x, y2), style=bs_left1)
638
+ elif i == 1:
639
+ if bs_left2 is not None:
640
+ line((x, y1), (x, y2), style=bs_left2)
641
+ elif i == len(column_widths) - 1:
642
+ # right -1
643
+ if self._bs_between_columns:
644
+ line((x, y1), (x, y2), style=self._bs_between_columns)
645
+
646
+ # right. It is last
647
+ x += col_width
648
+ if bs_right is not None:
649
+ line((x, y1), (x, y2), style=bs_right)
650
+ elif self._bs_between_columns:
651
+ line((x, y1), (x, y2), style=self._bs_between_columns)
652
+
653
+ elif self._bs_between_columns:
654
+ line((x, y1), (x, y2), style=self._bs_between_columns)
655
+
656
+ x += col_width
657
+
658
+ y = xy[1]
659
+ x1 = xy[0]
660
+ x2 = xy[0] + sum(column_widths)
661
+ for i, row_height in enumerate(row_heights):
662
+ if i == 0:
663
+ if bs_top1 is not None:
664
+ line((x1, y), (x2, y), style=bs_top1)
665
+ elif i == 1:
666
+ if bs_top2 is not None:
667
+ line((x1, y), (x2, y), style=bs_top2)
668
+ elif i == len(row_heights) - 1:
669
+ # bottom -1
670
+ if self._bs_between_rows:
671
+ line((x1, y), (x2, y), style=self._bs_between_rows)
672
+
673
+ # right. It is last
674
+ y -= row_height
675
+ if bs_bottom is not None:
676
+ line((x1, y), (x2, y), style=bs_bottom)
677
+ elif self._bs_between_rows:
678
+ line((x1, y), (x2, y), style=self._bs_between_rows)
679
+
680
+ elif self._bs_between_rows:
681
+ line((x1, y), (x2, y), style=self._bs_between_rows)
682
+
683
+ y -= row_height