Skip to content

Latest commit

 

History

History
330 lines (242 loc) · 21.6 KB

page_level.md

File metadata and controls

330 lines (242 loc) · 21.6 KB

Page-Level Controls

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


Headers and Footers

Headers and footers can be added to the page. Typically these will hold a date, a location, a title, and perhaps some branding—the author includes URLs.

There is a little intricacy, to allow tastings split into multiple sessions to have different headers and footers in each session. The parameters doing most of the work are HeadersLeft, HeadersCenter, HeadersRight, and their Footer… equivalents, though using all of six of these text positions would be very cluttered. Each is an array of even length.

First, the default simple case: make the first item 0, and the second item the required compound string. Usually elegance is enhanced by having left and right headers of approximately equal length.

To have different headers on different pages, each array consists of alternate items as in the simple example: number; compound string; number; compound string; …. The number is a page ordering, with the immediately following compound string to be used only if the page ordering is at least this (unless over-ruled by a larger number that is still not bigger than the page ordering).

/HeadersLeft [
	0   (The Pub, Saturday Afternoon)  % If PageOrdering >=0 and <10
	10  (The Hotel, Saturday Evening)  % If PageOrdering >=10
] def

Headers are shown in font HeaderFont, at size HeaderFontSize, with the baseline of the characters a distance HeaderBaselineFromPageTop from the top of the page (which should be inside the top margin). Likewise FooterLeftText, FooterCenterText, and FooterRightText are shown in FooterFont at size FooterFontSize, with the top of the footer a distance FooterTopFromPageBottom from page bottom (which should be inside the bottom margin). Very rarely one might want the centre heading non-centred (e.g.). This can be controlled with HeaderFooterCenterX, which is the x position of the centre of the relevant item of HeadersCenter and of FootersCenter.

Also see /SuppressNonRightOrnaments and /SuppressNonLeftOrnaments, being flags usable within PackingStyles.

GlassesOnSheets and GlassesOnTastingNotePages

More than six or seven glasses would be too many for one sheet of /A4 or /USL. If the PaperType is one of these, there must be ≥2 sheets.

Consider:

/GlassesOnSheets [
	[ 0 1 2 3 4 ]
	[ 5 6 7 8 9 ]
] def

PostScript arrays start at zero, so if Titles etc are of length n then the integers must be ≥ 0 and ≤ n−1. The example has two pages.

Because this is the standard pattern, there is help, in the form of GlassesOnSheetsMaxPerSheet. The default assignment of GlassesOnSheets uses this number, spreading glasses evenly over sheets subject to this constraint. And the default value of GlassesOnSheetsMaxPerSheet itself depends on PaperType:

PaperType Size, mm Size, inches Default value of
GlassesOnSheetsMaxPerSheet
Diameter if ‘full’
& margins=24pt
/A4 210 × 297 ≈ 8.27 × 11.69 6 92.7mm, 3.65″
/USL 215.9 × 279.4 8½ × 11 6 88.6mm, 3.49″
/USLegal 215.9 × 355.6 14 × 8½ 9 78.7mm, 3.10″
/A3 420 × 297 ≈ 16.54 × 11.69 14 86.4mm, 3.40″
/Tabloid 431.8 × 279.4 17 × 11 14 86.8mm, 3.42″

/Tabloid has the synonym /USL2 (issue 159).

But the PaperType can vary. E.g., not unusual for 17–20 glasses:

/PaperType {SheetNum 0 eq {/A3} {/A4} ifelse} def

With that one might define:

/GlassesOnSheets [
	[  0 1 13 {} for ]
	[ 14 1 19 {} for ]
] def  % /GlassesOnSheets

The PostScript loops save the small effort of typing and checking consecutive integers: ʏᴍᴍᴠ.

Tasting-note pages are configured similarly, with the integer GlassesOnSheetsMaxPerTNSheet helping make a default value for GlassesOnTastingNotePages.

BackgroundTexts

Tastings typically have a theme, something shared by most of the wines. It is possible to add a background text, scaled to fit the whole page, such as “S”, “RV”, or “85”. This is activated by setting BackgroundTextsGlasses to true. Then BackgroundTextsGlassesTexts is an array of the same length as GlassesOnSheets, containing the strings to be shown on glasses pages. The strings should be short: one or two characters is recommended, and are shown in font BackgroundTextsFont.

The text is aligned with BackgroundTextsAlignmentVertical being one of /Middle, /Bottom, /Top; and BackgroundTextsAlignmentHorizontal being one of /Centre, /Left, /Right. These are orientated with the text: so if the text is rotated, /Top means towards the left of the page.

