○–○––○–○ blog

Matthew R. Richardson’s articles on topics such as web programming and recreational math.

68f - Ornamental Box-drawing

2025-10-19T22:11:11-04:00

During this past summer and my last semester at UMass I read a few good books:

Tall Trees and Wild Bees
Stewart Coffin writes about growing up in Amherst.
The Last Man on the Moon
Astronaut Eugene Cernan's biography that he wrote with coauthor Don Davis.
Wise Blood
My suggestion for the Page to Screen book club, a novel by Flannery O'Connor.
As I Lay Dying
1930 novel by William Faulkner, set in Mississippi.
Hadji Murád
Leo Tolstoy's story as translated by Aylmer Maude.

Also during the semester I wrote an SVG to stylize the initials of the recreational math club. It's supposed to look similar to Escher's Circle Limit series, and is based on the surface of a mucube with a repeating pattern. The thinly outlined quadrilaterals are distorted square faces.

Circular graphic featuring the letters R M C and resembling an order-6 square tiling of the hyperbolic plane.

Granjon Boxes

I started working on a font which will recreate some of the typographical ornaments designed by Robert Granjon. A few years ago, in an essay by Elliot Offner, I was introduced to the following set of six pieces of moveable type which can serve as the building blocks for complex printed arabesques.

Six decorative characters printed to a binary bitmap.

Offner published The Granjon Arabesque, which is a book showcasing thirty arrangements pressed using these characters. The glyphs that I've added to the Granjon Boxes font so far are based on photos I took of the book's pages.

The arrangement of Unicode box-drawing characters above is my recreation of a design featured on page 105 of Granjon's flowers by Hendrik D. L. Vervliet. The characters were traced in Inkscape, creating an SVG file, which was converted to WOFF2 using FontForge. The 2025 version of Granjon Boxes contains the characters ┑┘┖┏╺╸╷╻╴╶╹╵┝┫┬┰┨├┻┷ and the space character U+20. The fonts directory on this website will include the latest version licensed under the SIL Open Font License.

If you're reading this on my website, you should be able to click on the arrangements and reveal the typical box-drawing character designs by displaying them in the Kreative Square font.

These designs have been digitized before. The much more comprehensive font Graveur by Juanjo López is based on the work of Robert Granjon and also includes some of the same decorative characters. Another book of arrangements, Kleines Spiel mit Ornamenten by Max Caflisch, was digitized and translated from German to French by Jacques André and published online: Petits jeux avec des ornements.

Window Painting

Photo of a geometric floral window painting.

677 - URI Encoding

2024-12-29T22:35:12-05:00

With the book club this past semester I read Daphne du Maurier's Rebecca, Gerald Durrell's My Family and Other Animals, and most of Tony Mendez's Argo. I also read Cormac McCarthy's All the Pretty Horses.

Percent-Encoding Guide

According to STD 66 RFC 39861, the characters of the string !#$&'()*+,/:;=?@[] could have a special meaning in a URI and are reserved. The ASCII alphanumeric characters and those contained in -._~ are unreserved. Any character outside of these two sets must be percent-encoded before inclusion in a URI. This is what the JavaScript encodeURI() function does, except that it also encodes the square brackets [], which were not yet included in the set of reserved or unreserved characters when the superseded RFC 2396 was written.

The unreserved characters can always be left unencoded, but we may need to encode a subset of the reserved characters.2 This subset depends on the URI scheme being used and where in the URI the characters are. The function encodeURIComponent() encodes everything except for the characters of !'()*, which might not need to be encoded as they weren't yet reserved in RFC 2396, and the unreserved characters. This means thirteen of the reserved characters are encoded, but we can encode a smaller subset in the two following cases.

Data URIs

RFC 2397 states that the main content section of a data URI will consist of zero or more urlchar characters, and that these characters are defined in RFC 2396. The definition is restated below as 2396-uric, and allows any percent-encoded or unreserved character, in addition to all but #[] of the reserved characters.

