Link to the main program: placemat.ps
Links to documentation: ▶︎ Introduction, and a first placemat ▶︎ Fonts and glass decoration ▶︎ Compound Strings and non‑ASCII characters ▶︎ Page‑level controls ▷︎ Arrangement of glasses on the page ▶︎ Non‑Glasses Pages ▶︎ Document‑level controls ▶︎ Type sizes ▶︎ Translations ▶︎ Code injection ▶︎ Bitmap images ▶︎ Debugging
The core purpose of the software is to have a place to put glasses.
Already known are the PaperType
(e.g., /A3
), the margins, and how many glasses are to go on the page.
Further, the user has much direct control over the layout of the glasses.
The user has much control, but the default is also good. Unless there is a compelling need to change things, users are encouraged to take the easy course of letting the defaults do their work.
The user specifies an array, PackingStyles
, which is a list of generic packing styles.
In turn each style from the list is taken, and each allowed variation is tested.
Broadly, if a later-tested style allows a larger radius than the current best, then the old best is replaced by this better style.
If the element of PackingStyles
is just the style, it doesn’t need to be in an array.
But the elements of PackingStyles
can have constraints and variations.
If an element of PackingStyles
is to have such extra detail, then that element must be an array, starting with the style name.
E.g. (which is unrealistically verbose to show some of the possibilities):
[ /Diamonds /OnlyIfSheetNumMin 1 /OnlyIfSheetNumMax 1 /GlassesNumMin 3 /GlassesNumMax 3 /OnlyIfOrientation /Landscape /RowsNumMin 3 /RowsNumMax 4 /Mirror ]
The general mathematical problem, largest possible radius for n non-overlapping circles in a particular rectangle, is difficult (see PackoMania for solutions to many special cases).
This code has enough generality that it often finds the best, and when it can’t, its radius is not much smaller than the mathematical optimum.
E.g., for 14 glasses on /A3
see discussion in issue 151.
For many possible tastings, PackingStyles
can be left at its default value.
For example, if there are 24 different Madeiras over four /A4
pages, the defaults work (though, of course, very important, don’t forget to invite me).
In such a case the default value of GlassesOnSheetsMaxPerSheet
would cause GlassesOnSheets
to be four page-defining arrays each of length six glasses, which would cause the chosen element of PackingStyles
to be /RectangularDislocation
.
But if table space were tight, it could be better to have fifteen on an /A3
, six on an /A4
, and three on the right side of an /A4
, so using table space of only 210mm×3½ = 735mm per person.
There follow descriptions and pictures of the various allowable styles.
(The two PDFs (non-/Array
, /Array
), from which the bitmaps were made, show the element of PackingStyles
in the header.)
There are several classes of packing which might use space quite efficiently.
/Diamonds
: circles in each row and column lie midway between those in neighbouring rows and columns.
The code tests every number of rows from 1 to the number of glasses.
If some of these are not wanted, for whatever reason, control is possible by the likes of [ /Diamonds /RowsNumMin 3 /RowsNumMax 4 ]
.
The /Mirror
flag chooses the alternate chirality, if that has a sensible meaning.
/RectangularDislocation
: The next four examples are rectangular, optionally with a horizontal ‘dislocation’ in the middle, that having, /Diamonds
-style, circles lying between the circles in the adjacent row.
/SquareGrid
can be similar to /RectangularDislocation
.
In the latter the glasses fill the page: observe the six-glass example above, in which the circles touch their vertical neighbours, but have a slight gap from the horizontal neighbour.
In /SquareGrid
there is no dislocation, and by default the vertical and horizontal distances are the same and equal to the diameter.
/SquareGrid
has two optional one-parameter flags: /HorizontalAlignment
(which must have a value of one of /Left
, /Right
, /Centre
, or /Justify
); and /VerticalAlignment
(one of /Top
, /Bottom
, /Middle
, or /Justify
).
Obviously either /Justify
can allow distances to differ.
/DiamondsAndRectangular
: for some numbers of glasses and aspect ratios, the best fit can come from a pattern that is a combination of /Diamonds
and a rectangular pattern.
The diamonds can be offset from the diamonds either vertically or horizontally.
/DiamondsPlus
is mostly three-row diamonds (or, if /Portrait
, three-column), with two extras set between the three rows (columns).
It is the best packing of seven or ten glasses on several paper sizes.
There is a generalisation of /DiamondsPlus
over any number of rows (columns), not yet coded.
/RectangularAlternateNudge
: in this rectangular variant, alternate rows (or columns) are slightly nudged, better to fill the space.
Thus /RectangularAlternateNudge
is a small deviation away from greater symmetry, about which the the author is unenthusiastic.
Because of this non-enthusiasm, by default, /RectangularAlternateNudge
comes with a sub-parameter: [ /RectangularAlternateNudge /ImprovementPointsMin 2 ]
.
This imposes an additional requirement: this packing style is chosen only if it is an improvement on the previous best radius of ≥ 2pt ≈ 0.7mm ≈ 0.028″.
The next table of examples shows six glasses on /USL
= 8½″×11″ and on /A4
= 210mm×297mm, in all cases with margins of 24pt = ⅓″ ≈ 8.5mm, and space of 6pt ≈ 2.1mm for the header.
It shows four packing styles: /Diamonds
; /SquareGrid
; /RectangularDislocation
; and /RectangularAlternateNudge
.
On /USL
, /RectangularAlternateNudge
improves the radius by ≈ 2.6pt ≈0.9mm, and hence the diameter by ≈ 1.8mm.
But on /A4
the improvement in the radius is much less: only ≈ 0.28pt ≈ 0.098mm, which seems insufficient to justify the asymmetry’s aesthetic damage.
RectangularAlternateSplitNudge
, by making the nudged rows go in both directions, lessens the aesthetic damage of the non-Split
nudge, but makes an even smaller gain in the radius.
PackingStyles elementPaperType = width×height |
Example |
---|---|
/RectangularAlternateSplitNudge Ten glasses /Tabloid = 17″×11″ |
![]() |
/RectangularAlternateSplitNudge Twelve glasses /Tabloid = 17″×11″ |
![]() |
[ /RectangularAlternateSplitNudge /Mirror ] Twelve glasses /Tabloid = 17″×11″ |
![]() |
Both /RectangularAlternateSplitNudge
and /RectangularAlternateNudge
admit use of flags /ProhibitVerticalNudging
or /ProhibitHorizontalNudging
.
Obviously using both would be strange.
/Bespoke5
and /Bespoke7
are ignored for more than five and seven glasses, respectively.
PackingStyles elementPaperType = width×height |
Example |
---|---|
/Bespoke5 Five glasses /A4 = 297mm×210mm |
![]() |
[ /Bespoke5 /Mirror ] Five glasses /A4 = 297mm×210mm |
![]() |
/Bespoke7 Seven glasses /USLegal = 14″×8½″ |
![]() |
[ /Bespoke7 /Mirror ] Seven glasses /USLegal = 14″×8½″ |
![]() |
/Temple
is an intricate pattern, that has the largest radius for 10 glasses on /USL
, and for 13 glasses on either /USLegal
or /Tabloid
.
The sub-parameter /TempleExtraColsToLeftOrRowsBelow
is followed by one integer, and is to /Temple
as /RectColsToLeftOrRowsBelow
is to /DiamondsAndRectangular
.
PackingStyles elementPaperType = width×height |
Example |
---|---|
/Temple Ten glasses /USL = 8½″×11″ |
![]() |
/Temple Thirteen glasses /USLegal = 14″×8½″ |
![]() |
/Temple Thirteen glasses /Tabloid = 17″×11″ |
![]() |
There are classes of packing which, though they can have decorative or other advantages, are not space efficient.
/PostsAndLintel
arranges the circles around the left, top and right edges of the page, optionally with some circles at the bottom centre.
This design might be particularly appropriate if the central circles held the candidate blends of a vintage, with some of the components around the edge.
/Arch
is similar, with the circles on a half ellipse.
PackingStyles elementPaperType = width×height |
Example |
---|---|
[ /Arch /CentralGlasses 3 ] Seven + three = ten glasses /A3 = 420mm×297mm |
![]() |
[ /PostsAndLintel /CentralGlasses 1 ] Seven + one = eight glasses /A3 = 420mm×297mm |
![]() |
[ /PostsAndLintel /CentralGlasses 2 /Mirror ] Eight + two = ten glasses /A3 = 420mm×297mm |
![]() |
/Sides
, /LeftSide
, /RightSide
, /TopRow
, /MiddleRow
, and /BottomRow
are mostly self-explanatory.
/RightSide
(and, mutatis mutandis, /LeftSide
) have a particular use.
Consider a tasting with 17 glasses.
This could be set as 6 + 6 + 5 on 3×/A4
.
But if table space were tight, 14 on 1×/A3
and 3 on 1×/RightSide
/A4
would use only 2½ /A4
widths, if the unused half of the /A4
were tucked under the /A3
.
Suppressing the ornaments tucked under the /A3
, such as HeadersCenter
, would look neater.
This can be done by adding to the array an extra flag or flags, /SuppressOrnamentsLeft
, /SuppressOrnamentsCentre
, /SuppressOrnamentsRight
, which for the pages using that packing style suppresses the unwanted headers, footers, icons, and water boxes.
It also accepts the /VerticalAlignment
parameter.
Example item of PackingStyles:
[ /RightSide /GlassesNumMax 3 /OnlyIfOrientation /Portrait /VerticalAlignment /Justify /SuppressNonRightOrnaments ]
PackingStyles elementPaperType = width×height |
Example |
---|---|
Sides Six glasses /A3 = 420mm×297mm |
![]() |
TopRow Four glasses /A3 = 420mm×297mm |
![]() |
BottomRow Four glasses /A3 = 420mm×297mm |
![]() |
MiddlemRow Four glasses /A3 = 420mm×297mm |
![]() |
/LeftSide Three glasses /A4 = 297mm×210mm |
![]() |
/RightSide Three glasses /A4 = 297mm×210mm |
![]() |
It is possible to specify an arrangement with an array, thus:
[ /Array /Positions [0 2] [2 2] [4 2] [6 2] [0 1] [3 1] [6 1] [0 0] [3 0] [6 0] ]
The style is /Array
.
That can have any of the selection controls, such as /OnlyIfOrientation /Landscape
.
After which is /Positions
.
After the /Positions
marker is a list of points, typically of the form [x y]
, with y increasing up the page.
The code then chooses the radius and separately scales the x and y directions such that things fit as snugly as possible, obviously subject to the other upper bounds on the radius.
The glass ordering is that given in the array: there is no subsequent sorting or ordering.
There is a more complicated variant, in which some of the sub-arrays are of the form [x y x' y']
.
The first two elements are used, as before, and fix the radius and the canvas.
The circle at (x,y) is then moved in a straight line towards (x',y').
A circle stops moving if it collides with another circle (a tangential touch not being a collision); if it collides with the edge of the page; or it arrives at (x',y').
The example image shows, on /A3
, an alternation of the previous code extract to:
[ /Array /Positions [0 2] [2 2 3 2] [4 2 3 2] [6 2] [0 1] [3 1] [6 1] [0 0] [3 0] [6 0] ]
The difference is the movement of the circles at (2,2) and (4,2), both towards (3,2). For the author’s taste, the closer relationship of the two B0 circles (same wine in single and magnum) means they should be touching, rather than equally spaced between A0 and C0.
There are other flags and constraints and sub-parameters, as follows.
-
/Mirror
: the alternate chirality. If there isn’t a sensible meaning to this, ignored. -
/ShoveLeft
and/ShoveRight
: if there is spare space between the circles, probably because the radius has been shrunk to that on another sheet, the circles are shoved leftwards (or rightwards) against that margin. But not heeded in every base style. -
/PackingDirectionTopToBottom bool
and/PackingDirectionLeftToRight bool
and/PackingNestingColumnMajor bool
: by default most layouts start at the top-left, work across to the top-right, then start the second row slightly further down on the left. This can be changed: glasses can run right-to-left, bottom-to-top, and the nesting order of columns and rows can be exchanged. For large pre-poured tastings on/A3
or/Tabloid
, generally one pre-pours the youngest first. If these are in the front row, it is more fiddly to lower subsequent older vintages into place. Adding/PackingNestingColumnMajor true
fixes this. (The global value ofPackingNestingColumnMajor
is the default value within PackingStyles, and also affects the ordering in Cork-Display pages.) -
/ProhibitVerticalNudging
or/ProhibitHorizontalNudging
: have the obvious effect in base styles/RectangularAlternateNudge
and/RectangularAlternateSplitNudge
. -
/RectColsToLeftOrRowsBelow int
chooses the position of the diamonds-style block in/DiamondsAndRectangular
. -
/TempleExtraColsToLeftOrRowsBelow int
chooses the position of the ‘hole’ in/Temple
. -
/CentralGlasses int
: both/PostsAndLintel
and/Arch
packings can have 0, 1, 2, or 3 glasses in the centre, the remainder running round the edge or semi-ellipse. -
/RowsNumMin int
and/RowsNumMax int
: minimum and maximum number of rows permitted. Relevant in the/Diamonds
,/RectangularDislocation
,/SquareGrid
,/RectangularAlternateNudge
, and/PostsAndLintel
packing styles. -
/OnlyIfSheetNumMin int
and/OnlyIfSheetNumMax int
: specifying permitted values for the internal variable SheetNum, which is 0 on the first sheet, 1 on the second, etc. -
/GlassesNumMin int
and/GlassesNumMax int
: permitted number of glasses. If outside range, this packing specification not used. -
/OnlyIfOrientation name
, the sub-parameter being one of/Landscape
,/Portrait
, or/Either
, the last being the default if this sub-parameter absent. IfOrientation
not matching, this packing specification not used. -
/ImprovementPointsMin num
and/ImprovementProportionMin num
: this specification used only if beating previous best radius by the required amount, either absolute (e.g.,2
points) or as a proportion (e.g.,0.01
= 1%).
If PackingStyles
is empty, or contains only invalid/impossible layouts, then instead several of the regular layouts are tried.
There is aesthetic merit in imposing that radii are consistent across pages within a single session.
This is controlled by ShrinkRadii
, which can be:
/NotAtAll
, which leaves each page at its own best possible;/ToSmallestSamePageOrdering
, which ensures consistency within each session;/ToSmallest
, which ensures consistency over the whole document;[ 0 0 0 … 1 1 1 … 2 2 ]
, which is an arbitrary array of the same length asGlassesOnSheets
, pages’ radii being forced to their lesser if elements satisfy PostScript’seq
condition.
There is also the simple numerical parameter MaxRadius
, defaulting to 150
⇒ diameter ≤ 300pt = 4⅙″ = 105⅚mm.
the following shows glass diameters for various numbers of glasses on various page sizes, using the default value of PackingStyles
, and assuming that MaxRadius
is sufficently large.
Num Glasses |
/A4 210mm×297mm |
/A3 420mm×297mm |
/USLegal 14″×8½″ |
/USL 8½″×11″ |
/Tabloid 17″×11″ |
---|---|---|---|---|---|
3 | 113.9mm, 4.49″ | 164.6mm, 6.48″ | 126.9mm, 5.00″ | 113.1mm, 4.45″ | 161.1mm, 6.34″ |
4 | 105.1mm, 4.14″ | 151.6mm, 5.97″ | 112.8mm, 4.44″ | 105.6mm, 4.16″ | 145.6mm, 5.73″ |
5 | 96.5mm, 3.80″ | 140.0mm, 5.51″ | 106.9mm, 4.21″ | 96.6mm, 3.80″ | 138.3mm, 5.45″ |
6 | 92.7mm, 3.65″ | 134.4mm, 5.29″ | 101.4mm, 3.99″ | 88.6mm, 3.49″ | 130.2mm, 5.13″ |
7 | 81.1mm, 3.19″ | 117.2mm, 4.61″ | 92.7mm, 3.65″ | 79.3mm, 3.12″ | 116.4mm, 4.58″ |
8 | 77.2mm, 3.04″ | 111.2mm, 4.38″ | 87.0mm, 3.42″ | 76.5mm, 3.01″ | 109.7mm, 4.32″ |
9 | 72.8mm, 2.87″ | 105.0mm, 4.13″ | 78.7mm, 3.10″ | 73.2mm, 2.88″ | 101.3mm, 3.99″ |
10 | 70.7mm, 2.78″ | 100.8mm, 3.97″ | 76.7mm, 3.02″ | 67.2mm, 2.64″ | 97.2mm, 3.83″ |
11 | 69.5mm, 2.74″ | 100.8mm, 3.97″ | 75.1mm, 2.96″ | 66.3mm, 2.61″ | 97.2mm, 3.83″ |
12 | 64.4mm, 2.53″ | 94.7mm, 3.73″ | 72.8mm, 2.86″ | 65.1mm, 2.56″ | 92.2mm, 3.63″ |
13 | 62.9mm, 2.47″ | 91.0mm, 3.58″ | 68.8mm, 2.71″ | 60.7mm, 2.39″ | 87.7mm, 3.45″ |
14 | 59.7mm, 2.35″ | 86.4mm, 3.40″ | 67.7mm, 2.67″ | 57.9mm, 2.28″ | 86.8mm, 3.42″ |
15 | 58.1mm, 2.29″ | 83.9mm, 3.30″ | 65.6mm, 2.58″ | 56.8mm, 2.24″ | 83.0mm, 3.27″ |
16 | 55.7mm, 2.19″ | 80.4mm, 3.16″ | 60.4mm, 2.38″ | 55.9mm, 2.20″ | 77.6mm, 3.06″ |
17 | 54.1mm, 2.13″ | 78.0mm, 3.07″ | 58.2mm, 2.29″ | 52.1mm, 2.05″ | 75.0mm, 2.95″ |
18 | 54.1mm, 2.13″ | 78.0mm, 3.07″ | 58.2mm, 2.29″ | 52.1mm, 2.05″ | 75.0mm, 2.95″ |
19 | 50.5mm, 1.99″ | 75.5mm, 2.97″ | 56.4mm, 2.22″ | 49.7mm, 1.96″ | 73.1mm, 2.88″ |
20 | 50.5mm, 1.99″ | 73.3mm, 2.89″ | 56.4mm, 2.22″ | 49.7mm, 1.96″ | 73.1mm, 2.88″ |
21 | 48.3mm, 1.90″ | 69.5mm, 2.74″ | 55.1mm, 2.17″ | 49.1mm, 1.93″ | 69.2mm, 2.72″ |
Measure the diameter of your glasses.
Add something for two fingers.
Add something for the inebriation of the owner of the fingers.
Suggestion: ≤6 on /A4
; ≤14 on /A3
; ≤9 on /USLegal
; ≤6 on /USL
; ≤14 on /Tabloid
.
But sometimes table space is a binding constraint, in which case there must be less paper area, hence smaller circles.
The images show the packings for the diameters ≤ 4½″ ≈ 114mm, down to slightly below 3″ ≈ 76mm.
PaperType (s) |
Examples |
---|---|
/A4 /A3 Height: 297mm Widths: 210mm 420mm |
![]() ![]() |
/USLegal Height: 8½″ Width: 14″ |
![]() |
/USL /Tabloid Height: 11″ Widths: 8½″ 17″ |
![]() ![]() |
The Names
are shown on each Glasses page.
Whether a name is shown at the bottom of the page is controlled by NamesShowBottom
, being an array of Booleans the same length as GlassesOnSheets
, with default elements of true
.
And likewise at the top of the page, NamesShowTop
, with default elements of false
.
Each is shown in the font NamesFont
, at size not exceeding and usually equallingNamesFontSize
, but wiggled smaller if neccessary to avoid the circles.
Some extracts from the code used to make some of examples on this page.
/ThePortForumIconPlacement /None def
/VoteRecorders false def
/CorkDisplayNumCopies 0 def
/NeckTagsNumCopies 0 def
/TastingNotePagesNumCopies 0 def
/DecantingNotesNumCopies 0 def
/TitlesFont /LucidaGrande-Bold def
/CircletextFont /LucidaGrande-Bold def
/HeaderFont /LucidaSans-TypewriterBold def
/FontSizesRatioTitlesMin 999 def
/CircletextsMinNumSpacesBetween 1.5 def
/CircletextFontSize 12 def
/InlineTitles false def
/WaterBoxes /None def
/ShrinkRadii /NotAtAll def
/MaxRadius 9999 def
/OutputLogToPage false def