Can the text fill the whole page? BackgroundTextsSpaceX and BackgroundTextsSpaceY can be /PageLessMargins or /PageWhole, and ignored not on glasses pages, /CirclesEdges or /CirclesCentres. These are orientated with the page. The default of /CirclesEdges copes well if the used element of PackingStyles is, say, /RightSide.

Example 1977 tasting, with BackgroundTextsGlasses

Superfluous

The code chooses separate x and y sizes, each as large as possible, subject to the following. The ratio of the vertical and horizontal font sizes, y÷x, must lie between BackgroundTextsSquooshMin and BackgroundTextsSquooshMax. For non-squooshed text, generally recommended, set both to 1. This precise fitting can be done by inserting into the compound string some {±x Kern}. The amount of extra kerning required is computed, and appears in the output log — typically the last page of the PDF.

BackgroundTextsGlassesSameSizeIfAllOf determines whether the background texts on different pages are compelled to be the same size. They are if all the ‘conditions’ specified are true. Valid conditions are /False (always false, so each is fitted separately); /OnSheetWithSameNumberGlasses (obvious meaning); /OnSheetWithSameRadius (ditto); /SamePageOrdering (ditto); /SamePaperSize (ditto); /TextSameLength (ditto); and /RadiiShrunkToBeSame (referring to ShrinkRadii).

The x and y font sizes may not exceed BackgroundTextsFontSizeMaxX and BackgroundTextsFontSizeMaxY, which default to large. Like CircletextsMaxCopies, when each of these is used the ‘current font size’ is on the stack, which allows the following code.

/BackgroundTextsSquooshMin 0.25 def  % Allow very wide

/BackgroundTextsFontSizeMaxX
{
	dup                               % Value to which compared
	PageWidth MgnL MgnR add sub       % Usable page width
	dup Radii SheetNum get 2 mul sub  % ditto, minus 2*radius
	exch div mul   % Used X font size reduced by the ratio,
	               % so level with centres of L-&-R edge circles
} bind def  % /BackgroundTextsFontSizeMaxX

BackgroundTextsGlassesVerticalMiddling determines whether the vertical alignment is determined jointly or separately, much like the VerticalMiddling… parameters. BackgroundTextsGlassesVerticalMiddling must be one of /MatchAll, /MatchSamePaperSize, or /MatchNone. The bounding path of the text is constructed and then BackgroundTextsGlassesPaintCode is executed. The default clips then strokes.

Extracts from code that made the example:

/TitlesFont /RobotoCondensed-Bold def
/BackgroundTextsFont /Roboto-Bold def

/BackgroundTextsGlasses true def
/BackgroundTextsGlassesTexts [
	[(8) {-0.00630915 Kern} (5)]  % First attempt used (85); kern amount in output log.
] def

% Strenghened from default because of conversion to low-resolution bitmap.
/BackgroundTextsGlassesPaintCode {clip 6 setlinewidth 0.9 setgray stroke} def

H₂O boxes

Particularly at a tasting of a sweet fortified wine, drinking water is important. By default the glasses pages have a set of little glass icons with which to record water consumption: check one for each glass of water downed. Even better, use a diagonal slash for a half glass and a cross for a whole.

This is activated by the parameter WaterBoxes, which takes one of the values: /Glasses (so the water boxes appear on the glasses pages); /TastingNotes (on the tasting-note pages); /Both; or /None. The number of boxes is WaterBoxesNum, each of which is no bigger than WaterBoxesSizeMax on each side, the gap÷box ratio being WaterBoxesGapProportionSize.

On glasses pages boxes should appear on the lower-right of the page for right-handed drinkers, and lower-left for left-handers. Tasters’ handedness is set by LeftHanders, an array of the text of the name of known left-handers, whether or not at this tasting. If a name within Names is duplicated, and both are to be left-handed, that name must appear twice within LeftHanders.

Superfluous

If there are multiple pages of glasses, by GlassesOnSheets, then on which of these pages should water boxes they appear? For simplicity, WaterBoxesOverrideShowEverySheet being true overrides the complexity in the next paragraph, putting water boxes on every sheet (of glasses or of tasting notes, per WaterBoxes).

If WaterBoxesOverrideShowEverySheet is false, the presence or absence is controlled, for the right handers, by the array of booleans WaterBoxesShowRight; and for the lefties by WaterBoxesShowLeft. WaterBoxesShowRight and WaterBoxesShowLeft must be the same length as GlassesOnSheets. The default values of WaterBoxesShowRight and WaterBoxesShowLeft refer to PageOrderingGlasses: WaterBoxesShowRight defaults to true for the last page in each ‘session’, and WaterBoxesShowLeft for the first. There is an analgous parameter WaterBoxesShowTN, used for water boxes on the tasting-note sheets.