unreserved = '-' | '.' | [0-9] | [A-Z] | '_' | [a-z] | '~'
2396-reserved = '$' | '&' | '+' | ',' | '/' | ':' | ';' | '=' | '?' | '@'
previously-unreserved = '!' | ''' | '(' | ')'| '*'
3986-reserved = '#' | '[' | ']' | 2396-reserved | previously-unreserved
hex = [0-9] | [A-F] | [a-f]
escaped = '%' hex hex
encodeURIComponent = unreserved | previously-unreserved | escaped
2396-uric = 2396-reserved | encodeURIComponent
encodeURI = '#' | 2396-uric

This more formal description of the character sets shows that every character in the output of encodeURI() will either be the number sign U+23 or a urlchar character. This means we can call encodeURI(), then replace all occurrences of # with %23, and the output will be valid within a data URI.

The code below shows how an SVG data URI might be constructed using JavaScript.3

const rectSVG = String.raw`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
	<rect fill="#69E" x="20" y="20" width="15.2" height="87"/>
	<text fill="Gray" x="19" y="19">\o/</text>
</svg>`;
const rectDataURI = 'data:image/svg+xml,' + encodeURI(rectSVG).replaceAll('#', '%23');

The resulting string is data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20128%20128%22%3E%0A%09%3Crect%20fill=%22%2369E%22%20x=%2220%22%20y=%2220%22%20width=%2215.2%22%20height=%2287%22/%3E%0A%09%3Ctext%20fill=%22Gray%22%20x=%2219%22%20y=%2219%22%3E%5Co/%3C/text%3E%0A%3C/svg%3E.

The WHATWG has published a section on data URLs. If you write a valid URL string, it will be converted to a URL record by the URL parser before being processed as a data URL. This processing discards the fragment component of the URL, which begins after U+23 (#), so leaving a number sign unencoded will cut the data short. The rest of the valid URL string may include the following parts: data:, URL-path-segment string, U+2F (/), U+3F (?), URL-query string. All of these parts are formed from URL units, which is a set of characters equal to 2396-uric plus most non-ASCII Unicode characters. The fact that the URL parser splits a data URL into parts has no effect on the data. You can leave forward slash and question mark delimiters unencoded, as the parts will be rejoined by the URL serializer during data URL processing.

In this case, both the RFC 2397 and WHATWG definitions of validity treat ASCII characters in effectively the same way. The difference is that the WHATWG allows unencoded non-ASCII.

Query Strings

From RFC 3986 Section 3.4:

The query component is indicated by the first question mark ("?") character and terminated by a number sign ("#") character or by the end of the URI.

Continuing the description of character sets from the Data URIs section, we can add the definition for URI query components as given in RFC 3986.

sub-delims = '!' | '$' | '&' | ''' | '(' | ')' | '*' | '+' | ',' | ';' | '='
3986-pchar = ':' | '@' | unreserved | escaped | sub-delims
query = *( '/' | '?' | 3986-pchar )

It turns out that the characters allowed within the query are exactly the 2396-uric characters.

A method to store data within the query is not specified in RFC 3986, but usually the characters &+= have special purposes. By reading the query contents with something like the JavaScript URLSearchParams interface, you interpret the query string as a set of key=value pairs. These pairs are separated by U+26 (&) ampersands, and U+2B (+) plus signs are used throughout the string to represent spaces. See the WHATWG's definition of the application/x-www-form-urlencoded parser for an exact description of this process.

The equals sign U+3D (=) needs to be encoded within the key portion of a pair, but not in the value portion, as only the first equals sign separates the two parts. Encode the data as you would for a data URI, then handle these three special characters, and as a final step we can change the encoding of U+20 spaces from %20 to +.

const style = `background-image:url("${rectDataURI}");color-scheme:light dark`;
const vertices = '[[1,0],[0.58,0.58],[0,1],[-0.58,0.58],[-1,0],[-0.58,-0.58],[0,-1],[0.58,-0.58]]';

function encodeQueryValue(val) {
	return encodeURI(val).replace(/[#&+]|%20/g, char => ({
		'#': '%23',
		'&': '%26',
		'+': '%2B',
		'%20': '+'
	}[char]));
}

const query = `?v=${encodeQueryValue(vertices)}&i=${encodeQueryValue(style)}&h=6`;
const mirrorPolygonURI = 'https://home.6t.lt/66c/mirror-polygon.svg' + query;

The above code generates the URI https://home.6t.lt/66c/mirror-polygon.svg?v=%5B%5B1,0%5D,%5B0.58,0.58%5D,%5B0,1%5D,%5B-0.58,0.58%5D,%5B-1,0%5D,%5B-0.58,-0.58%5D,%5B0,-1%5D,%5B0.58,-0.58%5D%5D&i=background-image:url(%22data:image/svg%2Bxml,%253Csvg%2520xmlns=%2522http://www.w3.org/2000/svg%2522%2520viewBox=%25220%25200%2520128%2520128%2522%253E%250A%2509%253Crect%2520fill=%2522%252369E%2522%2520x=%252220%2522%2520y=%252220%2522%2520width=%252215.2%2522%2520height=%252287%2522/%253E%250A%2509%253Ctext%2520fill=%2522Gray%2522%2520x=%252219%2522%2520y=%252219%2522%253E%255Co/%253C/text%253E%250A%253C/svg%253E%22);color-scheme:light+dark&h=6.

If you want to encode the whole query string at once, instead of individual values, then the reserved characters to be encoded are #+[]. You can't encode all ampersands and equals signs as they may be present and acting as delimiters. Any U+26 (&) or U+3D (=) characters intended as data would be manually encoded if necessary, and all of this encoding must be done after using something like encodeURI() to avoid double encoding.

Just as with data URLs, you can leave most non-ASCII characters unencoded in the query component while still complying with the WHATWG's rules for writing URLs.

Endnotes

As of writing, there is an open issue on the WHATWG URL Standard's GitHub page in support of unescaped square brackets. The definition of a valid URL string may change in the future, as some ASCII characters are frequently used in URLs despite being disallowed.

The URL Specification by Alwin Blok is a condensed description of URLs which I found helpful.

I also wrote a webpage comparing encoding methods for data within a query value.

End of 2024 Changes

66f - Square Mesh Navigation

