
| 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/util.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.
from __future__ import annotations
import contextlib
import logging
import re
from copy import copy
from typing import Any
import arabic_reshaper
import reportlab
import reportlab.pdfbase._cidfontdata
from bidi import get_display
from reportlab.lib.colors import Color, toColor
from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY, TA_LEFT, TA_RIGHT
from reportlab.lib.units import cm, inch
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
import xhtml2pdf.default
log = logging.getLogger(__name__)
rgb_re = re.compile(
r"^.*?rgb[a]?[(]([0-9]+).*?([0-9]+).*?([0-9]+)(?:.*?(?:[01]\.(?:[0-9]+)))?[)].*?[ ]*$"
)
# =========================================================================
# Memoize decorator
# =========================================================================
class Memoized:
"""
A kwargs-aware memoizer, better than the one in python :).
Don't pass in too large kwargs, since this turns them into a tuple of
tuples. Also, avoid mutable types (as usual for memoizers)
What this does is to create a dictionary of {(*parameters):return value},
and uses it as a cache for subsequent calls to the same method.
It is especially useful for functions that don't rely on external variables
and that are called often. It's a perfect match for our getSize etc...
"""
def __init__(self, func) -> None:
self.cache: dict = {}
self.func = func
self.__doc__ = self.func.__doc__ # To avoid great confusion
self.__name__ = self.func.__name__ # This also avoids great confusion
def __call__(self, *args, **kwargs):
# Make sure the following line is not actually slower than what you're
# trying to memoize
args_plus = tuple(kwargs.items())
key = (args, args_plus)
try:
if key not in self.cache:
res = self.func(*args, **kwargs)
self.cache[key] = res
return self.cache[key]
except TypeError:
# happens if any of the parameters is a list
return self.func(*args, **kwargs)
def toList(value: Any, *, cast_tuple: bool = True) -> list:
cls: tuple[type, ...] = (list, tuple) if cast_tuple else (list,)
return list(value) if isinstance(value, cls) else [value] # type: ignore[call-overload]
def transform_attrs(obj, keys, container, func, extras=None):
"""
Allows to apply one function to set of keys checking if key is in container,
also transform ccs key to report lab keys.
extras = Are extra params for func, it will be call like func(*[param1, param2])
obj = frag
keys = [(reportlab, css), ... ]
container = cssAttr
"""
cpextras = extras
for reportlab_key, css in keys:
extras = cpextras
if extras is None:
extras = []
elif not isinstance(extras, list):
extras = [extras]
if css in container:
extras.insert(0, container[css])
setattr(obj, reportlab_key, func(*extras))
def copy_attrs(obj1, obj2, attrs):
"""
Allows copy a list of attributes from object2 to object1.
Useful for copy ccs attributes to fragment.
"""
for attr in attrs:
value = getattr(obj2, attr) if hasattr(obj2, attr) else None
if value is None and isinstance(obj2, dict) and attr in obj2:
value = obj2[attr]
setattr(obj1, attr, value)
def set_value(obj, attrs, value, *, do_copy=False):
"""Allows set the same value to a list of attributes."""
for attr in attrs:
if do_copy:
value = copy(value)
setattr(obj, attr, value)
@Memoized
def getColor(value, default=None):
"""
Convert to color value.
This returns a Color object instance from a text bit.
Mitigation for ReDoS attack applied by limiting input length and validating input.
"""
if value is None:
return None
if isinstance(value, Color):
return value
value = str(value).strip().lower()
# Limit the length of the value to prevent excessive input causing ReDoS
if len(value) > 100: # Set a reasonable length limit to avoid extreme inputs
return default
if value in {"transparent", "none"}:
return default
if value in COLOR_BY_NAME:
return COLOR_BY_NAME[value]
if value.startswith("#") and len(value) == 4:
value = "#" + value[1] + value[1] + value[2] + value[2] + value[3] + value[3]
elif rgb_re.match(value):
# Use match instead of search to ensure proper regex usage and limit to valid patterns
try:
r, g, b = (int(x) for x in rgb_re.match(value).groups())
value = f"#{r:02x}{g:02x}{b:02x}"
except ValueError:
pass
else:
# Shrug
pass
return toColor(value, default) # Calling the reportlab function
def getBorderStyle(value, default=None):
if value and (str(value).lower() not in {"none", "hidden"}):
return value
return default
MM: float = cm / 10.0
DPI96: float = 1.0 / 96.0 * inch
ABSOLUTE_SIZE_TABLE: dict[str, float] = {
"1": 50.0 / 100.0,
"xx-small": 50.0 / 100.0,
"x-small": 50.0 / 100.0,
"2": 75.0 / 100.0,
"small": 75.0 / 100.0,
"3": 1.0,
"medium": 1.0,
"4": 125.0 / 100.0,
"large": 125.0 / 100.0,
"5": 150.0 / 100.0,
"x-large": 150.0 / 100.0,
"6": 175.0 / 100.0,
"xx-large": 175.0 / 100.0,
"7": 200.0 / 100.0,
"xxx-large": 200.0 / 100.0,
}
RELATIVE_SIZE_TABLE: dict[str, float] = {
"larger": 1.25,
"smaller": 0.75,
"+4": 200.0 / 100.0,
"+3": 175.0 / 100.0,
"+2": 150.0 / 100.0,
"+1": 125.0 / 100.0,
"-1": 75.0 / 100.0,
"-2": 50.0 / 100.0,
"-3": 25.0 / 100.0,
}
MIN_FONT_SIZE: float = 1.0
@Memoized
def getSize(
value: str | float | list | tuple,
relative=0,
base: int | None = None,
default: float = 0.0,
) -> float:
"""
Converts strings to standard sizes.
That is the function taking a string of CSS size ('12pt', '1cm' and so on)
and converts it into a float in a standard unit (in our case, points).
>>> getSize('12pt')
12.0
>>> getSize('1cm')
28.346456692913385
"""
try:
original = value
if value is None:
return relative
if isinstance(value, float):
return value
if isinstance(value, int):
return float(value)
if isinstance(value, (tuple, list)):
value = "".join(value)
value = str(value).strip().lower().replace(",", ".")
if value.endswith("cm"):
return float(value[:-2].strip()) * cm
if value.endswith("mm"):
return float(value[:-2].strip()) * MM # 1MM = 0.1cm
if value.endswith("in"):
return float(value[:-2].strip()) * inch # 1pt == 1/72inch
if value.endswith("pt"):
return float(value[:-2].strip())
if value.endswith("pc"):
return float(value[:-2].strip()) * 12.0 # 1pc == 12pt
if value.endswith("px"):
# XXX W3C says, use 96pdi
# http://www.w3.org/TR/CSS21/syndata.html#length-units
return float(value[:-2].strip()) * DPI96
if value in {"none", "0", "0.0", "auto"}:
return 0.0
if relative:
if value.endswith("rem"): # XXX
# 1rem = 1 * fontSize
return float(value[:-3].strip()) * relative
if value.endswith("em"): # XXX
# 1em = 1 * fontSize
return float(value[:-2].strip()) * relative
if value.endswith("ex"): # XXX
# 1ex = 1/2 fontSize
return float(value[:-2].strip()) * (relative / 2.0)
if value.endswith("%"):
# 1% = (fontSize * 1) / 100
return (relative * float(value[:-1].strip())) / 100.0
if value in {"normal", "inherit"}:
return relative
if value in RELATIVE_SIZE_TABLE:
if base:
return max(MIN_FONT_SIZE, base * RELATIVE_SIZE_TABLE[value])
return max(MIN_FONT_SIZE, relative * RELATIVE_SIZE_TABLE[value])
if value in ABSOLUTE_SIZE_TABLE:
if base:
return max(MIN_FONT_SIZE, base * ABSOLUTE_SIZE_TABLE[value])
return max(MIN_FONT_SIZE, relative * ABSOLUTE_SIZE_TABLE[value])
return max(MIN_FONT_SIZE, relative * float(value))
try:
value = float(value)
except ValueError:
log.warning("getSize: Not a float %r", value)
return default # value = 0
return max(0, value)
except Exception:
log.warning("getSize %r %r", original, relative, exc_info=True)
return default
@Memoized
def getCoords(x, y, w, h, pagesize):
"""
As a stupid programmer I like to use the upper left
corner of the document as the 0,0 coords therefore
we need to do some fancy calculations.
"""
# ~ print pagesize
ax, ay = pagesize
if x < 0:
x = ax + x
if y < 0:
y = ay + y
if w is not None and h is not None:
if w <= 0:
w = ax - x + w
if h <= 0:
h = ay - y + h
return x, (ay - y - h), w, h
return x, (ay - y)
@Memoized
def getBox(box, pagesize):
"""
Parse sizes by corners in the form:
<X-Left> <Y-Upper> <Width> <Height>
The last to values with negative values are interpreted as offsets form
the right and lower border.
"""
box = str(box).split()
if len(box) != 4:
msg = "box not defined right way"
raise RuntimeError(msg)
x, y, w, h = (getSize(pos) for pos in box)
return getCoords(x, y, w, h, pagesize)
def getFrameDimensions(
data, page_width: float, page_height: float
) -> tuple[float, float, float, float]:
"""
Calculate dimensions of a frame.
Returns left, top, width and height of the frame in points.
"""
box = data.get("-pdf-frame-box", [])
if len(box) == 4:
return (getSize(box[0]), getSize(box[1]), getSize(box[2]), getSize(box[3]))
top = getSize(data.get("top", 0))
left = getSize(data.get("left", 0))
bottom = getSize(data.get("bottom", 0))
right = getSize(data.get("right", 0))
if "height" in data:
height = getSize(data["height"])
if "top" in data:
top = getSize(data["top"])
bottom = page_height - (top + height)
elif "bottom" in data:
bottom = getSize(data["bottom"])
top = page_height - (bottom + height)
if "width" in data:
width = getSize(data["width"])
if "left" in data:
left = getSize(data["left"])
right = page_width - (left + width)
elif "right" in data:
right = getSize(data["right"])
left = page_width - (right + width)
top += getSize(data.get("margin-top", 0))
left += getSize(data.get("margin-left", 0))
bottom += getSize(data.get("margin-bottom", 0))
right += getSize(data.get("margin-right", 0))
width = page_width - (left + right)
height = page_height - (top + bottom)
return left, top, width, height
@Memoized
def getPos(position, pagesize):
"""Pair of coordinates."""
position = str(position).split()
if len(position) != 2:
msg = "position not defined right way"
raise RuntimeError(msg)
x, y = (getSize(pos) for pos in position)
return getCoords(x, y, None, None, pagesize)
def getBool(s):
"""Is it a boolean?."""
return str(s).lower() in {"y", "yes", "1", "true"}
def getFloat(s):
with contextlib.suppress(Exception):
return float(s)
ALIGNMENTS = {
"left": TA_LEFT,
"center": TA_CENTER,
"middle": TA_CENTER,
"right": TA_RIGHT,
"justify": TA_JUSTIFY,
}
def getAlign(value, default=TA_LEFT):
return ALIGNMENTS.get(str(value).lower(), default)
_rx_datauri = re.compile(
"^data:(?P<mime>[a-z]+/[a-z]+);base64,(?P<data>.*)$", re.M | re.DOTALL
)
COLOR_BY_NAME = {
"activeborder": Color(212, 208, 200),
"activecaption": Color(10, 36, 106),
"aliceblue": Color(0.941176, 0.972549, 1),
"antiquewhite": Color(0.980392, 0.921569, 0.843137),
"appworkspace": Color(128, 128, 128),
"aqua": Color(0, 1, 1),
"aquamarine": Color(0.498039, 1, 0.831373),
"azure": Color(0.941176, 1, 1),
"background": Color(58, 110, 165),
"beige": Color(0.960784, 0.960784, 0.862745),
"bisque": Color(1, 0.894118, 0.768627),
"black": Color(0, 0, 0),
"blanchedalmond": Color(1, 0.921569, 0.803922),
"blue": Color(0, 0, 1),
"blueviolet": Color(0.541176, 0.168627, 0.886275),
"brown": Color(0.647059, 0.164706, 0.164706),
"burlywood": Color(0.870588, 0.721569, 0.529412),
"buttonface": Color(212, 208, 200),
"buttonhighlight": Color(255, 255, 255),
"buttonshadow": Color(128, 128, 128),
"buttontext": Color(0, 0, 0),
"cadetblue": Color(0.372549, 0.619608, 0.627451),
"captiontext": Color(255, 255, 255),
"chartreuse": Color(0.498039, 1, 0),
"chocolate": Color(0.823529, 0.411765, 0.117647),
"coral": Color(1, 0.498039, 0.313725),
"cornflowerblue": Color(0.392157, 0.584314, 0.929412),
"cornsilk": Color(1, 0.972549, 0.862745),
"crimson": Color(0.862745, 0.078431, 0.235294),
"cyan": Color(0, 1, 1),
"darkblue": Color(0, 0, 0.545098),
"darkcyan": Color(0, 0.545098, 0.545098),
"darkgoldenrod": Color(0.721569, 0.52549, 0.043137),
"darkgray": Color(0.662745, 0.662745, 0.662745),
"darkgreen": Color(0, 0.392157, 0),
"darkgrey": Color(0.662745, 0.662745, 0.662745),
"darkkhaki": Color(0.741176, 0.717647, 0.419608),
"darkmagenta": Color(0.545098, 0, 0.545098),
"darkolivegreen": Color(0.333333, 0.419608, 0.184314),
"darkorange": Color(1, 0.54902, 0),
"darkorchid": Color(0.6, 0.196078, 0.8),
"darkred": Color(0.545098, 0, 0),
"darksalmon": Color(0.913725, 0.588235, 0.478431),
"darkseagreen": Color(0.560784, 0.737255, 0.560784),
"darkslateblue": Color(0.282353, 0.239216, 0.545098),
"darkslategray": Color(0.184314, 0.309804, 0.309804),
"darkslategrey": Color(0.184314, 0.309804, 0.309804),
"darkturquoise": Color(0, 0.807843, 0.819608),
"darkviolet": Color(0.580392, 0, 0.827451),
"deeppink": Color(1, 0.078431, 0.576471),
"deepskyblue": Color(0, 0.74902, 1),
"dimgray": Color(0.411765, 0.411765, 0.411765),
"dimgrey": Color(0.411765, 0.411765, 0.411765),
"dodgerblue": Color(0.117647, 0.564706, 1),
"firebrick": Color(0.698039, 0.133333, 0.133333),
"floralwhite": Color(1, 0.980392, 0.941176),
"forestgreen": Color(0.133333, 0.545098, 0.133333),
"fuchsia": Color(1, 0, 1),
"gainsboro": Color(0.862745, 0.862745, 0.862745),
"ghostwhite": Color(0.972549, 0.972549, 1),
"gold": Color(1, 0.843137, 0),
"goldenrod": Color(0.854902, 0.647059, 0.12549),
"gray": Color(0.501961, 0.501961, 0.501961),
"graytext": Color(128, 128, 128),
"green": Color(0, 0.501961, 0),
"greenyellow": Color(0.678431, 1, 0.184314),
"grey": Color(0.501961, 0.501961, 0.501961),
"highlight": Color(10, 36, 106),
"highlighttext": Color(255, 255, 255),
"honeydew": Color(0.941176, 1, 0.941176),
"hotpink": Color(1, 0.411765, 0.705882),
"inactiveborder": Color(212, 208, 200),
"inactivecaption": Color(128, 128, 128),
"inactivecaptiontext": Color(212, 208, 200),
"indianred": Color(0.803922, 0.360784, 0.360784),
"indigo": Color(0.294118, 0, 0.509804),
"infobackground": Color(255, 255, 225),
"infotext": Color(0, 0, 0),
"ivory": Color(1, 1, 0.941176),
"khaki": Color(0.941176, 0.901961, 0.54902),
"lavender": Color(0.901961, 0.901961, 0.980392),
"lavenderblush": Color(1, 0.941176, 0.960784),
"lawngreen": Color(0.486275, 0.988235, 0),
"lemonchiffon": Color(1, 0.980392, 0.803922),
"lightblue": Color(0.678431, 0.847059, 0.901961),
"lightcoral": Color(0.941176, 0.501961, 0.501961),
"lightcyan": Color(0.878431, 1, 1),
"lightgoldenrodyellow": Color(0.980392, 0.980392, 0.823529),
"lightgray": Color(0.827451, 0.827451, 0.827451),
"lightgreen": Color(0.564706, 0.933333, 0.564706),
"lightgrey": Color(0.827451, 0.827451, 0.827451),
"lightpink": Color(1, 0.713725, 0.756863),
"lightsalmon": Color(1, 0.627451, 0.478431),
"lightseagreen": Color(0.12549, 0.698039, 0.666667),
"lightskyblue": Color(0.529412, 0.807843, 0.980392),
"lightslategray": Color(0.466667, 0.533333, 0.6),
"lightslategrey": Color(0.466667, 0.533333, 0.6),
"lightsteelblue": Color(0.690196, 0.768627, 0.870588),
"lightyellow": Color(1, 1, 0.878431),
"lime": Color(0, 1, 0),
"limegreen": Color(0.196078, 0.803922, 0.196078),
"linen": Color(0.980392, 0.941176, 0.901961),
"magenta": Color(1, 0, 1),
"maroon": Color(0.501961, 0, 0),
"mediumaquamarine": Color(0.4, 0.803922, 0.666667),
"mediumblue": Color(0, 0, 0.803922),
"mediumorchid": Color(0.729412, 0.333333, 0.827451),
"mediumpurple": Color(0.576471, 0.439216, 0.858824),
"mediumseagreen": Color(0.235294, 0.701961, 0.443137),
"mediumslateblue": Color(0.482353, 0.407843, 0.933333),
"mediumspringgreen": Color(0, 0.980392, 0.603922),
"mediumturquoise": Color(0.282353, 0.819608, 0.8),
"mediumvioletred": Color(0.780392, 0.082353, 0.521569),
"menu": Color(212, 208, 200),
"menutext": Color(0, 0, 0),
"midnightblue": Color(0.098039, 0.098039, 0.439216),
"mintcream": Color(0.960784, 1, 0.980392),
"mistyrose": Color(1, 0.894118, 0.882353),
"moccasin": Color(1, 0.894118, 0.709804),
"navajowhite": Color(1, 0.870588, 0.678431),
"navy": Color(0, 0, 0.501961),
"oldlace": Color(0.992157, 0.960784, 0.901961),
"olive": Color(0.501961, 0.501961, 0),
"olivedrab": Color(0.419608, 0.556863, 0.137255),
"orange": Color(1, 0.647059, 0),
"orangered": Color(1, 0.270588, 0),
"orchid": Color(0.854902, 0.439216, 0.839216),
"palegoldenrod": Color(0.933333, 0.909804, 0.666667),
"palegreen": Color(0.596078, 0.984314, 0.596078),
"paleturquoise": Color(0.686275, 0.933333, 0.933333),
"palevioletred": Color(0.858824, 0.439216, 0.576471),
"papayawhip": Color(1, 0.937255, 0.835294),
"peachpuff": Color(1, 0.854902, 0.72549),
"peru": Color(0.803922, 0.521569, 0.247059),
"pink": Color(1, 0.752941, 0.796078),
"plum": Color(0.866667, 0.627451, 0.866667),
"powderblue": Color(0.690196, 0.878431, 0.901961),
"purple": Color(0.501961, 0, 0.501961),
"red": Color(1, 0, 0),
"rosybrown": Color(0.737255, 0.560784, 0.560784),
"royalblue": Color(0.254902, 0.411765, 0.882353),
"saddlebrown": Color(0.545098, 0.270588, 0.07451),
"salmon": Color(0.980392, 0.501961, 0.447059),
"sandybrown": Color(0.956863, 0.643137, 0.376471),
"scrollbar": Color(212, 208, 200),
"seagreen": Color(0.180392, 0.545098, 0.341176),
"seashell": Color(1, 0.960784, 0.933333),
"sienna": Color(0.627451, 0.321569, 0.176471),
"silver": Color(0.752941, 0.752941, 0.752941),
"skyblue": Color(0.529412, 0.807843, 0.921569),
"slateblue": Color(0.415686, 0.352941, 0.803922),
"slategray": Color(0.439216, 0.501961, 0.564706),
"slategrey": Color(0.439216, 0.501961, 0.564706),
"snow": Color(1, 0.980392, 0.980392),
"springgreen": Color(0, 1, 0.498039),
"steelblue": Color(0.27451, 0.509804, 0.705882),
"tan": Color(0.823529, 0.705882, 0.54902),
"teal": Color(0, 0.501961, 0.501961),
"thistle": Color(0.847059, 0.74902, 0.847059),
"threeddarkshadow": Color(64, 64, 64),
"threedface": Color(212, 208, 200),
"threedhighlight": Color(255, 255, 255),
"threedlightshadow": Color(212, 208, 200),
"threedshadow": Color(128, 128, 128),
"tomato": Color(1, 0.388235, 0.278431),
"turquoise": Color(0.25098, 0.878431, 0.815686),
"violet": Color(0.933333, 0.509804, 0.933333),
"wheat": Color(0.960784, 0.870588, 0.701961),
"white": Color(1, 1, 1),
"whitesmoke": Color(0.960784, 0.960784, 0.960784),
"window": Color(255, 255, 255),
"windowframe": Color(0, 0, 0),
"windowtext": Color(0, 0, 0),
"yellow": Color(1, 1, 0),
"yellowgreen": Color(0.603922, 0.803922, 0.196078),
}
def get_default_asian_font():
lower_font_list = []
upper_font_list = []
font_dict = copy(reportlab.pdfbase._cidfontdata.defaultUnicodeEncodings)
fonts = font_dict.keys()
for font in fonts:
upper_font_list.append(font)
lower_font_list.append(font.lower())
return {lower_font_list[i]: upper_font_list[i] for i in range(len(lower_font_list))}
def set_asian_fonts(fontname):
font_dict = copy(reportlab.pdfbase._cidfontdata.defaultUnicodeEncodings)
fonts = font_dict.keys()
if fontname in fonts:
pdfmetrics.registerFont(UnicodeCIDFont(fontname))
def detect_language(name):
asian_language_list = xhtml2pdf.default.DEFAULT_LANGUAGE_LIST
if name in asian_language_list:
return name
return None
def arabic_format(text, language):
# Note: right now all of the languages are treated the same way.
# But maybe in the future we have to for example implement something
# for "hebrew" that isn't used in "arabic"
if detect_language(language) in {
"arabic",
"hebrew",
"persian",
"urdu",
"pashto",
"sindhi",
}:
ar = arabic_reshaper.reshape(text)
return get_display(ar)
return None
def frag_text_language_check(context, frag_text):
if hasattr(context, "language"):
language = context.__getattribute__("language")
detect_language_result = arabic_format(frag_text, language)
if detect_language_result:
return detect_language_result
return None
return None
class ImageWarning(Exception): # noqa: N818
pass