Droplets

Droplets

Droplets are a decorative amusement, flowing around the page as if attracted or repelled by spinning charges. Activate with the Boolean Droplets.

The array DropletsCharges is of a length that is a multiple of four, as follows.

  • 0: An integer, a value of SheetNum for this charge, or /All meaning all sheets.
  • 1: A location: this can be:
    • an integer, the centre of the WithinPage circle on that sheet; or
    • an array of integers [WithinPage0, WithinPage1, …], the location being the average of the centres of these WithinPage circles; or
    • an array of length two, [x y], with x and y both non-integer float, in which [0.0 0.0] is bottom-left of the page (but locations at ±huge can cause overflow: experiments suggest it’s safe with both |x| and |y| ≤ 10⁶ ≈ 353 metres).
  • 2: A numeric charge, droplets flowing away from −ve charges and to +ve charges (so signed like the gravitational effect of mass).
  • 3: A numeric spin; with +ve charge and +ve spin the droplets approach clockwise (at a distance from a lone charge of spin points, the angle of the droplets is 45°).

Starting locations of the drops have a distance apart of DropletsAverageSeparation adjusted by a random fraction of DropletsAverageMaxTweakPlusMinus, and path length is DropletsPathLength.

There is a little humour in having a small proportion of the droplets going backwards; a small proportion will be noticed only slowly by the uninitiated, increasing the humour. (In the example page are seventeen: seek! Or peek.) That proportion is controlled by DropletsProportionBackwards, which obviously should be ≥0 and ≤1. The default value is 0.0204 ≈ 1/49.

The gray and width of each path is controlled by DropletsOuterGrayEnd, DropletsOuterGrayStart, DropletsOuterWidthEnd, DropletsOuterWidthStart, DropletsInnerGrayEnd, DropletsInnerGrayStart, DropletsInnerWidthEnd, and DropletsInnerWidthStart. (But if DropletsOuterGrayEndDropletsOuterGrayStart, or likewise …Inner…, the PDF can be multiple megabytes larger.) There are also Boolean parameters PrePourShowDroplets, NeckTagsShowDroplets, and StickyLabelsShowDroplets.

/Droplets true def

/DropletsCharges [
	/All  {[MgnL Radii SheetNum get add  PageHeight MgnT sub Radii SheetNum get sub]}   2  120  
	/All  {[PageWidth MgnR sub           MgnB Radii SheetNum get 2 mul add         ]}  -1    0
] def

If SideBySideGlassesTastingNotes is true, the Boolean Droplets_SideBySide_UnderTNs controls whether the Droplets span just the glasses, or also the TNs.

Separating flights within a page

It is often natural to split a tasting into ‘flights’ of a few wines. If there is lots of table space, and five wines per flight, then this can be arranged as one flight per /A4 or /USL page. But sometimes within-page division is necessary. And if table space is tight, 14 on /A3 is more efficient than 12 on 2×/A4, making more useful the possibility of within-page division.

This is enabled by the Boolean parameter FlightSeparations; the hard work of specifying the lines happening with FlightSeparationLines.

FlightSeparationLines is fiddly. If one wine is added or taken away then that page’s element of FlightSeparationLines might need to be completely reworked. Hence it is recommended that division into flights be saved until late in the planning.

Let’s start with an example, to give a sense of the range of what can be done, and also (part of) the code by which made.

Example FlightSeparations

/PackingNestingColumnMajor true def

/FlightSeparations true def

/FlightSeparationLines
[
	[  % + SheetNum = 0
		% Straight lines and sharp corners
		[ [/Bottom 2 5]  [2 5]  [1 4]  [3 4]  [3 6]  [/Top 3 6 +0.3] ]
		[ [/Bottom 5 7 +0.3]  [5 7]  [4 7]  [4 6]  [3 6]  [/Top 3 6 +0.3] ]
		[ /Closed  [/Bottom 7 10 -0.3]  [7 10]  [9 10]  [9 12]  [8 11]  [/Right 11 +1.0]  [/Bottom 13 +1.0] ]
	]  % - SheetNum = 0
	[  % + SheetNum = 1
		% Gentle softening of corners, without clinging to the circles
		[ [/Bottom 2 5]  /Arc  [2 5]  /Arc  [1 4]  /Arc  [3 4]  [3 6]  /Arc  [/Top 3 6 +0.3] ]
		[ [/Bottom 5 7 +0.3]  /Arc  [5 7]  /Arc  [4 7]  /Arc  [4 6]  [3 6]  /Arc  [/Top 3 6 +0.3] ]
		[ /Closed  [/Bottom 7 10 -0.3]  /Arc  [7 10]  /Arc  [9 10]  /Arc  [9 12]  [8 11]  [/Top 8 11]  [/Right 11 +1.0]  [/Bottom 13 +1.0] ]
	]  % - SheetNum = 1
] def  % /FlightSeparationLines