2024-09-27T15:01:38-04:00

This is the content currently featured on my website's home page. I may edit the About Me section in the future, but the Number Space description I wrote in early 2023 should stay the same.

Collage visible through two rectangular cutouts.

I put this collage together in November 2021, using material related to books published by the University of Massachusetts Press.

66c - Death and the Compass

2024-08-27T22:07:34-06:00

I first learned about Jorge Luis Borges and his short story Death and the Compass in 2022 while reading George Fayen's essay in the book Patterns of Symmetry. In July 2024 I read the story and discussed it with a book club run by my school's library.

The Page to Screen Book Club

After finishing Terry Pratchett's Going Postal, an eight day story about a conman turned postmaster, we read Death and the Compass and watched the short film adaptation Spiderweb directed by Paul Miller.

The first sentence of the story, as translated by Donald A. Yates, is: Of the many problems which exercised the reckless discernment of Lönnrot, none was so strange—so rigorously strange, shall we say—as the periodic series of bloody events which culminated at the villa of Triste-le-Roy, amid the ceaseless aroma of the eucalypti.

I read this in the book Labyrinths, and we noted that translator Anthony Kerrigan worded it as the exercise of daring perspicacity in the text other club members read. The line came up again as the first narration of Spiderweb: Of the many unusual cases which challenged the powers of the celebrated investigator Erik Lönnrot, none was as bizarre as the periodic series of events which came to a conclusion at the desolate mansion of Triste-le-Roy amid the heady aroma of eucalyptus.

I liked the story, its half hour adaptation, and the cover of the copy of Labyrinths I checked out at Neilson Library:

Labyrinths 2007 cover art.

A slightly different version of the cover was at one point featured on photographer Jason Fulford's website, but can now be found at isfdb.org (jpg). The mirrored pentagon inspired me to put together a little polygon reflection project I had been thinking about for a while.

SVG Mirror Polygon

link

This interactive polygon placement tool is an SVG image that edits itself. Click to place a polygon, and the next click will place a reflection of the polygon across one of its sides. The most recently placed polygon is selected, unless you shift-click to select another one. Shift-click the background to select none, and press backspace or delete to remove the selected polygon. Use control-C or command-C to copy a data URI containing the current image.

The following parameters listed with their default values can be edited using a query string:

borderColor
CanvasText
decimalPlaces
6
fillColor
red
heightOfPolygon
10
inlineStyle
background-color:Canvas;color-scheme:light dark
selectedWidth
.5
unselectedWidth
.1
vertices
5

Set the values using the first letter of the parameter name. The default values for the polygon border color and the background color are system colors. The width variables adjust the size of the polygon's border. The vertices parameter can either be a number 3 or greater, or a 2D array of points defining any polygon. Convex polygons work best, and if you only specify the number of vertices then a regular polygon will be used.

For example, use these settings for a silver colored pentagon with one right angle: f=silver&h=12&v=[[0,0],[0,1],[0.74,1.31],[1.31,0.74],[1,0]]. I abbreviated the anchor text to include less digits, and some characters should be percent-encoded.1 This is an octagon that fits well inside a regular pentagon: f=%23000&h=14&v=[[1,0],[0.58,0.58],[0,1],[-0.58,0.58],[-1,0],[-0.58,-0.58],[0,-1],[0.58,-0.58]]. The fill color %23000 means #000 or the color black. One more example: i=background-image:url("https://apollojournals.org/...").

An additional six parameters were added in 2026:

animationFoldAngle
0
closeAnimation
4s ease-in-out alternate infinite close
marginSize
keep
placeInitial
no
transformScene
(empty string)
xlinkHrefs
false

If a nonzero number of degrees is provided for the fold angle, then the SVG output to the clipboard will be animated with CSS transforms. If the margin size is not keep, then it is a number, and the view box of the output SVG will frame the polygon arrangement. You can automate the initial polygon placement by passing a 2D point as two numbers separated by a comma. A transform can be applied to the output SVG, but the syntax of the transform string depends on the other parameters. If there is no animation, use a SVG transform attribute form like rotate(90). If there is an animation, use a CSS transform property form like rotate(90deg). If there is animation and a custom margin, use a form compatible with both like matrix(0,1,-1,0,0,0). Finally, insert xlink:href attributes into the output <use> elements by passing true or 1.

Adding the fold animation feature allowed me to create this polyhedral net SVG based on the great dodecahedron. The settings used were a=116.565&c=5s+ease-out+alternate+infinite+close&f=%23fb58&m=3. And here is an example showing the rest of the new parameters in use: m=0&p=.5e2,50&t=scale(1.732,1)&v=4.

Footnotes

Over my summer break, which ends in a week, I walked by Buckminster Fuller's gravestone on the Bellwort Path at Mount Auburn Cemetery.

The book cover image above was accessed through the Open Library covers API.

Embedded List

               46 45 44 43 42 41 40
               47                39
               48                38
               49                37
               50                36
                                 35
                                 34
               27 28 29 30 31 32 33
21 22 23 24 25 26
20
19
18             01 02 03 04
17 16 15 14 13 12       05
               11       06
               10 09 08 07