 OK, we're going to start. It's already half past. So it is my pleasure to introduce the last but not least keynote speaker of PylonDinium 2019. We have Brandon Rhodes with us here. He's coming all the way from Ohio, which we are very grateful for. Thank you very much. He's a Python programmer. He was a Python US Chair. And I don't want to say you're more of your time because I know you have lots of interesting things to say, so over to you, Brandon. Thank you. And I want to thank the organizers. I want to thank the organizers very much for inviting me. It's been at least half a decade. No, yeah, half a decade, been five years or more since I first got to visit London. And I've really enjoyed my visit. And I've really enjoyed the energy of the conference. And especially how compared to other conferences I've been to in Britain, how many different places you are all from. I've enjoyed meeting you. And I'm here to talk about typesetting with Python. When I was growing up, there was a book on my father's bookshelf. He was an electrical engineer. It attracted me because of the interesting design of its cover, which showed an ancient way of trying to design the letter S. It was by someone I'd never heard of named Donald E. Knuth. And it was called Tech and MetaFont. Not text and MetaFont, no. He named the typesetting system that he wrote after the Greek word technae. They had the same word in Greek for both a craft and an art. And so he named his typesetting system, not with the letter X, but with the Greek letter chi as its third letter, tech. It introduced me as I read through the book and he explained its workings. Introduced me to the idea of a markup language. I'd never heard of such a thing. Instead of being in a WYSIWYG editor, you just wrote plain text that you were completely in control of, and then tech read the plain text and turned it into a document. You had to learn some conventions, some commands that started with backslashes and the proper use of curly braces. But you got to write plain text without thinking much about how it would be laid out on the page and the tech system swung into action and made it look like a professionally typeset paragraph from a book. The book's lessons on how to type these .tech input files were themselves a small course in typography. You learned where to put non-breaking spaces because it turns out it is a minor typographic disaster if a title like Doctor or Mr. or Mrs. ends one line of a paragraph separated from the name on the next line. So I learned about non-breaking spaces. I learned that these four pieces of punctuation are completely different. And it is entirely wrong to use any of them for the other's purpose. The hyphen, the n-dash, the m-dash, and the minus sign look different in the case of the minus sign or at a different height on the line and have completely different purposes. You will be frequently annoyed for the rest of your life if you learn the difference. And again, it's markup language. Even though all you had was ASCII keys back in the 70s, there was a way to separately type each of those symbols into your input file. Its specialty was math type setting. Donald Knuth was a mathematician. And that was the real reason for inventing tech. When math journals stopped paying for professionals to expensively set type by hand, math papers looked so ugly that Knuth could no longer publish to survive as a mathematician. He had to remedy the situation so that he could stand seeing his equations in print. And so he took an entire year off, I believe it was 78, might have been 77, to invent tech. And so its specialty is inside of dollar signs. You have a little language that kicks in, underscore is subscript, carrot is superscript, where you can just say, well, take the sum from k equals 0 to infinity. And out comes beautifully typeset math that looked to me like something from an expensive professional math book. It was a beautiful system. He did not ignore non-math problems like breaking paragraphs into lines. Tech represents the words of a paragraph as fixed-width boxes separated by stretchy glue. Because to lay out a paragraph, you try to make the lines the same length by stretching or shrinking those spaces. This internal representation of the problem of breaking a paragraph into lines has a combinatorical problem, a paragraph within positions at which text could be split into lines, can be laid out two to the indifferent ways, depending on whether you take each break or not, how could we ever find the optimum layout? I'll bet you thought dynamic programming existed only to be a whiteboard problem in interviews. Donald Knuth actually found dynamic programming could solve that problem and provide the optimal solution for breaking each paragraph into lines In an in-squared worst case, actually usually only on the order of the number of possible breaks. So you put in plain text, it turned it into boxes and glue, and it managed to beautifully line up that right-hand side of each paragraph just like in a book. The output of tech was beautiful, but as I then some day, I eventually had a computer powerful enough to run it, I found it was difficult to control. Once you set up the parameters, layout proceeded largely outside of your control. It reminded me of a problem that I've seen recently, which is the tractor and trailer problem. Tractor has a motor and can move forward and back, and the trailers are simply passive. If you've ever tried backing up a single trailer, it can be difficult. They often today consider for factory floors and things like that. Two, three, four, trailers, if you've ever tried to back up a small toy train, you might have an idea of the difficulty here. Backing up a trailer, tractor and trailers is today an open problem in AI. You can find recent papers that talk about could we back up three trailers with an appropriate AI? Trailers are difficult to back up because the input, the motion of the tractor has an increasingly distant relationship to the motion of the int trailer. And that problem, trying to control tech sometimes felt similar, and in general I would say that frameworks in programming, and I use that term in its fully pejorative sense, frameworks often have the same problem, that you feel like there's this little opinion you've been asked for, and it can be very hard to get the form that they form's library outputs to respond to the settings that you're trying to change. Controlling tech felt similar, so I had an idea. What if instead of typesetting systems that we merely configure and then run off and do their thing, what if there were a typesetting library that left the programmer in control for difficult situations that you might want to customize? Recently, I realized that typesetting and printing a book from Python was coming within technical reach. There's now an industry called print on demand. All you need to give them is a PDF and they can send you back a custom hardcover book that is all yours. A real hardcover, case bound, and smith zone, not something just put together with glue. We now have technologies that largely replace all the things Donald Knuth had to invent on his own. We now have true type and open type. We don't have to invent the idea of vector fonts. Instead of a macro language, there are many ways to represent paragraphs and headings and plain text. I didn't even need to implement paragraph layout. Andrew Kuchling translated that boxes and glue algorithm into Python long ago and called it techlib. And we don't need to use text custom DVI output format. PDFs are now the industry standard, but I needed something to print. This is the problem with little projects that you dream about. And then I stumbled across a backup of my grandfather's essays, which after he died in the 90s, I'd backed up off of his computer. I could write the pipe setting myself in Python and have a gift to give to family members, a hardback of his thoughts and memories and history. So, all right, we're going to be re-implementing tech. What would I do differently? If I'm going to try to do layout over again, what's the thing I want to focus on? I chose a specific first goal to see if I could not just re-implement it, but do something interestingly different. What about different width columns? As you're laying out the document, that's not supported in tech. It wants each column to be the same width. As tech breaks a paragraph into lines, it doesn't even know what page the paragraph will land on, because first it splits the paragraph into lines at some width. That's a separate step from then figuring out how many lines of the paragraph fit on each page. So you can't support graphic designs that have one width on the first page of your newspaper and maybe a different column width on the inside. You can't support graphic designers that on the front page of the article have a single wide column to accommodate an image, but then on the inside have a double column. My idea was that the paragraph should ask for more space as it needs it so it learns about any width change when it asks for more space and has to cross to a new column. I wanted to see if I could get that working because that would be a real advance over what tech had been able to do. So my plan was to find a library for rendering PDF from Python, and then invent a new page layout engine. I decided to tackle them in that order. I had already used to make a, I think a program, a printed program for a conference. I'd used Report Lab. It's a PDF library in Python. And so I hooked it up to Andrew Kuchling's Tech Lib and I printed out the first paragraph of my grandfather's first essay about his great grandfather in 1800s Alabama and the result, as you can see, was a disaster. On what? Yes. We're just going to have to stop right here if this is the best that we can do. And interestingly enough, when I used this font on the web, I double checked it, the word war looked fine. Report Lab specifically as a library doesn't understand that true type and open type fonts come with a table, a kerning table that tells you that some letters need to be pushed closer together or they'll leave a gap. It didn't know about kerning, so the word war is an unmitigated disaster here. Fortunately, it turns out there was an alternative. I'd always thought that the QT library was kind of a useless way to write big bloated desktop applications, but it turns out that desktop applications need a print button. And so QT has an entire PDF rendering engine inside of it. But thank goodness, reads the metrics of a font before putting it on the page. All right, so I had my PDF library, I was moving again. The input is going to be, from however my text is stored, a list of typesetting actions, titles, headings and paragraphs, and now I simply need to design the routine that will run through them, putting them on the page. What API should the layout engine use to call each of these actions, like heading and paragraph? Well, let's start by asking, what information does an action need if we're gonna set it loose, laying out content on page? Well, I thought it would need to know the width of the text it's expected to lay out. We'll need to probably know the height of the column, so I wrap that up into a little object, and then it probably needs to know how much of the column's been used already. It needs to know the Y coordinate that it should start at as it adds more content. Each time the paragraph needs another line, it can look at the amount of letting it needs. It used to be a little sheet of lead that you put in between each line of type. Today it's just a little blank space to give a nice line height, not crowd the text too much vertically, and the font's height, and if the current Y coordinate, plus the letting it needs to add, plus the size of the font it needs to add, is going to be more than the column's height or out of room, we ask for another column. How do we ask for another column? I had several possibilities I could try here. Maybe the column itself has a method called next. Maybe we pass a layout object that can give you new columns as you ask for them. Or I could just pass a next column callable. Number of different options. I decided to pass a plain callable, and I knew it was the right decision when I did it, and that wouldn't have been true when I was a younger programmer. I knew that if all the paragraph needs is a verb, a next column that spits out new columns, that's all that I should pass it. Why? To avoid, and I decided I needed a name for it, premature object orientation. There is an old saying in computer science, premature optimization is the root of all evil. Who said that? Donald Knuth. I didn't know that till I looked it up. Premature object orientation, which is my phrase, is attaching a verb gratuitously to a noun when you don't actually need to yet. The symptom is when you see code passing an object on which a function will only ever call a single method. This is the symptom. You're given an object, why? All you ever do is call that single method. Why not just pass the verb instead? Premature object orientation couples code that needs only a verb to all the implementation details of the noun, because you receive the whole thing. And so, because of some restraint that I've learned over my career, I simply passed the callable next column that generates new space in the document. I now had a rough plan for an action's inputs. What now will an action return? Can, for example, a paragraph simply go ahead and draw on the output device. No! It turns out it cannot, because there are a number of problems which I can illustrate by talking about headings. Heading is supposed to sit atop the content of which it's the head. I guess that's why it's called a heading. It looks sort of like this. This is a paragraph laid out by my, oh, Andrew Kuchling's Tech Lib Library, called by mine. And paragraph, paragraph ends, and we have just enough room on this page for the heading and a line of text. What if there's no room beneath the heading? What if the column were a little smaller? Well, then you get a typographic disaster. If the column weren't high enough for that line, the heading would stand by itself, not heading anything. The heading, in that case, is going to need to move itself to the next column far better to have a little blank space at the bottom of a column than to have a heading that doesn't have any content beneath it. Now you might think that that's going to be easy to avoid. Can the heading simply check whether there is room for an extra line beneath it? But checking for a free line won't, alas, always work. Why? Because a paragraph might not choose to use the final line of a column because of something paragraphs do on their own. They're tricky beasts called widow and orphan detection. A single line paragraph might deign to remain at the bottom of the page, and then our heading has something to head before the text moves to the next column. But a multi-line paragraph will refuse to leave its opening line alone. Will refuse to leave it an orphan. It will look and say, I can't leave that line by itself. It's considered a sin in typography. So even if there's the space for a line, a multi-line paragraph will pull it up to the top of the next page to look better, leaving the heading stranded even though it looked like there was enough room for more content. Well, okay, if paragraphs are going to do tricky interior things, decisions about where they land, how can the heading predict whether it will be stranded alone? Well, it can either know everything about paragraphs and have inside of it all of the details about how paragraphs lay out. That doesn't sound wise or sustainable. Or I could invent a way for a heading to ask the next action to lay itself out speculatively. And just kind of ask, well, if I stay here at the bottom of the page, what paragraph will you do next? But this is going to require undo. The ability, if layout goes wrong, if I wind up stranded to back up, because the heading is going to have to add itself to the document and the following paragraph. And if it worked, if having done that, there's some lines of text beneath the heading were done. If the heading finds that it wound up stranded and alone, you're gonna have to undo the paragraph, undo the heading, start over again on the next page. That means these routines cannot be writing to the PDF manually. Layout is going to need to return an intermediate data structure that the caller can expect instead of just starting to write type all over our document. Consequence number two is that the intermediate data needs to be easy to discard. I went through several pencil and paper. I don't even try writing code on the stage of design. I tried to figure out what relationship would let me add stuff to a document plan that I could then just take away. And I was startled to discover that a very elegant data structure for it was one I had never used in Python. In Python, I used a linked list. I defined a line as not only belonging to a column, having a Y coordinate and then having some graphics that if you decide to keep the line, you draw on the page. But I gave it a previous attribute, which as I go down the page, I set each lines dot previous to the line above it. Thus creating a linked list of lines that when I need to stop and make a decision, I can run a paragraph's layout one way, look at those lines, run it another way, and the two possible futures for my document automatically share the storage, all the previous document without my having to store it twice because they both share a reference to it. A linked list lets us extend the document with any number of speculative layouts, which Python automatically disposes of as we discard them. Actions, okay, are now going to need a new argument because if they're going, if they're job, if the job of an action is now to append new lines, they need as an argument the most recently laid out line before the action goes to work. Ooh, but look, remember how a paragraph needs to know the column it's drawing in and the Y position so far? Those are two attributes of the line itself. I didn't know I'd be passing lines in as an argument, but now that I know that this gets passed in, I no longer need to separately pass in the column and the Y coordinate. The paragraph learns everything about the current context merely by looking at this line object that I didn't even pass in for its own amusement. I passed it in because it needs to append more lines but it wound up carrying almost all the information the paragraph needs. In other words, designing our return value wound up eliminating two of our input arguments. Always look for chances to simplify as you proceed with the design. It's often pleasant how things simplify as you go along. Another nice thing is the symmetry that this concept of a line provides. The line becomes a common currency that's both our argument, we get one line passed in and our return value as we return a new terminal line back out. But how will the heading action invoke the action that follows? I haven't talked yet about how the heading talks to the paragraph. And how will it tell the engine that the following action's already laid out? If the heading needs to lay out the paragraph, it needs to tell the engine, hey, I've already laid out the paragraph, don't do it over again. Now that I hand back control and return a value. Once again, pencil and paper, I looked at special callable and exception. Should these two things be co-routines that talk to each other? I took a deep breath. All right, and I looked at the reality of however I arrange the conversation between the heading and the paragraph, the real reality is that I just have a list of tuples of things that I need to put on the page. And so I thought of something kind of radical. What if I just pass the list into the heading and the current, I know that sounds crazy. You'd think I would hide it behind something. But what if in this case I just tell the heading the truth, hey, there's a list of actions, you're at position A, go to town, and when you return the concluding line from all the work you've done, tell me how far to advance my counter before continuing. Incrementing and returning the A index lets an action invoke as many subsequent actions as it needs to to compare different outcomes. So stepping back, I wrote several routines to that spec. I looked at the scans at the amount of repetition in my code that this created. Heading took actions in A. Section took actions in A, both used them. But a paragraph which doesn't care about the subsequent action also takes actions in N and A as did other simple routines. They just kept returning A plus one because I laid myself out, continue. And that seemed a bit verbose. For opinionated actions, like headings that care about what follows, it's necessary to pass actions in A so that they can get to the next thing. But simple actions that just lay themselves out and don't care about what comes next, ignore them. And so the simple actions at the bottom are receiving parameters they don't really need. How can I eliminate actions in A from simple actions that don't need them? This is a principle in programming called dry. Don't repeat yourself. And when I thought of those words, I suddenly heard the call of distant decades, the 1990s called and said from the practices back in the 90s that all in Python I can introspect functions to learn if they take actions, arguments, or not. So given a list of functions, I can just leave actions off of these argument lists and use Python's introspection abilities to know whether to pass those arguments or not. The 90s, crazy times. Oh, but then the early 2000s called. I could have a special registry because functions or objects and can be enrolled in data structures. I could have a registry for functions that don't need actions in A so I know to call them differently. The late 2000s called. I could have a decorator for functions that don't need actions in A that takes those two parameters but doesn't pass them to the function being wrapped. All I would then need to do is put that decorator in front of each of my simpler routines and I wouldn't have those extra parameters. Don't repeat yourself. So I looked at the different possibilities that Python makes possible and what did I decide? I decided to repeat myself. I decided after several minutes, I have for probably about a half hour of the liberation, trying different things out, maybe a bourbon. I made the radical and counter-cultural decision to simply pass actions in A to every single routine. Why? Because of symmetry. Everything looks the same if I pass actions in A. When I returned to code months and years later, I relearned my code by rereading it. Given a stack of functions that do exactly the same thing, if half of them use one convention and half of them use another, then I now have twice the number of conventions to relearn later and only half the number of examples of each to relearn from. So I would not have done this when I was younger. I just chose verbose symmetry over asymmetric brevity, which would always have been my choice earlier in my career. As a reader, I need routines that behave the same to look the same if I have a chance of being able to maintain my own software and so symmetry, it remained. I simply had all the routines follow the same API. All right, we're ready for a final design step. I talked earlier about widows and orphans, avoiding stranding the first or last paragraph of a line alone as a human with intelligence laying out type, as it was done for hundreds of years in Europe. How does that look in code? Well, my paragraph routine was a bit complicated because of widows and orphans. The paragraph had to lay itself out. If it stranded an orphan at the page bottom, it had to go back and try all over again, lay itself out. If it then stranded a widow at the top of the next page, had to try again. Inside of this if-else widow orphan logic, paragraph had a hidden, simpler inner routine called repeatedly that did the actual paragraph layout, but you couldn't get to it. It was down at the bottom of all these ifs and elses. So what if you had wanted to just call the simple paragraph layout routine? Well, I had to start adding Boolean options to the paragraph callable in order to turn on and off that outer level of if statements. But Boolean switches are often a hint that we have coupled what could actually be two different routines instead of stranding code inside of other code with a switch to turn it on and off. Can you pull it out separately? Composition, when it's possible, is always to be preferred over coupling. And so, if a heading, it occurred to me, can control things about the paragraph that follows it, could I write an avoid widows and orphans routine that had no content of its own to offer to the page, but that simply controlled the next item, whatever it was, to help it avoid the problem of stranding lines. I thought through it, avoiding an orphan, easy. Here on the left, a paragraph has stranded an orphan, its first line, at the bottom of a page. All we need to do, if a paragraph does that, is have this previous action go ahead and use up with white space, the bottom of the first page, then let the paragraph lay out. It's very, very easy to bump an orphan to the next page and fix it. Before calling the paragraph, simply move to the next column. What about avoiding a widow? I tried to sketch out a solution and found it is nearly impossible. Why? Well, because a widow is created when the very last line of the paragraph falls onto a separate page. This means I have to lay out the paragraph all over again and it's next to the last line, even though there's room for it in the column, needs to decide to move to the next page. How am I going in the middle of the paragraph making decisions to just convince it to not use the page like it's supposed to and move to the next column? How would we ever convince a paragraph to move to the next column early? I went and looked at the inside of the paragraph and each time the paragraph needs another line, letting the height adds them together, compares them to the column height and it's that trigger. I need so much space and it's not available that convinces it to ask for another column. How could we influence this choice? Well, I could lie about the value of Y. I could create a line object that I pass it that has a Y that's a little bigger than it should be so that the paragraph will run out of room early but then I'll have to go and fix all the line objects it returns to be back up where they belong on the page. I could make a fake column object instead of the real column object that's a little shorter than normal and thus force the routine to move to the next page early. We are looking desperately for parameters to tweak. Why? Because we're standing outside of the code that makes the decision. So all of the ideas I was thinking of were really bad ones. It was that feeling of using a framework you're not quite in control of. You're thinking of what parameter can I possibly set here that will make the paragraph behave like I want? We're on the outside. Is that really where we want to be during our code's crucial decision? No. When code is making a crucial decision, you want to be in the room where it happens. The room where it happens. The room where it happens. I want to be in the room where it happens. Right now the paragraph only consults us when it needs a whole new column, right? If it needs another page, another column, it calls the callback that we've provided it. Well, what if I get this concept which actually was repeated all through my code of needing another line, comparing and seeing if I have enough room and either returning a line in the same column or if we run out of room, a line in the next column? What if I wrap that up as a callable? And so what if the paragraph calls back not only when it thinks it needs a next column? What if instead of only calling me back when it thinks it's ready for the next column, the paragraph calls back every time it needs a next line? What if I get that crucial if statement I need to control that's down in the paragraph's own code and bump it up into a callable that I pass in? Then the widow orphan logic can subvert the paragraph's normal decision simply by passing a custom next line function. Right inside of avoid widows and orphans, I can just make a little wrapper and pass the wrapped version of next line to paragraph and I then complete control of exactly the point at which it decides to go ask for the next column and moves to the top of that page. This design was a complete success and completely let me break out widow and orphan avoidance from paragraphs and turn it into a general routine which can apply to bulleted lists to tables to all kinds of information that I might wanna apply the same logic to. But did you catch why this approach is a success? Why was I able to do this slide of hand? The fancy next line wrapper is so simple because we avoided premature object orientation. What if instead of just passing next line, we were passed a whole layout object and next line was merely a method? How would you make an objects, an already existing objects next line method return a different value because you wanted it to? Would you monkey patch it? Would you wrap it in an adapter class? Would you implement the entire gang of four decorator pattern? In object orientation, customizing a verb, one little verb can require trundling out an entire design pattern to wrap the object. But if you pass collables, if you treat your verbs as first class citizens, which is one of the strengths of Python, simple inline wrapper can put you in the room where it happens. I've learned over the years in my designs to start verbose and don't be afraid of that and to simplify and collapse things down later. I have come, despite myself, to value symmetry over special cases, to avoid premature object orientation, and to let verbs be first class citizens, attach them to nouns only if there's a compelling reason like dynamic dispatch. I plan, if you want to look at some of this code, parts of it work on releasing my type setting Python library later this summer, but you can already watch my progress on GitHub. Among other things, it laid out this entire presentation, which is a PDF. And so to conclude, what was that book that I showed you a picture of earlier? That is my grandfather's memoirs. The story, the family history going back to the 1820s, his opinions on religion and politics and death. All bound together in a hardback book. Sure print and design in Toronto, Canada will print runs of as little as two books. I wanted to hold it in my hand and proofread it and have a copy to give my father. So we got everything we needed to fix before we handed it to other family members. It'll be a great gift at people's birthday, I hope, and Christmas. My grandfather is just the little essays he'd written and printed off on printer paper and handed to family over the years. When put all in one place ran to 328 hardcover pages, we had no idea it amounted to that much material. And so I was able recently to surprise my father. I had not told him about this project by giving him a professionally printed hardback of his own father's writings. That looked like a professionally typeset book, even though as I admitted to him, it was really typeset with Python. Thank you very much. Thank you very much, Anton. Okay, we have a few minutes for questions now in the audience. You know, if you wanna disagree with me on typesetting. I'm delighted to have, thank you very much for this talk, I'm delighted to hear it because I've been using tech for about twice as long as I've been using Python. And this is probably the best attended talk on digital typography in the world. This year and perhaps the year before. The only thing I think that can rival it will be Don Canoet's 80th birthday celebration which was surely well attended. So my, I'm sorry, I don't have a question. Thank you. Thank you, Brandon, that was really very lovely. I have just a quite a banal technical question, I'm very sorry. When you received the original material that your grandfather wrote, presumably it was written in something like Word or something like that, I guess. Enroth. Oh, Enroth, okay, so it already had good markup. Well, the dashes and stuff that you needed. So his question was, how was my grandfather's essays marked up? One or two of them were in a sort of a quirky format. Most of them had been written on his Unix PC in a format called Enroth, which you can, if you go look at the source code to any Unix man page, Enroth is the system of markup, Bell Labs 1970s markup to mark paragraphs in bold and italics and headings. The main problem with it is that it's more a styling language than a markup language. It typically doesn't say, unless you're using the macros for a man page, natively Enroth doesn't say, here's a heading. It just says, oh, make these words bold. And so I had to write a Python script that I had to kind of learn his conventions. All right, when there's a line by itself that's bold, I will interpret it as a heading. When there is a line with automatic indentation, I will interpret it as formatted poetry because he included my great, great, great grandfather who started a small saw milling town in Southern Alabama in his old age would sit and write poetry. We don't even know how he learned that there was such a thing. And so I just had to learn which Enroth command he tended to use before normal paragraphs and before poetry. And so it has more special cases, I think, than any other code I've ever written. Any more questions? Yeah, so when will your grandfather's memoirs be in bookstores? I would need to find an agent and I would also need to make sure that everyone is dead because he wrote very candidly about what it was like growing up in the segregated South. He talked very candidly about how different ethnicities within Birmingham, different white ethnicities were divided. And while I think that everything he says is very friendly and well-meaning, there are events he recounts that might embarrass sons and daughters of other family members or he developed strong friendships with other Boy Scouts and some of the portraits of them are of people more racist than the average person in Birmingham in 1935. And so to publish the complete essays I would have to make sure that no one was still alive who might be embarrassed by the way that their parents were portrayed or I guess I could try to anonymize those scenes. But so some things are up on my website. If you go look on the website, from the 90s I've had some of his essays about the oldest history of Alabama up there and distant family members have sometimes contacted me because they learn the names of their great and great great grandfathers from those essays. But some of them will probably have to remain unpublished until I'm sure that anyone who might be implicated or parents might be implicated either wanted them published or was gone. So I guess the next keynote is gonna be ensuring everybody's dead with Python. But my question is, because we have to ask the important questions, were all the dashes the right dashes? In Roth did not give him the full palette of dashes and so as I as editor went through fixing spelling mistakes and rectifying things that needed correction. I assured that all of the dashes were correct dashes. In some cases my Python script could just do it automatically. If the things to each side of the dash are digits, I turned it into an end dash. Otherwise I had to know whether it needed to be a hyphen or an M dash which I had to do manually. It's gonna be the last question here. Last question. So are you intended to just release this as a library or do you have ideas about how to plummet into existing markup languages? I was planning, so there is at the moment as far as I know, no lingua franca between markup languages by which an implementation of markdown and an implementation of restructured text agree to express themselves in some neutral equivalent term heading paragraph paragraph. So I think that it would need not just for markdown but for each particular markdown library because there's probably I would choose a major one and it would probably just need a little bit of custom Python code that spoke to that particular markdown library because while I could choose, try to choose one format to represent them all and ask one of those markdown translators to move everything over to that, generally there's impedance mismatching that can miss nuances. So probably custom code to talk to markdown, custom code to talk to restructured text might work best. Good question. Thank you. Thank you all very much.