
| Current Path : /var/www/bavspeed/venvxxx/lib64/python3.12/site-packages/xhtml2pdf/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : /var/www/bavspeed/venvxxx/lib64/python3.12/site-packages/xhtml2pdf/xhtml2pdf_reportlab.py |
# 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.
# ruff: noqa: N802, N803
from __future__ import annotations
import contextlib
import copy
import logging
import sys
from hashlib import md5
from html import escape as html_escape
from io import BytesIO, StringIO
from typing import TYPE_CHECKING, ClassVar, Iterator
from uuid import uuid4
from PIL import Image as PILImage
from PIL import UnidentifiedImageError
from PIL.Image import Image
from reportlab.lib.enums import TA_RIGHT
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.utils import LazyImageReader, flatten, haveImages, open_for_read
from reportlab.pdfbase import pdfform
from reportlab.platypus.doctemplate import (
BaseDocTemplate,
IndexingFlowable,
PageTemplate,
)
from reportlab.platypus.flowables import (
CondPageBreak,
Flowable,
KeepInFrame,
ParagraphAndImage,
)
from reportlab.platypus.tableofcontents import TableOfContents
from reportlab.platypus.tables import Table, TableStyle
from reportlab.rl_config import register_reset
from xhtml2pdf.builders.watermarks import WaterMarks
from xhtml2pdf.files import pisaFileObject, pisaTempFile
from xhtml2pdf.reportlab_paragraph import Paragraph
from xhtml2pdf.util import ImageWarning, getBorderStyle
if TYPE_CHECKING:
from reportlab.graphics.shapes import Drawing
from reportlab.pdfgen.canvas import Canvas
try:
from reportlab.graphics import renderPM
from svglib.svglib import svg2rlg
except ImportError:
svg2rlg = None
renderPM = None
log = logging.getLogger(__name__)
MAX_IMAGE_RATIO: float = 0.95
PRODUCER: str = "xhtml2pdf <https://github.com/xhtml2pdf/xhtml2pdf/>"
class PTCycle(list):
def __init__(self) -> None:
self._restart: int = 0
self._idx: int = 0
super().__init__()
def cyclicIterator(self) -> Iterator:
while 1:
yield self[self._idx]
self._idx += 1
if self._idx >= len(self):
self._idx = self._restart
class PmlMaxHeightMixIn:
def setMaxHeight(self, availHeight: int) -> int:
self.availHeightValue: int = availHeight
if availHeight < 70000 and hasattr(self, "canv"):
if not hasattr(self.canv, "maxAvailHeightValue"):
self.canv.maxAvailHeightValue = 0
self.availHeightValue = self.canv.maxAvailHeightValue = max(
availHeight, self.canv.maxAvailHeightValue
)
return self.availHeightValue
def getMaxHeight(self) -> int:
return self.availHeightValue if hasattr(self, "availHeightValue") else 0
class PmlBaseDoc(BaseDocTemplate):
"""We use our own document template to get access to the canvas and set some information once."""
def beforePage(self) -> None:
self.canv._doc.info.producer = PRODUCER
"""
# Convert to ASCII because there is a Bug in Reportlab not
# supporting other than ASCII. Send to list on 23.1.2007
author = toString(self.pml_data.get("author", "")).encode("ascii","ignore")
subject = toString(self.pml_data.get("subject", "")).encode("ascii","ignore")
title = toString(self.pml_data.get("title", "")).encode("ascii","ignore")
# print repr((author,title,subject))
self.canv.setAuthor(author)
self.canv.setSubject(subject)
self.canv.setTitle(title)
if self.pml_data.get("fullscreen", 0):
self.canv.showFullScreen0()
if self.pml_data.get("showoutline", 0):
self.canv.showOutline()
if self.pml_data.get("duration", None) is not None:
self.canv.setPageDuration(self.pml_data["duration"])
"""
def afterFlowable(self, flowable: Flowable) -> None:
# Does the flowable contain fragments?
if getattr(flowable, "outline", False):
self.notify(
"TOCEntry",
(
flowable.outlineLevel,
html_escape(copy.deepcopy(flowable.text), quote=True),
self.page,
),
)
def handle_nextPageTemplate(self, pt: str | int | list | tuple) -> None:
"""If pt has also templates for even and odd page convert it to list."""
has_left_template: bool = self._has_template_for_name(f"{pt}_left")
has_right_template: bool = self._has_template_for_name(f"{pt}_right")
if has_left_template and has_right_template:
pt = [f"{pt}_left", f"{pt}_right"]
"""On endPage change to the page template with name or index pt"""
if isinstance(pt, str):
if hasattr(self, "_nextPageTemplateCycle"):
del self._nextPageTemplateCycle
for t in self.pageTemplates:
if t.id == pt:
self._nextPageTemplateIndex: int = self.pageTemplates.index(t)
return
msg = f"can't find template('{pt}')"
raise ValueError(msg)
if isinstance(pt, int):
if hasattr(self, "_nextPageTemplateCycle"):
del self._nextPageTemplateCycle
self._nextPageTemplateIndex = pt
elif isinstance(pt, (list, tuple)):
# used for alternating left/right pages
# collect the refs to the template objects, complain if any are bad
c: PTCycle = PTCycle()
for ptn in pt:
# special case name used to short circuit the iteration
if ptn == "*":
c._restart = len(c)
continue
for t in self.pageTemplates:
sys.exit()
if t.id == ptn.strip():
c.append(t)
break
if not c:
msg = "No valid page templates in cycle"
raise ValueError(msg)
if c._restart > len(c):
msg = "Invalid cycle restart position"
raise ValueError(msg)
# ensure we start on the first one$
self._nextPageTemplateCycle: PageTemplate = c.cyclicIterator()
else:
msg = "Argument pt should be string or integer or list"
raise TypeError(msg)
def _has_template_for_name(self, name: str) -> bool:
return any(template.id == name.strip() for template in self.pageTemplates)
class PmlPageTemplate(PageTemplate):
PORTRAIT: str = "portrait"
LANDSCAPE: str = "landscape"
# by default portrait
pageorientation: str = PORTRAIT
def __init__(self, **kw) -> None:
self.pisaStaticList: list = []
self.pisaBackgroundList: list[tuple] = []
self.pisaBackground = None
super().__init__(**kw)
self._page_count: int = 0
self._first_flow: bool = True
# Background Image
self.img = None
self.ph: int = 0
self.h: int = 0
self.w: int = 0
self.backgroundids: list[int] = []
def isFirstFlow(self, canvas: Canvas) -> bool:
if self._first_flow:
if canvas.getPageNumber() <= self._page_count:
self._first_flow = False
else:
self._page_count = canvas.getPageNumber()
canvas._doctemplate._page_count = canvas.getPageNumber()
return self._first_flow
def isPortrait(self) -> bool:
return self.pageorientation == self.PORTRAIT
def isLandscape(self) -> bool:
return self.pageorientation == self.LANDSCAPE
def beforeDrawPage(self, canvas: Canvas, doc):
canvas.saveState()
try:
if doc.pageTemplate.id not in self.backgroundids:
pisaBackground = None
if (
hasattr(self, "pisaBackground")
and self.pisaBackground
and (not self.pisaBackground.notFound())
):
if self.pisaBackground.getMimeType().startswith("image/"):
pisaBackground = WaterMarks.generate_pdf_background(
self.pisaBackground,
self.pagesize,
is_portrait=self.isPortrait(),
context=self.backgroundContext,
)
else:
pisaBackground = self.pisaBackground
self.backgroundids.append(doc.pageTemplate.id)
if pisaBackground:
self.pisaBackgroundList.append(
(canvas.getPageNumber(), pisaBackground, self.backgroundContext)
)
def pageNumbering(objList):
for obj in flatten(objList):
if isinstance(obj, PmlParagraph):
for frag in obj.frags:
if frag.pageNumber:
frag.text = str(pagenumber)
elif frag.pageCount:
frag.text = str(canvas._doctemplate._page_count)
elif isinstance(obj, PmlTable):
# Flatten the cells ([[1,2], [3,4]] becomes [1,2,3,4])
flat_cells = [
item for sublist in obj._cellvalues for item in sublist
]
pageNumbering(flat_cells)
try:
# Paint static frames
pagenumber = canvas.getPageNumber()
if pagenumber > self._page_count:
self._page_count = canvas.getPageNumber()
canvas._doctemplate._page_count = canvas.getPageNumber()
for frame in self.pisaStaticList:
frame_copy = copy.deepcopy(frame)
story = frame_copy.pisaStaticStory
pageNumbering(story)
frame_copy.addFromList(story, canvas)
except Exception: # TODO: Kill this!
log.debug("PmlPageTemplate", exc_info=True)
finally:
canvas.restoreState()
_ctr: int = 1
class PmlImageReader: # TODO We need a factory here, returning either a class for java or a class for PIL
"""Wraps up either PIL or Java to get data from bitmaps."""
_cache: ClassVar[dict] = {}
# Experimental features, disabled by default
use_cache: bool = False
use_lazy_loader: bool = False
process_internal_files: bool = False
def __init__(self, fileName: PmlImage | Image | str) -> None:
if isinstance(fileName, PmlImage):
self.__dict__ = fileName.__dict__ # borgize
return
# start with lots of null private fields, to be populated by
# the relevant engine.
self.fileName: PmlImage | Image | str = fileName or f"PILIMAGE_{id(self)}"
self._image: Image = None
self._width: int | None = None
self._height: int | None = None
self._transparent = None
self._data: bytes | str | None = None
self._dataA: PmlImageReader | None = None
self.fp: BytesIO | StringIO | None = None
if Image and isinstance(fileName, Image):
self._image = fileName
self.fp = getattr(fileName, "fp", None)
else:
try:
self.fp = open_for_read(fileName, "b")
if self.process_internal_files and isinstance(self.fp, StringIO):
data: str = self.fp.read()
with contextlib.suppress(Exception):
self.fp.close()
if self.use_cache:
if not self._cache:
register_reset(self._cache.clear)
cache_key = md5(data.encode("utf8")).digest()
data = self._cache.setdefault(cache_key, data)
self.fp = StringIO(data)
elif self.use_lazy_loader and isinstance(fileName, str):
# try Ralf Schmitt's re-opening technique of avoiding too many open files
self.fp.close()
del self.fp # will become a property in the next statement
self.__class__ = LazyImageReader
if haveImages:
# detect which library we are using and open the image
if not self._image:
self._image = self._read_image(self.fp)
if getattr(self._image, "format", None) == "JPEG":
self.jpeg_fh = self._jpeg_fh
else:
from reportlab.pdfbase.pdfutils import readJPEGInfo
try:
self._width, self._height, c = readJPEGInfo(self.fp)
except Exception as e:
msg = (
"Imaging Library not available, unable to import bitmaps"
" only jpegs"
)
raise ImageWarning(msg) from e
self.jpeg_fh = self._jpeg_fh
self._data = self.fp.read()
self.fp.seek(0)
# Catch all errors that are known and don't need the stack trace
except UnidentifiedImageError as e:
msg = "Cannot identify image file"
raise ImageWarning(msg) from e
@staticmethod
def _read_image(fp) -> Image:
if sys.platform[:4] == "java":
from java.io import ByteArrayInputStream
from javax.imageio import ImageIO
input_stream = ByteArrayInputStream(fp.read())
return ImageIO.read(input_stream)
return PILImage.open(fp)
def _jpeg_fh(self) -> BytesIO | StringIO | None:
fp = self.fp
if isinstance(fp, (BytesIO, StringIO)):
fp.seek(0)
return fp
def jpeg_fh(self) -> BytesIO | StringIO | None: # noqa: PLR6301
"""Might be replaced with _jpeg_fh in some cases"""
return None
def getSize(self) -> tuple[int, int]:
if self._width is None or self._height is None:
if sys.platform[:4] == "java":
self._width = self._image.getWidth()
self._height = self._image.getHeight()
else:
self._width, self._height = self._image.size
if TYPE_CHECKING:
assert self._width is not None and self._height is not None
return self._width, self._height
def getRGBData(self) -> bytes | str:
"""Return byte array of RGB data as string."""
if self._data is None:
self._dataA = None
if sys.platform[:4] == "java":
import jarray # TODO: Move to top.
from java.awt.image import PixelGrabber
width, height = self.getSize()
buffer = jarray.zeros(width * height, "i")
pg: PixelGrabber = PixelGrabber(
self._image, 0, 0, width, height, buffer, 0, width
)
pg.grabPixels()
# there must be a way to do this with a cast not a byte-level loop,
# I just haven't found it yet...
pixels: list[str] = []
a = pixels.append
for rgb in buffer:
a(chr((rgb >> 16) & 0xFF))
a(chr((rgb >> 8) & 0xFF))
a(chr(rgb & 0xFF))
self._data = "".join(pixels)
self.mode = "RGB"
else:
im = self._image
mode = self.mode = im.mode
if mode == "RGBA":
im.load()
self._dataA = PmlImageReader(im.split()[3])
im = im.convert("RGB")
self.mode = "RGB"
elif mode not in {"L", "RGB", "CMYK"}:
im = im.convert("RGB")
self.mode = "RGB"
self._data = im.tobytes() if hasattr(im, "tobytes") else im.tostring()
return self._data
def getImageData(self):
width, height = self.getSize()
return width, height, self.getRGBData()
def getTransparent(self):
if sys.platform[:4] == "java":
return None
if "transparency" in self._image.info:
transparency = self._image.info["transparency"] * 3
palette = self._image.palette
if hasattr(palette, "palette"):
palette = palette.palette
elif hasattr(palette, "data"):
palette = palette.data
else:
return None
# 8-bit PNGs could give an empty string as transparency value, so
# we have to be careful here.
try:
return list(palette[transparency : transparency + 3])
except Exception as e:
log.debug(str(e), exc_info=e)
return None
else:
return None
def __str__(self) -> str:
if isinstance(self.fileName, (PmlImage, Image, BytesIO)):
fn = self.fileName.read() or id(self)
return f"PmlImageObject_{hash(fn)}"
return str(self.fileName or id(self))
class PmlImage(Flowable, PmlMaxHeightMixIn):
def __init__(
self,
data: pisaFileObject | pisaTempFile | bytes,
src: str | None = None,
width: int | None = None,
height: int | None = None,
mask: str = "auto",
mimetype: str | None = None,
**kw: dict,
) -> None:
self.kw: dict = kw
self.hAlign: str = "CENTER"
self._mask: str = mask
self._imgdata: bytes = b""
if isinstance(data, bytes):
self._imgdata = data
elif isinstance(data, pisaTempFile):
self._imgdata = data.getvalue()
elif isinstance(data, pisaFileObject):
self._imgdata = data.getData() or b""
self.src: str | None = src
# print "###", repr(data)
self.mimetype: str | None = mimetype
# Resolve size
drawing = self.getDrawing()
self.imageWidth: float = 0.0
self.imageHeight: float = 0.0
if drawing:
_, _, self.imageWidth, self.imageHeight = drawing.getBounds() or (
0,
0,
0,
0,
)
else:
img = self.getImage()
if img:
self.imageWidth, self.imageHeight = img.getSize()
self.drawWidth: float = width or self.imageWidth
self.drawHeight: float = height or self.imageHeight
def wrap(self, availWidth, availHeight):
"""
Resize the image if necessary.
This can be called more than once! Do not overwrite important data like drawWidth.
"""
availHeight = self.setMaxHeight(availHeight)
# print "image wrap", id(self), availWidth, availHeight, self.drawWidth, self.drawHeight
width = min(self.drawWidth, availWidth)
wfactor = float(width) / self.drawWidth
height = min(self.drawHeight, availHeight * MAX_IMAGE_RATIO)
hfactor = float(height) / self.drawHeight
factor = min(wfactor, hfactor)
self.dWidth = self.drawWidth * factor
self.dHeight = self.drawHeight * factor
# print "image result", factor, self.dWidth, self.dHeight
return self.dWidth, self.dHeight
def getDrawing(
self, width: float | None = None, height: float | None = None
) -> Drawing | None:
"""If this image is a vector image and the library is available, returns a ReportLab Drawing."""
if svg2rlg:
try:
drawing = svg2rlg(BytesIO(self._imgdata))
except Exception:
return None
if drawing:
# Apply size
scale_x = 1
scale_y = 1
try:
if getattr(self, "drawWidth", None) is not None:
if width is None:
width = self.drawWidth
scale_x = width / drawing.width
if getattr(self, "drawHeight", None) is not None:
if height is None:
height = self.drawHeight
scale_y = height / drawing.height
if scale_x != 1 or scale_y != 1:
drawing.scale(scale_x, scale_y)
except ZeroDivisionError:
log.warning(
"SVG drawing could not be resized: %r",
self.src or self._imgdata[:50],
)
return drawing
return None
def getDrawingRaster(self) -> BytesIO | None:
"""If this image is a vector image and the libraries are available, returns a PNG raster."""
if svg2rlg and renderPM:
svg: Drawing = self.getDrawing()
if svg:
imgdata = BytesIO()
renderPM.drawToFile(svg, imgdata, fmt="PNG")
return imgdata
return None
def getImage(self) -> PmlImageReader:
"""Return a raster image."""
vectorRaster = self.getDrawingRaster()
imgdata = vectorRaster or BytesIO(self._imgdata)
return PmlImageReader(imgdata)
def draw(self) -> None:
# TODO this code should work, but untested
# drawing = self.getDrawing(self.dWidth, self.dHeight)
# if drawing and renderPDF:
# renderPDF.draw(drawing, self.canv, 0, 0)
# else:
img = self.getImage()
self.canv.drawImage(img, 0, 0, self.dWidth, self.dHeight, mask=self._mask)
def identity(self, maxLen=None):
return Flowable.identity(self, maxLen)
class PmlParagraphAndImage(ParagraphAndImage, PmlMaxHeightMixIn):
def wrap(self, availWidth, availHeight):
self.I.canv = self.canv
result = ParagraphAndImage.wrap(self, availWidth, availHeight)
del self.I.canv
return result
def split(self, availWidth, availHeight):
# print "# split", id(self)
if not hasattr(self, "wI"):
self.wI, self.hI = self.I.wrap(
availWidth, availHeight
) # drawWidth, self.I.drawHeight
return ParagraphAndImage.split(self, availWidth, availHeight)
class PmlParagraph(Paragraph, PmlMaxHeightMixIn):
def _calcImageMaxSizes(self, availWidth, availHeight):
self.hasImages = False
for frag in self.frags:
if hasattr(frag, "cbDefn") and frag.cbDefn.kind == "img":
img = frag.cbDefn
if img.width > 0 and img.height > 0:
self.hasImages = True
width = min(img.width, availWidth)
wfactor = float(width) / img.width
height = min(
img.height, availHeight * MAX_IMAGE_RATIO
) # XXX 99% because 100% do not work...
hfactor = float(height) / img.height
factor = min(wfactor, hfactor)
img.height *= factor
img.width *= factor
def wrap(self, availWidth, availHeight):
availHeight = self.setMaxHeight(availHeight)
style = self.style
self.deltaWidth = (
style.paddingLeft
+ style.paddingRight
+ style.borderLeftWidth
+ style.borderRightWidth
)
self.deltaHeight = (
style.paddingTop
+ style.paddingBottom
+ style.borderTopWidth
+ style.borderBottomWidth
)
# reduce the available width & height by the padding so the wrapping
# will use the correct size
availWidth -= self.deltaWidth
availHeight -= self.deltaHeight
# Modify maximum image sizes
self._calcImageMaxSizes(availWidth, availHeight)
# call the base class to do wrapping and calculate the size
Paragraph.wrap(self, availWidth, availHeight)
# self.height = max(1, self.height)
# self.width = max(1, self.width)
# increase the calculated size by the padding
self.width += self.deltaWidth
self.height += self.deltaHeight
return self.width, self.height
def split(self, availWidth, availHeight):
if len(self.frags) <= 0:
return []
# the split information is all inside self.blPara
if not hasattr(self, "deltaWidth"):
self.wrap(availWidth, availHeight)
availWidth -= self.deltaWidth
availHeight -= self.deltaHeight
return Paragraph.split(self, availWidth, availHeight)
def draw(self):
# Create outline
if getattr(self, "outline", False):
# Check level and add all levels
last = getattr(self.canv, "outlineLast", -1) + 1
while last < self.outlineLevel:
# print "(OUTLINE", last, self.text
key = uuid4().hex
self.canv.bookmarkPage(key)
self.canv.addOutlineEntry(self.text, key, last, not self.outlineOpen)
last += 1
self.canv.outlineLast = self.outlineLevel
key = uuid4().hex
self.canv.bookmarkPage(key)
self.canv.addOutlineEntry(
self.text, key, self.outlineLevel, not self.outlineOpen
)
last += 1
# Draw the background and borders here before passing control on to
# ReportLab. This is because ReportLab can't handle the individual
# components of the border independently. This will also let us
# support more border styles eventually.
canvas = self.canv
style = self.style
bg = style.backColor
leftIndent = style.leftIndent
bp = 0 # style.borderPadding
x = leftIndent - bp
y = -bp
w = self.width - (leftIndent + style.rightIndent) + 2 * bp
h = self.height + 2 * bp
if bg:
# draw a filled rectangle (with no stroke) using bg color
canvas.saveState()
canvas.setFillColor(bg)
canvas.rect(x, y, w, h, fill=1, stroke=0)
canvas.restoreState()
# we need to hide the bg color (if any) so Paragraph won't try to draw it again
style.backColor = None
# offset the origin to compensate for the padding
canvas.saveState()
canvas.translate(
(style.paddingLeft + style.borderLeftWidth),
-1 * (style.paddingTop + style.borderTopWidth),
) # + (style.leading / 4)))
# Call the base class draw method to finish up
Paragraph.draw(self)
canvas.restoreState()
# Reset color because we need it again if we run 2-PASS like we
# do when using TOC
style.backColor = bg
canvas.saveState()
def _drawBorderLine(bstyle, width, color, x1, y1, x2, y2):
# We need width and border style to be able to draw a border
if width and getBorderStyle(bstyle):
# If no color for border is given, the text color is used (like defined by W3C)
if color is None:
color = style.textColor
# print "Border", bstyle, width, color
if color is not None:
canvas.setStrokeColor(color)
canvas.setLineWidth(width)
canvas.line(x1, y1, x2, y2)
_drawBorderLine(
style.borderLeftStyle,
style.borderLeftWidth,
style.borderLeftColor,
x,
y,
x,
y + h,
)
_drawBorderLine(
style.borderRightStyle,
style.borderRightWidth,
style.borderRightColor,
x + w,
y,
x + w,
y + h,
)
_drawBorderLine(
style.borderTopStyle,
style.borderTopWidth,
style.borderTopColor,
x,
y + h,
x + w,
y + h,
)
_drawBorderLine(
style.borderBottomStyle,
style.borderBottomWidth,
style.borderBottomColor,
x,
y,
x + w,
y,
)
canvas.restoreState()
class PmlKeepInFrame(KeepInFrame, PmlMaxHeightMixIn):
def wrap(self, availWidth, availHeight):
availWidth = max(availWidth, 1.0)
availHeight = max(availHeight, 1.0)
self.maxWidth = availWidth
self.maxHeight = self.setMaxHeight(availHeight)
return KeepInFrame.wrap(self, availWidth, availHeight)
class PmlTable(Table, PmlMaxHeightMixIn):
@staticmethod
def _normWidth(w, maxw):
"""Normalize width when using percentages."""
if isinstance(w, str):
w = (maxw / 100.0) * float(w[:-1])
elif (w is None) or (w == "*"):
w = maxw
return min(w, maxw)
def _listCellGeom(self, V, w, s, W=None, H=None, aH=72000):
# print "#", self.availHeightValue
if aH == 72000:
aH = self.getMaxHeight() or aH
return Table._listCellGeom(self, V, w, s, W=W, H=H, aH=aH)
def wrap(self, availWidth, availHeight):
self.setMaxHeight(availHeight)
# Strange bug, sometime the totalWidth is not set !?
if not hasattr(self, "totalWidth"):
self.totalWidth = availWidth
# Prepare values
totalWidth = self._normWidth(self.totalWidth, availWidth)
remainingWidth = totalWidth
remainingCols = 0
newColWidths = self._colWidths
# Calculate widths that are fix
# IMPORTANT!!! We can not substitute the private value
# self._colWidths therefore we have to modify list in place
for i, colWidth in enumerate(newColWidths):
if colWidth is not None:
newColWidth = self._normWidth(colWidth, totalWidth)
remainingWidth -= newColWidth
else:
remainingCols += 1
newColWidth = None
newColWidths[i] = newColWidth
# Distribute remaining space
minCellWidth = totalWidth * 0.01
if remainingCols > 0:
for i, colWidth in enumerate(newColWidths):
if colWidth is None:
newColWidths[i] = max(
minCellWidth, remainingWidth / remainingCols
) # - 0.1
# Bigger than totalWidth? Lets reduce the fix entries propotionally
if sum(newColWidths) > totalWidth:
quotient = totalWidth / sum(newColWidths)
for i in range(len(newColWidths)):
newColWidths[i] *= quotient
# To avoid rounding errors adjust one col with the difference
diff = sum(newColWidths) - totalWidth
if diff > 0:
newColWidths[0] -= diff
return Table.wrap(self, availWidth, availHeight)
class PmlPageCount(IndexingFlowable):
def __init__(self) -> None:
super().__init__()
self.second_round = False
def isSatisfied(self):
s = self.second_round
self.second_round = True
return s
def drawOn(self, canvas, x, y, _sW=0):
pass
class PmlTableOfContents(TableOfContents):
def wrap(self, availWidth, availHeight):
"""All table properties should be known by now."""
widths = (availWidth - self.rightColumnWidth, self.rightColumnWidth)
# makes an internal table which does all the work.
# we draw the LAST RUN's entries! If there are
# none, we make some dummy data to keep the table
# from complaining
if len(self._lastEntries) == 0:
_tempEntries = [(0, "Placeholder for table of contents", 0)]
else:
_tempEntries = self._lastEntries
lastMargin = 0
tableData = []
tableStyle = [
("VALIGN", (0, 0), (-1, -1), "TOP"),
("LEFTPADDING", (0, 0), (-1, -1), 0),
("RIGHTPADDING", (0, 0), (-1, -1), 0),
("TOPPADDING", (0, 0), (-1, -1), 0),
("BOTTOMPADDING", (0, 0), (-1, -1), 0),
]
for i, entry in enumerate(_tempEntries):
level, text, pageNum = entry[:3]
leftColStyle = self.levelStyles[level]
if i: # Not for first element
tableStyle.append(
(
"TOPPADDING",
(0, i),
(-1, i),
max(lastMargin, leftColStyle.spaceBefore),
)
)
# print leftColStyle.leftIndent
lastMargin = leftColStyle.spaceAfter
# right col style is right aligned
rightColStyle = ParagraphStyle(
name="leftColLevel%d" % level,
parent=leftColStyle,
leftIndent=0,
alignment=TA_RIGHT,
)
leftPara = Paragraph(text, leftColStyle)
rightPara = Paragraph(str(pageNum), rightColStyle)
tableData.append([leftPara, rightPara])
self._table = Table(tableData, colWidths=widths, style=TableStyle(tableStyle))
self.width, self.height = self._table.wrapOn(self.canv, availWidth, availHeight)
return self.width, self.height
class PmlRightPageBreak(CondPageBreak):
def __init__(self) -> None:
pass
def wrap(self, availWidth, availHeight):
if not self.canv.getPageNumber() % 2:
self.width = availWidth
self.height = availHeight
return availWidth, availHeight
self.width = self.height = 0
return 0, 0
class PmlLeftPageBreak(CondPageBreak):
def __init__(self) -> None:
pass
def wrap(self, availWidth, availHeight):
if self.canv.getPageNumber() % 2:
self.width = availWidth
self.height = availHeight
return availWidth, availHeight
self.width = self.height = 0
return 0, 0
# --- Pdf Form
class PmlInput(Flowable):
def __init__(
self,
name,
input_type="text",
width=10,
height=10,
default="",
options=None,
multiline=0,
) -> None:
self.width = width
self.height = height
self.type = input_type
self.name = name
self.default = default
self.options = options if options is not None else []
self.multiline = multiline
def wrap(self, *args):
return self.width, self.height
def draw(self):
c = self.canv
c.saveState()
c.setFont("Helvetica", 10)
if self.type == "text":
pdfform.textFieldRelative(
c, self.name, 0, 0, self.width, self.height, multiline=self.multiline
)
c.rect(0, 0, self.width, self.height)
elif self.type == "radio":
c.rect(0, 0, self.width, self.height)
elif self.type == "checkbox":
if self.default:
pdfform.buttonFieldRelative(c, self.name, "Yes", 0, 0)
else:
pdfform.buttonFieldRelative(c, self.name, "Off", 0, 0)
c.rect(0, 0, self.width, self.height)
elif self.type == "select":
pdfform.selectFieldRelative(
c, self.name, self.default, self.options, 0, 0, self.width, self.height
)
c.rect(0, 0, self.width, self.height)
c.restoreState()