FlightSeparationLines is an array nested about four deep. FlightSeparationLines is of the same length as GlassesOnSheets. Each item of FlightSeparationLines is an array, each of which holds line descriptions, as many as there are lines on that page. Each line description is an array, pieces of which can be as follows.

  • The first, and only the first, item of a line description may be /Closed, which specifies that the line is a closed loop.
  • The typical piece is [a b], a and b being integers ≥0, and strictly less than the number of circles on the page. The line runs through the mid-point of the glass positions a and b, the line being perpendicular to the line connecting the centres. So if the circles touch then the line segment touches their joint tangent. Order doesn’t matter: [a b] and [b a] are equivalent. The integer a can be replaced with [xa ya] (making [[xa ya] b]), and likewise b with [xb yb].
  • Between two pieces the non-array item /Arc causes the sharp corner between two straight lines to be replaced with a soft arc having radius FlightSeparationsArcProportionRadius × the radius of the circles.
  • The last or first non-/Closed piece may be [/Left …]. This goes to the far left of the page, at a y specifed by the ‘…’, which is an average of at least one absolute level, perhaps ± some real number of radii. Absolutes can be /Bottom (with obvious meaning); /Top (ditto); or an integer, in which case the y is that of the centre of that circle number. Or several integers, in which case the y is the average of the centres of those circle numbers. Optionally there may also be a real, that number of radii being added to the average. So [/Left 0 3 -0.5] is a horizontal line to the left of the page, at a y value of the average of those of circles 0 and 3, minus half a radius. This can be thought of as Average[Circle 0’s bottom, Circle 3’s centre]. Hence the integer 1 and the real 1.0 are very different, the former referring to the centre of circle 1, the latter to an offset of +1.0 radii.
  • Likewise [/Right …].
  • And [/Top …] and [/Bottom …], for which the value ‘…’ is the x, integers referring to that circle’s x centre; allowed components including /Left and /Right. If there are /Arcs, it can be elegant for /Left /Right /Bottom /Top pieces to align with the edge of the curves. The distillation log lists offsets, in radii, which might be elegant.

In the PDF of examples the header shows that page’s item of FlightSeparationLines (i.e., FlightSeparationLines is an array one deeper than the header). If using FlightSeparations it is strongly recommended that these examples be examined and appropriate parts used as a starting draft.

The path can be stroked, clipped or filled by the code FlightSeparationPaintCode, which has a default stroke that is complicated but elegant. Use of fill is discouraged, as it impedes comparison of colours of the wines; use of clip is likely to require non-trivial PostScript programming (e.g.). If FlightSeparationPaintSeparately then FlightSeparationPaintCode is invoked after each line, the variable FlightSeparationLineNum being set to the appropriate integer ≥0. If FlightSeparationPaintSeparately not, then all the lines on the current page are drawn, after which is the single call of FlightSeparationPaintCode. The former is better if the format is not constant; the latter preferred if overlapping or crossing lines are to be double-stroked.

There is a bug in (at least some versions of) Adobe Distiller, such that if FlightSeparationPaintSeparately is false and there are multiple lines on one page (⟺ FlightSeparationLines SheetNum get length ≥ 2), the apparent ends of some lines are controlled by setlinejoin rather than setlinecap. It is a small aesthetic imperfection, but an aesthetic imperfection nonetheless. The problem can be avoided if no line begins where the previous one ended, achievable by reversing the direction of individual lines, or by re-ordering the set of lines.

The Port Forum Icon The Port Forum Icon The Port Forum Icon

The majority of the placemats created by the author are for tastings arranged via www.ThePortForum.com. To control the placement of a ThePortForum icon the parameter ThePortForumIconPlacement is set to one of /None, /LowerLeft, /LowerRight, /UpperLeft, /UpperRight, /LowerNonWaterBox, /UpperNonWaterBox or /UpperNonWaterBox, the last three evaluating to left or right according to the side on which the WaterBoxes are placed. Alternatively, for multiple icons on the page, ThePortForumIconPlacement can be an array of any of the non-/None of these possibilities.

Icons on the tasting-note pages are controlled by ThePortForumIconTastingNotePlacement, with permitted values /None, /LowerLeft, /LowerRight, /UpperLeft, /UpperRight, /LowerNonName, /UpperNonName and /UpperName. Whether or not the icons are shown in colour is set by the boolean ThePortForumIconColour.

These parameters are mentioned in issue 97, re other websites’ icons.