import math
import re
[docs]class PaperSize(object):
"""General class for paper sizes.
For every papersize, the class contains a regular expression match pattern for the name
and a method named ``get_<basename>_papersize``. The match pattern should include at least a group
for the basename of the papersize, which might be the full name. This basename is used to identify
the correct method to call. If more than one group is defined in the match pattern, the rest of
the groups are passed as arguments to the method. The method should have ``landscape`` as the final
argument.
For aliases, a separate dict is used: any name is first checked for aliases, and the canonical name is
used to determine the paper size.
Args:
width (float): the width of the paper size in mm
height (float): the height of the paper size in mm
name (str): the name of the paper size
landscape (bool): whether to set the largest value to width or not
"""
MATCH_TARGETS = (
re.compile("(a)(\d+)"),
re.compile("(b)(\d+)"),
re.compile("(c)(\d+)"),
re.compile("(ansi)-([a-e])"),
re.compile("(legal)"),
)
ALIASES = {"letter": "ANSI-A", "tabloid": "ANSI-B"}
[docs] def __init__(self, name="", width=None, height=None, landscape=False):
if not (width or height or name):
name = "A4"
self.width = width
self.height = height
self.name = name
self.landscape = landscape
if self.width is None and self.height is None and self.name:
# No width/height passed in, but a name is given
if self.name in self.ALIASES:
name = self.ALIASES[self.name]
for mt in self.MATCH_TARGETS:
m = mt.match(name.lower())
if not m:
continue
# Extract the basename and arguments for the method from the name
basename = m.groups()[0]
args = m.groups()[1:]
try:
func = getattr(self, "_get_{}_papersize".format(basename))
except AttributeError:
raise ValueError("Unable to parse papersize name")
self.width, self.height = func(*args, landscape=self.landscape)
def _get_a_papersize(self, index, landscape=False):
"""Returns the papersize from the A series with the given index number.
The A series is defined by setting the area of A0 to 1 square meter requiring that
the ratio between the largest and the smallest side is equal to the square root of 2, and
that successive members in the series have half the area. The result is rounded to an integer
number of mm.
Args:
index (int): the A series number to retrieve the papersize for (e.g. the 4 in A4)
landscape (bool): whether to return a landscape papersize or not
Returns:
tuple: the requested papersize (width, height) in mm
"""
index = int(index)
x = int(round(math.sqrt(1e6 / (pow(2, index) * math.sqrt(2)))))
y = int(round(math.sqrt(2) * x))
if landscape:
x, y = y, x
return x, y
def _get_b_papersize(self, index, landscape=False):
"""Returns the papersize from the B series with the given index number.
The B series is defined by taking the geometric mean of areas of the A series papersizes
with indices that are equal and one lower than the B series index. The ratio between the
largest and smallest size is again equal to the square root of 2. The geometric mean of
two numbers is defined as the square root of the product of the two numbers.
Args:
index (int): the B series number to retrieve the papersize for (e.g. the 4 in B4)
landscape (bool): whether to return a landscape papersize or not
Returns:
tuple: the requested papersize (width, height) in mm
"""
index = int(index)
x1, y1 = self._get_a_papersize(index - 1, landscape)
x2, y2 = self._get_a_papersize(index, landscape)
area1 = x1 * y1
area2 = x2 * y2
area = math.sqrt(area1 * area2)
x = int(round(math.sqrt(area / math.sqrt(2))))
y = int(round(x * math.sqrt(2)))
return x, y
def _get_c_papersize(self, index, landscape=False):
"""Returns the papersize from the C series with the given index number.
The C series is defined by taking the geometric mean of areas of the A series papersize
and the B series papersize with the same index. The ratio between the largest and smallest
size is again equal to the square root of 2. The geometric mean of two numbers is defined
as the square root of the product of the two numbers.
Args:
index (int): the B series number to retrieve the papersize for (e.g. the 4 in C4)
landscape (bool): whether to return a landscape papersize or not
Returns:
tuple: the requested papersize (width, height) in mm
"""
index = int(index)
x1, y1 = self._get_a_papersize(index, landscape)
x2, y2 = self._get_b_papersize(index, landscape)
area1 = x1 * y1
area2 = x2 * y2
area = math.sqrt(area1 * area2)
x = int(round(math.sqrt(area / math.sqrt(2))))
y = int(round(x * math.sqrt(2)))
return x, y
def _get_ansi_papersize(self, index, landscape=False):
n = ord(index) - ord("a") + 1
x, y = 216, 279
if landscape:
x, y = y, x
return n * x, n * y
def _get_legal_papersize(self, landscape=False):
x, y = 216, 356
if landscape:
x, y = y, x
return x, y
def __repr__(self):
return "PaperSize({}, {} mm x {} mm)".format(self.name, self.width, self.height)
[docs]class PaperMargin(object):
[docs] def __init__(self, left=24, bottom=24, right=None, top=None):
self.left = left
self.bottom = bottom
self.right = left if right is None else right
self.top = bottom if top is None else top
@property
def l(self):
return self.left
@property
def r(self):
return self.right
@property
def t(self):
return self.top
@property
def b(self):
return self.bottom
def __repr__(self):
return "PaperMargins(left {} mm, right {} mm, top {} mm, bottom {} mm)".format(
self.left, self.right, self.top, self.bottom
)
if __name__ == "__main__":
print(PaperSize())
print(PaperSize("ANSI-A"))
print(PaperSize("letter"))
print(PaperSize("A4"))
print(PaperSize("A3"))
print(PaperSize("A2"))
print(PaperMargin(24, 24))
print(PaperMargin(0, 0))
print(PaperMargin())