# Copyright 2010 Dirk Holtwick, holtwick.it # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import annotations import copy import logging from reportlab.platypus.tables import TableStyle from xhtml2pdf.tags import pisaTag from xhtml2pdf.util import getAlign, getBorderStyle, getSize, set_value from xhtml2pdf.xhtml2pdf_reportlab import PmlKeepInFrame, PmlTable log = logging.getLogger(__name__) def _width(value: str | float | None = None) -> str | float | None: if value is None: return None value = str(value) return value if value.endswith("%") else getSize(value) def _height(value: str | float | None = None) -> str | float | None: if value is None: return None value = str(value) return value if value.endswith("%") else getSize(value) class TableData: def __init__(self) -> None: self.align: str = "" self.col: int = 0 self.colw: list = [] self.data: list = [] self.mode: str = "" self.padding: int = 0 self.repeat: bool = False self.row: int = 0 self.rowh: list = [] self.span: list = [] self.styles: list[ tuple[str, tuple[int, int], tuple[int, int], str, str, str] ] = [] self.width: str | float = 0 def add_cell(self, data=None): self.col += 1 self.data[len(self.data) - 1].append(data) def add_style(self, data): self.styles.append(copy.copy(data)) def add_empty(self, x, y): self.span.append((x, y)) def get_data(self): data = self.data for x, y in self.span: # Loop through all the spans that are inside the boundaries of our # tables. If the y-coordinate is valid, we insert an empty cell. # As for the x coordinate, we somehow don't care. if y < len(data): data[y].insert(x, "") return data def add_cell_styles(self, c, begin, end, mode="td"): self.mode = mode.upper() if c.frag.backColor and mode != "tr": # XXX Stimmt das so? self.add_style(("BACKGROUND", begin, end, c.frag.backColor)) if 0: log.debug( "%r", ( begin, end, c.frag.borderTopWidth, c.frag.borderTopStyle, c.frag.borderTopColor, c.frag.borderBottomWidth, c.frag.borderBottomStyle, c.frag.borderBottomColor, c.frag.borderLeftWidth, c.frag.borderLeftStyle, c.frag.borderLeftColor, c.frag.borderRightWidth, c.frag.borderRightStyle, c.frag.borderRightColor, ), ) if ( getBorderStyle(c.frag.borderTopStyle) and c.frag.borderTopWidth and c.frag.borderTopColor is not None ): self.add_style( ( "LINEABOVE", begin, (end[0], begin[1]), c.frag.borderTopWidth, c.frag.borderTopColor, "squared", ) ) if ( getBorderStyle(c.frag.borderLeftStyle) and c.frag.borderLeftWidth and c.frag.borderLeftColor is not None ): self.add_style( ( "LINEBEFORE", begin, (begin[0], end[1]), c.frag.borderLeftWidth, c.frag.borderLeftColor, "squared", ) ) if ( getBorderStyle(c.frag.borderRightStyle) and c.frag.borderRightWidth and c.frag.borderRightColor is not None ): self.add_style( ( "LINEAFTER", (end[0], begin[1]), end, c.frag.borderRightWidth, c.frag.borderRightColor, "squared", ) ) if ( getBorderStyle(c.frag.borderBottomStyle) and c.frag.borderBottomWidth and c.frag.borderBottomColor is not None ): self.add_style( ( "LINEBELOW", (begin[0], end[1]), end, c.frag.borderBottomWidth, c.frag.borderBottomColor, "squared", ) ) self.add_style(("LEFTPADDING", begin, end, c.frag.paddingLeft or self.padding)) self.add_style( ("RIGHTPADDING", begin, end, c.frag.paddingRight or self.padding) ) self.add_style(("TOPPADDING", begin, end, c.frag.paddingTop or self.padding)) self.add_style( ("BOTTOMPADDING", begin, end, c.frag.paddingBottom or self.padding) ) class pisaTagTABLE(pisaTag): @staticmethod def set_borders(frag, attrs): set_value( frag, ( "borderLeftColor", "borderRightColor", "borderTopColor", "borderBottomColor", ), attrs.bordercolor, ) set_value( frag, ( "borderLeftWidth", "borderRightWidth", "borderTopWidth", "borderBottomWidth", ), attrs.border, ) set_value( frag, ( "borderBottomStyle", "borderLeftStyle", "borderTopStyle", "borderRightStyle", ), "solid", ) def start(self, c): c.addPara() attrs = self.attr c.tableData, self.tableData = TableData(), c.tableData tdata = c.tableData if attrs.border and attrs.bordercolor: self.set_borders(c.frag, attrs) tdata.padding = attrs.cellpadding tdata.add_cell_styles(c, (0, 0), (-1, -1), "table") tdata.align = attrs.align.upper() tdata.col = 0 tdata.row = 0 tdata.colw = [] tdata.rowh = [] tdata.repeat = attrs.repeat tdata.width = _width(attrs.width) def end(self, c): tdata = c.tableData data = tdata.get_data() # Add missing columns so that each row has the same count of columns # This prevents errors in Reportlab table try: maxcols = max([len(row) for row in data] or [0]) except ValueError: log.warning(c.warning("