diff --git a/boxes/generators/bookholder.py b/boxes/generators/bookholder.py
new file mode 100644
index 00000000..2c8a3fa4
--- /dev/null
+++ b/boxes/generators/bookholder.py
@@ -0,0 +1,176 @@
+# Copyright (C) 2013-2019 Florian Festi
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from boxes import *
+
+
+class BookHolder(Boxes):
+ """Angled display stand for books, ring files, flyers, postcards, or business cards."""
+ description = """
+Smaller versions for postcards (with a small ledge) and for business cards:
+
+![BookHolder minis (front)](static/samples/BookHolder-minis.jpg)
+
+![BookHolder minis (side)](static/samples/BookHolder-minis-side.jpg)
+
+BookHolder with default parameters (A4 size, landscape, back_support):
+
+![BookHolder (back)](static/samples/BookHolder-back.jpg)
+"""
+
+
+ ui_group = "Misc"
+
+ def __init__(self) -> None:
+ super().__init__()
+
+ self.addSettingsArgs(edges.FingerJointSettings)
+
+ # Default size: DIN A4 (210mm * 297mm)
+ self.argparser.add_argument(
+ "--book_width", action="store", type=float, default=297.0,
+ help="total width of book stand")
+ self.argparser.add_argument(
+ "--book_height", action="store", type=float, default=210.0,
+ help="height of the front plate")
+ self.argparser.add_argument(
+ "--book_depth", action="store", type=float, default=40.0,
+ help="larger sizes for books with more pages")
+ self.argparser.add_argument(
+ "--ledge_height", action="store", type=float, default=0.0,
+ help="part in front to hold the book open (0 to turn off)")
+ self.argparser.add_argument(
+ "--angle", action="store", type=float, default=75.0,
+ help="degrees between floor and front plate")
+ self.argparser.add_argument(
+ "--bottom_support", action="store", type=float, default=20.0,
+ help="extra material on bottom to raise book up")
+ self.argparser.add_argument(
+ "--back_support", action="store", type=float, default=50.0,
+ help="height of additional support in the back (0 to turn off)")
+ self.argparser.add_argument(
+ "--radius", action="store", type=float, default=-1.0,
+ help="radius at the sharp corners (negative for radius=thickness)")
+
+ def sideWall(self, move=None):
+
+ # main angle
+ alpha = self.angle
+ # opposite angle
+ beta = 90 - alpha
+
+ # 1. Calculate the tall right triangle between front plate and back
+ # self.book_height is hypotenuse
+
+ # vertical piece
+ a = self.book_height * math.sin(math.radians(alpha))
+ # horizontal piece
+ b = self.book_height * math.sin(math.radians(beta))
+
+ # 2. Calculate the smaller triangle between bottom of book and front edge
+ # self.book_depth is hypotenuse, angles are the same as in other triangle but switched
+
+ # vertical piece
+ c = self.book_depth * math.sin(math.radians(beta))
+ # horizontal piece
+ d = self.book_depth * math.sin(math.radians(alpha))
+
+ # 3. Total dimensions
+
+ # Highest point on the right where the book back rests
+ max_height_back = a + self.bottom_support + self.radius
+ # Hightest point on the left where the book bottom rests
+ max_height_front = c + self.bottom_support + self.radius
+
+ total_height = max(max_height_back, max_height_front)
+
+ offset_s = math.sin(math.radians(alpha)) * self.radius
+ offset_c = math.cos(math.radians(alpha)) * self.radius
+
+ total_width = self.radius + offset_c + b + d + offset_s + self.radius
+
+ if self.move(total_width, total_height, move, True):
+ return
+
+ # Line on bottom
+ self.polyline(total_width, 90)
+
+ # Fingerholes for back support
+ if self.back_support > 0:
+ posx = self.bottom_support
+ posy = 2 * self.thickness
+ self.fingerHolesAt(posx, posy, self.back_support, 0)
+
+ # Back line straight up
+ self.polyline(max_height_back - offset_c - self.radius, 0)
+ self.corner((90+alpha, self.radius))
+
+ # Line for front plate
+ self.edges.get("F")(self.book_height)
+ self.corner(-90)
+
+ # Line where bottom of book rests
+ self.edges.get("F")(self.book_depth)
+ self.corner((90+beta, self.radius))
+
+ # Front line straight down
+ self.polyline(max_height_front - offset_s - self.radius, 90)
+
+ self.move(total_width, total_height, move)
+
+ def front_ledge(self, move):
+ total_height = self.ledge_height + self.thickness
+ if self.move(self.width, total_height, move, True):
+ return
+ self.moveTo(self.radius, 0)
+
+ h = total_height - self.radius
+ w = self.width - 2 * self.radius
+
+ self.edges.get("e")(w)
+ self.corner((90, self.radius))
+ self.edges.get("e")(h)
+ self.corner(90)
+ self.edges.get("F")(self.width)
+ self.corner(90)
+ self.edges.get("e")(h)
+ self.corner((90, self.radius))
+
+ self.move(self.width, total_height, move)
+
+ def render(self):
+ self.width = self.book_width - 2 * self.thickness
+ if self.radius < 0:
+ self.radius = self.thickness
+
+ # Back support
+ if self.back_support > 0:
+ self.rectangularWall(self.width, self.back_support, "efef", move="up", label="back support")
+
+ # Front ledge and fingers for lower plate
+ e = "e"
+ if self.ledge_height > 0:
+ self.front_ledge(move="up")
+ e = "f"
+
+ # Lower plate for book
+ self.rectangularWall(self.width, self.book_depth, e + "fFf", move="up", label="book bottom")
+
+ # Front plate
+ self.rectangularWall(self.width, self.book_height, "ffef", move="right", label="book back")
+
+ # Side walls
+ self.sideWall(move="right")
+ self.sideWall(move="right")
diff --git a/examples/BookHolder.svg b/examples/BookHolder.svg
new file mode 100644
index 00000000..c0cf5198
--- /dev/null
+++ b/examples/BookHolder.svg
@@ -0,0 +1,72 @@
+
+
\ No newline at end of file
diff --git a/static/samples/BookHolder-back-thumb.jpg b/static/samples/BookHolder-back-thumb.jpg
new file mode 100644
index 00000000..222ad024
Binary files /dev/null and b/static/samples/BookHolder-back-thumb.jpg differ
diff --git a/static/samples/BookHolder-back.jpg b/static/samples/BookHolder-back.jpg
new file mode 100644
index 00000000..b2c0ecf5
Binary files /dev/null and b/static/samples/BookHolder-back.jpg differ
diff --git a/static/samples/BookHolder-minis-side-thumb.jpg b/static/samples/BookHolder-minis-side-thumb.jpg
new file mode 100644
index 00000000..14de5a21
Binary files /dev/null and b/static/samples/BookHolder-minis-side-thumb.jpg differ
diff --git a/static/samples/BookHolder-minis-side.jpg b/static/samples/BookHolder-minis-side.jpg
new file mode 100644
index 00000000..5fa6bc2b
Binary files /dev/null and b/static/samples/BookHolder-minis-side.jpg differ
diff --git a/static/samples/BookHolder-minis-thumb.jpg b/static/samples/BookHolder-minis-thumb.jpg
new file mode 100644
index 00000000..13fc5509
Binary files /dev/null and b/static/samples/BookHolder-minis-thumb.jpg differ
diff --git a/static/samples/BookHolder-minis.jpg b/static/samples/BookHolder-minis.jpg
new file mode 100644
index 00000000..1f8615c6
Binary files /dev/null and b/static/samples/BookHolder-minis.jpg differ
diff --git a/static/samples/BookHolder-thumb.jpg b/static/samples/BookHolder-thumb.jpg
new file mode 100644
index 00000000..69eb72bc
Binary files /dev/null and b/static/samples/BookHolder-thumb.jpg differ
diff --git a/static/samples/BookHolder.jpg b/static/samples/BookHolder.jpg
new file mode 100644
index 00000000..bc936840
Binary files /dev/null and b/static/samples/BookHolder.jpg differ
diff --git a/static/samples/samples.sha256 b/static/samples/samples.sha256
index 082f46b4..17844e6f 100644
--- a/static/samples/samples.sha256
+++ b/static/samples/samples.sha256
@@ -167,6 +167,10 @@ f625a31c8f1f08341f8e4c0ba5d34524f92e258ca2ae3027774c399a200ddfc9 ../static/samp
bace3582c13ee543f09fd45752d4403e237d01541aaa4ea266e61e64fd12156a ../static/samples/FlexBook.jpg
0518ad5dfec317949f4a02b8bb4b60bcf781c82561e3f121dca1f2e2d0c5468c ../static/samples/BrickSorter-5.jpg
53ce98807aabf8fdd14e6fa9f0a3e405dadd8f4d7c936f8abc31f1572657763d ../static/samples/BrickSorter.jpg
+0ba4d726fb598ddb2d3b1fe67f5982b8d478997471177bf1247ca24b07927e83 ../static/samples/BookHolder-minis-side.jpg
+6ce9f050d9fb7648e4d203b02bd45190561f27595f259ec76f5ed33db3289c81 ../static/samples/BookHolder.jpg
+14179ba1de4a975f4f125aa58ab3448c64c34464e6fbc9e4269ea3ef15fc6b39 ../static/samples/BookHolder-back.jpg
+0bfff73dd7ddd30a2bb63b483db881e2141cc9eea52c0ee2472bea0027755257 ../static/samples/BookHolder-minis.jpg
f94c22f55d7067875d65c157a6dc221a09d383ace9234fee4b81544ab9cc4341 ../static/samples/Shadowbox-backlit.jpg
cf5315266705af168fcadf691dc58053e5623c3955bc782ca67087b0feff672e ../static/samples/BrickSorter-3.jpg
f51e3cb0e74e380beda4c0966ee770142ff4fa38c266ca026177f9f4978190a4 ../static/samples/BrickSorter-6.jpg