Font Interpolatio n Roey Shapir o August 202 2 C ontents (click to jum p to a section) : • initial motivation for f ont i nterpolation • background info on basic vector - graphic fonts ; loose definitions of Bezier curves, Contours, and Glyphs • glyph mapping; simpl ifying the problem into contour mapping • contour mapp ings • miscellaneous bits and bobs, conclusion A ssum es you know : • what linear interpolation ( lerping) is • set and sequence notations • basic linear algebra • formal definition of functions If you have form al m ath s know ledge, there ’ s som e of that in here, too! This is very m uch a w ork in progress. The code is at this G it H ub link , and throughout the article there w ill be ( ∗ ) sym bols w hich indicate places w here I think there is easy room for im provem ent . Feel free to jum p around by using C ontrol + F to find these spots and share your ow n ideas and code! A ll links w ill also appear at the end of the article. 1 What and Why is Font Interpolation ? 1.1 Motivatio n For some motivation and to help picture precisely what Font Interpolation is, consider the followin g short story I wrote a while ago : Slanted sunlight floated down through the opened windows, gently frame d by the wispy curtains flowing in the breeze. The r oom was a gentle yellow Grandfather lay on the bed, hypnagogic. He didn’t seem to be looking a t anything, his eyes focused to some vague infinity Watching paint dry or grass grow. Listening to a wail become a “mama” an d somehow incrementally a voicemail describing college life. Cooking an eg g sunny side up, or whipping cream to soft peaks. Then, it was one thing, an d now, it is another. These fragile diagonal rays of sun - how long would it tak e them to melt this block of ice before me ? My eyes pass over the leathery surface of Grandfather’s forehead in optic tiptoe It would be rude to disturb his reverence. . . or whatever it is. It is rare to se e the moment of transformation, perhaps because it is difficult to define. Theseu s was unaware, but I will wat ch this metamorphosis. I owe it to Grandfather In a moment of ultimate irony, considering these things caused my own eyes t o become unfocused from his pale skin. It only took that moment; I had failed t o witness the transition into the end. Without my not icing, the final piece of th e Grandfather had melted away. Only cold water remained While not much more than a 2AM ramble, it did manage to speak to me about transitions that happen right under our noses - a fascinating thought for any field... if one can get in the correct headspace. It’s easy to look at something like this and write it off as detached and overly poetic. I started considering other ways to help a reader empathize with the feeling of not realizing that a transition is occurring until it’s already occurred. Maybe I could change the font somehow to convey this ? There are ways to gradually change a font’s boldness or italicization gradually – there are tools to do that and it isn’t a particularly difficult issue to solve compared to the more g eneral issue of Font Interpolation outlined here. I wanted to be able to choose any two fonts and have the text start as one and end as the other – that didn’t exist yet Here's an image to help better visualize the effect : 2 How do Fonts work ? We should start by considering how fonts are rendered in most environments on the web or word - processing environments. On the surface, it may seem that a given font’s letters are simply images that are pasted on different parts of a page. Have you ever not iced that when you zoom into a page, though, the letters don’t get more pixelated? That’s because each character, or “glyph” as they’re called in the typographical world, is stored in an abstract “Vector Format”. Basically, this means that each glyph is s tored in memory as abstract curves and other drawing instructions. When it comes time to draw the glyph, it can be drawn at an accuracy fitting the current zoom level. The drawing instructions dictate how and which pixels are filled in. In this way, you ca n experience functionally limitless accuracy and font sizes with a single file. For more info, check out this video by Anya Smilanick : Vector - based images vs. Pixel - based (Bitmap) image s I won’t go super in - depth on this, so please do some reading. There are great resources out there ! The main thing to note here is that you have Bitmap images , often ca lled “ Raster ” images, which are in pixels , and Vector images, which have the potential to be “ Rasterized ” and turned into a fi xed pixel image on your screen depending on your de sired accuracy. Vector images are the type we ’ re interested in. 3 Bezier, Contour, Glyp h We’ re now ready to build up to the glyphs that comprise a font, starting from Bezier curve s . Simply put, a Bezier curve is an abstract curve defined by a few control points. Below is a Bezier defined by four control points : You may be able to imagine how stitching these together, one after another, could produce the desired effect of forming pretty much any shape we want. If you want to learn more about s pecifics of Bezier curves (i.e. how to draw them, how they’re defined, etc.), try out this wonderful interactive websit e by Pomax. There are two key takeaway s here : 1) any given Bezier curve can be represented as a matrix. In this case, since each point exists in 2D space, and there are four points, we could have a (4 x 2) matrix, where each row represents a point 2) a Bezier curve simply gives instructions of how to get points along its curve; to “ draw the curve ” , you have to decide how accurate you want to be, pick that many points along the curve, and then when it comes to actually drawing the thing, you just draw straight lines between those points. You never actually draw a curve – just straight lines. Font designers actually do precisely this - by starting each new curve from the end of the previous and, crucially, forming a closed loop, they form what is known as a Contou r . For example, consider the following 4 Bezier curves forming this circle contour : So we can see how as a mathematical obj ect, a contour can simply be an ordered list, or finite sequence, of Bezier objects. In this case, if we let the constant 𝑐 = 0 5522847 , that long number in the image, we could define this contour 𝐶 1 as follows : 𝐶 1 = ( [ 0 1 𝑐 1 1 𝑐 1 0 ] , [ 1 0 1 − 𝑐 𝑐 − 1 0 − 1 ] , [ 0 − 1 − 𝑐 − 1 − 1 − 𝑐 − 1 0 ] , [ − 1 0 − 1 𝑐 − 𝑐 1 0 1 ] ) Fi nally, a glyph ca n simply be defined (for our purposes here) as a set of contours. In theor y, this contour , 𝐶 1 , could be a glyph all on its own. Let ’ s say we called it 𝐺 1 So here, we ’ d have 𝐺 1 = { 𝐶 1 } – just the one contour! If it were, though, we ’ d obviously expect to see this: And not, well ... this: [cricket no ises] This , again, is a matter of the rasterizi ng process. Recall that before I mentioned how a program needs to decide how to fill in certain pixels in order to draw the glyph. If it were trying to draw thi s cir cle, it would need to know: is this a “ fill in ” contour or a “ subtract ” contour , so to speak. We only see the outline of this contour while editing it in special software! When it ’ s drawn , it ’ s pixels insid e a closed shape , not an outline! Th us, if the circle was a “ subtract ” contour, it might not be visible at all. It ’ ll be easier to see on this letter “ a ” Let ’ s define a new glyph 𝐺 𝑎 to represent it. There are two contours in 𝐺 𝑎 Let ’ s say 𝐺 𝑎 = { 𝐶 𝑖𝑛 , 𝐶 𝑜𝑢𝑡 } 𝐶 𝑜𝑢𝑡 defi nes the larger overall are a that ’ s bounding the whole thing , and 𝐶 𝑖𝑛 defines the little hole in the middle – it says, “ if some other contour is trying to draw in the area inside of me , don ’ t let it ! ” It ’ s a bit more complicated than that in practice, but just so you get th e idea for now. That ’ s what I meant earlier, though : it may be that the circle at the start is a “ subtract ” t yp e contour. If there ’ s nothing to subtract from, depending on the Rasterizer (the program that turns the abstract curves into pixels) , it might just happily subtract from nothing and sh ow nothing at all. I ’ ve rendered 𝐺 𝑎 such that we can see the order of the Bezier curves of each contour by their color. We begin from the f irst Bezier being colored with red and the last with green Two things: 1) You might be ab le to n otice, firstly, that the color blending is continuous. You might take this for granted, but it confirms that whoe ver made this font indeed made the contours one contin uous cycle. The curves don ’ t just form the correct shape – they do it in a connected sequence. 2) A second thing to observe is that 𝐶 𝑜𝑢𝑡 goes clockwise in the order of its Beziers and 𝐶 𝑖𝑛 count er - clockwise. This is because noting which are clockwise or counter - clockwise c an provide an easy way of implicitly defining which contours are “ filler - inners ” and which are “ subtracters ” . It is simply interpretable from the point - data itself using, for example, a winding number calculation algorithm One final bit of notation. I ’ ll be using the size notation in the following way s: F or a glyph 𝐺 , | 𝐺 | ≔ # 𝑜𝑓 𝑐𝑜𝑛𝑡𝑜𝑢𝑟𝑠 𝑖𝑛 𝐺 For a contour 𝐶 , | 𝐶 | ≔ # 𝑜𝑓 𝐵𝑒𝑧𝑖𝑒𝑟𝑠 𝑖𝑛 𝐶 Ok, sweet ! We know at leas t loosely what Beziers, Contours, and Glyphs are, and we ’ ve come up with simpl e mathematical representations for them. Now for the interpolation process. 4 The General Goal of Font Interpolatio n There are two stages to interpolating between two glyphs: finding a mapping between certain ele ments of the glyphs, and actually performing the interpolation between them given some percen tag e 𝑡 ∈ [ 0 , 1 ] The precise goal of the mapping phase of the process is perhaps subjectively definable. On e straight - forward mathematical approach is to pick certain points (more on how we pick points later) on each glyp h and find the mapping between them which simply reduces the mean squared error (MSE) One qualitative attribute we certainly want to maintain is identifiability. How can we adhered to attributes of each font ’ s signature look at all pe rcentages, but also always be able to identify the interpolated glyph as the correct character ? For example, given two drastically different ”a” glyphs : ... it wouldn’t be particularly desirable to obtain the following ”a” 50% bet ween them , since it doesn ’ t look like an “ a ” (or ... anything, really) : The interpolation phase of Font Interpolation can also v ary drastically from method to method Explored here is the naive approach of simply linearly interpolating between any two points in th e mapping found 4 1 Contour - Naivet e I employ ed a ”Contour - Naive” (CN) methodology. Consider the se ”k” glyph s : See how each has a single contour ? This means that a mapping method would only have to conside r how to move points or curves around between a single path in order to consider each glyph in it s entirety You might not even see the issue I ’ m getting at , so consider these glyphs now in comparison : These each have two contours , as we saw before What does the Contour - N aïve approach do? Well, it basically makes it so that a give n contour does not ”see” any other – you cannot map two points t o two different c ontours on one glyph to a corresponding pair on a single contour in the other glyph. I know that ’ s confusing ... just look at this picture : Suppose we didn ’ t apply CN and somehow mappe d the 𝑝 to 𝑞 and 𝑥 to 𝑦 . Maybe this happened because w e were using a MSE - based approach on the entire glyph and saw that they were reasonably close, for example. Interpolating 𝑝 to 𝑞 and 𝑥 to 𝑦 would tear the outer contour of the left ”a” and the inner contou r of the right ”a” : CN guarantees that there will be no such tearing, even if it limits the final resul t in more complex cases (as shown in t he botched cursive ”a” example ) I t also simplifies the entire problem. For one, t he interpolation step becomes simpler : we only have to worry about interpolating between two contours at a time rather than two entire glyph s It also simplifies the mapping step, since we can now just look at mapping points on a single contour to some other Contour - Naivete works especially well in cases where glyphs are reasonably similar to each other , and especially if they have the same number of contours. It doesn’t always perform particularl y badly when these conditions ar en’t satisfied, either There ’ s one more thing to consider. What if , for some reason, the glyphs don ’ t even have the same number of contours ? Where do we map the extra ones? The following alg orithm solves all of these issues. 4 2 Glyph mapping under Contour - Naivete We define a Contour - Naive glyph mapping as a set of pairs of contours One can be built as follows ( ∗ ) : Let 𝐶 ∗ be the space of all possible contour s. Let 𝑀 ∗ = ⋃ ( ℝ 2 × ℝ 2 ) 𝑖 ∞ 𝑖 = 0 be t he space of all mapping s of finitely many points in ℝ 2 Let 𝐺 1 , 𝐺 2 be glyphs such that | 𝐺 1 | ≥ | 𝐺 2 | and let 𝑛 1 = | 𝐺 1 | , 𝑛 2 = | 𝐺 2 | Finally, let 𝑀 𝐶 : 𝐶 ∗ × 𝐶 ∗ → 𝑀 ∗ × ℝ be a contour mapping function: one w hich takes two contours and re turns a mapping and the score of that mapping, where the higher the score, the better the mapping is This gets a bit complicated, but just take it slowly and it should be clear. Note that we assume the existence of the function that can find a mapping of points on two contou rs (referred to as 𝑀 𝐶 ). You haven ’ t mi s sed anything – the final part of this article covers what types of contour mapping functions I came up with and used For now, j ust assume we have some 𝑀 𝐶 that does what we want it to do : spit out a mapping of points and a score for how good the mapping is. The glyph mapping process I came up with uses two steps: F irst , map each contour in 𝐺 2 , the glyph with less contours, to a contour in 𝐺 1 , that with more. Second, map the remai ning 𝐺 1 curves to whichever curves are individual ly best in 𝐺 2 The goal is ob viously to maximize the score, but we must constrain the problem to hopefully maintain key features of t he cha racter we ’ re trying to produce. This glyph mapping method guarantees that each contour in 𝐺 2 is represented at least onc e – each is mapping to so me contour in 𝐺 1 Here are the details: We begin with step one by finding 𝐼 ⊆ 𝐺 1 , a n initial subset of 𝐺 1 ’ s contours, such that | 𝑆 1 | = 𝑛 2 . We determine 𝐼 out of all possible subsets of 𝐺 1 b y exhaustively picking some possible 𝑆 ⊆ 𝐺 1 and some permutation of 𝐺 2 ’ s contours to for m an initial mapping se t : 𝐼 = { ( 𝐺 1 𝑐𝑜𝑛𝑡 , 𝐺 2 𝑐𝑜𝑛𝑡 ) 1 , ( 𝐺 1 𝑐𝑜𝑛𝑡 , 𝐺 2 𝑐𝑜𝑛𝑡 ) 2 , ... , ( 𝐺 1 𝑐𝑜𝑛𝑡 , 𝐺 2 𝑐𝑜𝑛𝑡 ) 𝑛 2 } ... such that 𝐼 maximizes the sum of scores 𝑀 𝐶 returns from being run on each ( 𝐺 1 𝑐𝑜𝑛𝑡𝑜𝑢𝑟 , 𝐺 2 𝑐𝑜𝑛𝑡𝑜𝑢𝑟 ) pair. If you want t he formal stuff: Denote 𝑆 ( 𝑋 ) the set of all ordered permutations of a set 𝑋 For 𝑃 ( 𝑋 ) be ing the power set of a set 𝑋 , we d enote 𝑃 𝑎 ( 𝑋 ) = { 𝑥 ∈ 𝑃 ( 𝑋 ) | | 𝑥 | = 𝑎 } Therefore, w e have: 𝐼 = arg max 𝐼 ∈ ( 𝑃 𝑛 2 ( 𝐺 1 ) × 𝑆 ( 𝐺 2 ) ) ∑ 𝑀 𝐶 ( 𝐺 1 𝑐𝑜𝑛𝑡 , 𝐺 2 𝑐𝑜𝑛𝑡 ) 𝑠𝑐𝑜𝑟𝑒 𝑛 2 𝑖 = 1 Now for step two. We ’ ve bijectively mapped 𝑛 2 of 𝐺 1 ’ s contours to each of 𝐺 2 ’ s contours , leaving us with 𝑛 1 − 𝑛 2 𝐺 1 contours to map. As mentioned before, we won ’ t impose any restrictions on them , since all of 𝐺 2 ’ s contours have already been repre sented at least o nce by step one. Thus, we simply iterate through each remaining 𝐺 1 contour and find which of 𝐺 2 ’ s contours it yields the highest score with. Formally, this gives us for each remaining 𝐶 1 ∈ 𝐺 1 , mapping 𝐶 1 to: max 𝐶 2 ∈ 𝐺 2 𝑀 𝐶 ( 𝐶 1 , 𝐶 2 ) 𝑠𝑐𝑜𝑟𝑒 ... and we ’ re done! We have the initial 𝑛 2 contours of 𝐼 and the remaining 𝑛 1 − 𝑛 2 contours mapped here, so al together we ’ ve map ped all 𝑛 1 contours from 𝐺 1 to all 𝑛 2 c ontours of 𝐺 2 : an injective glyph mapping, as we wanted! 4 3 Using Contour Fill - types to maintain key features of a character As stated before, the CN - method doesn ’ t ler p the whole glyph – we ’ ve redu ced the problem to figuring out just how to lerp a contour to another. But that doesn ’ t mean that we can ’ t bias the scores so that obvious mistakes do n ’ t happen. Take th at example from before: Ob viously, we ’ d want to mat ch the inner contours to each other and outer contours to each other. If the glyphs are reasonably sim ilar to each oth er , this will happen naturally. It might se em like we can ’ t influence that choice any more than that once we decided to throw our hands up wit h Contour - N a ivete But recall from before that simply from the point data we can determine whether a contour is a “ fill - in ” or “ subtract ” This can be used in myr iad ways: making contours whose “ fill modes ” do n ’ t match have half the score, or − ∞ score, or adding them t o som e sort of queue i f you have lots of − ∞ ones , etc. ( ∗ ) Point is, you don ’ t have to do m uch to get it to do pretty much what you want. 5 Contour Mappings We ’ ve subdivided all th e way d own, going from fonts to glyphs to contours We ’ re now ready to tackle the proble m we ’ ve reduced this all down to: what good ways are there to map a given contour to anothe r ? This is where our review of Bezier curves will come in handy . There are three main methods I explored , each of which relies on final subdivision , either into curves or points along the contour. At those levels, lerping is easy to under stand : To lerp between two points, simpl y lerp! To lerp between two curves , simply ensure that they have the same number of control points, and then lerp the start points together, the first control points together, and so on, until the end poi nts are lerped together. Si milar to the macro - situation of two glyphs with different numbers of contours , thing s really get tricky when two contours have different numbers of curves In all of the following al gorithms and examples, I ’ ll maintain the convention that for two contour s 𝐶 1 , 𝐶 2 , | 𝐶 1 | ≥ | 𝐶 2 | We ’ ll also call | 𝐶 1 | = 𝑛 1 and | 𝐶 2 | = 𝑛 2 This is where most of the work went in – where most of the magic happens I ’ d love to see what others come up with! ( ∗ ) I ’ ll be detailing these sort of as a story, working through ideas roughly in the order I thought about them as I worked through this problem myself 5 1 The Reduction Method T h is technique uses a very simple idea. 𝐶 1 has more Bezier curves than 𝐶 2 . Let ’ s find which 𝑛 2 of the 𝑛 1 curv es in 𝐶 1 are the “ most important ” or “ best fit ” the 𝑛 2 curves of 𝐶 2 Any other 𝐶 1 curve shrinks down to a single point : it is reduced to nothing. F or example, let ’ s say we had the following two contours : Let ’ s denote the hexagon and triangle 𝐶 𝐻 , 𝐶 𝑇 respectively. The reduction method sees that | 𝐶 𝐻 | = 6 ≥ 3 = | 𝐶 𝑇 | a nd therefore looks f or the 3 most fitting curves in 𝐶 𝐻 . F or example, i t m ight make this choice: What happens to the remaining 3 𝐶 𝐻 curves ? Th ey ’ re “ reduced ” down to a single point – whichever point separates the corresponding curves in 𝐶 𝑇 – in the interpolation process In this example, it would look like this: I ’ ve bracketed the three remaining , unpaired curves and the 𝐶 𝑇 points they ’ ll be reduced in to in the interpolation process. In addition, I ’ ve marked 3 𝐶 𝐻 curves and t heir corresponding elements in 𝐶 𝑇 to show how this works for one of these reduced, “ null curves ” In our mapping earl ier, we mapped 𝑥 → 𝑎 and 𝑧 → 𝑐 As we ’ re interpolating between these two contours, we walk around 𝐶 𝐻 , the larger contour, and see ok ... 𝑥 ’ s partner is 𝑎 . Next curve. 𝑦 doesn ’ t have a partner. What ’ s the last endpoint we ’ ve seen? T he e ndpoint of 𝑎 ! We can create a new curve that just has all of its points be i ng that endpoint – let ’ s call it 𝑁 𝑎 for “ Null cu rve a ” a nd suddenly 𝑦 does have a partner : 𝑁 𝑎 ! And we continue in this way. If multipl e successive 𝐶 𝐻 curves need to be mapped to null curves, it works in the same way. The last endpoint we ’ ve seen is only up date d when we map another non - null curve. In this case, w e ’ d continue to 𝑧 , see it has a non - null curve partner 𝑐 , and update the last endpoint to be th e endpoint of curve 𝑐 So that ’ s all great, but how do we find that mapping in the first place? Sometimes when solving a very open ended problem it ’ s easier to begin by ask ing the opposite of what you want first. In this ca se, we don ’ t ask, “ what do we want ” but rather “ what don ’ t we want ” to give us some structure. I n this case , something we definitely don ’ t want is tearing Think about that “ a ” that we pinched and tore up ( RIP ) i n section 4.2 We can prevent this by making sure that our contour mapping function maps curves in order. So that ’ s one constraint: we can only look at in - order sequence s of curves on each contour. So that ’ s how I started . I literally made a list of all in - order subsets of 𝐶 1 ’ s curves that were of size 𝑛 2 For any subset of curves in that list and a rotation offset, I compu te the mean squared error ( MSE) o f each curve in 𝐶 2 . For example, let ’ s say I label the curves as such : ... and I begin by checking the 𝐶 1 subset { 4 , 5 , 6 } and t he MSE of ( 4 , 1 ) , ( 5 , 2 ) , ( 6 , 3 ) , where each pair is of the form ( 𝐶 1 𝑐𝑢𝑟𝑣𝑒 , 𝐶 2 𝑐𝑢𝑟𝑣𝑒 ) Now for that same choice of 𝐶 1 ’ s 4 , 5 , and 6 , I can try 2 more rotat ed choices of 𝐶 2 ’ s 1, 2, and 3: 𝑜𝑓𝑓𝑠𝑒𝑡 = 1 : ( 4 , 2 ) , ( 5 , 3 ) , ( 6 , 1 ) 𝑜𝑓𝑓𝑠𝑒𝑡 = 2 : ( 4 , 3 ) , ( 5 , 1 ) , ( 6 , 2 ) So, just for one more iteration of this for example , I could then pick the 𝐶 1 subset { 1 , 4 , 5 } . I do the same thing with 𝐶 2 , checking the three offsets: 𝑜𝑓𝑓𝑠𝑒𝑡 = 0 : ( 1 , 1 ) , ( 4 , 2 ) , ( 5 , 3 ) 𝑜𝑓𝑓𝑠𝑒𝑡 = 1 : ( 1 , 2 ) , ( 4 , 3 ) , ( 5 , 1 ) 𝑜𝑓𝑓𝑠𝑒𝑡 = 2 : ( 1 , 3 ) , ( 4 , 1 ) , ( 5 , 2 ) For each of these, we check what the MSE was ; if that subset of 𝐶 1 and rotational offset of 𝐶 2 yielded a lower MSE than we ’ ve seen before, we save them and continue looking. Exhaustively. ... yeah, I don ’ t really know what I th ought would happen. It work ed really quickly on my test contours, which had, like, 6 curves max. S o quickly I could have it run every frame and re calculate the best mapping as I dragged around curves in real time ( see it in action in this video ) But then when I started importing fonts off the internet and saw even basic glyphs had 30 curves ... Y eah. Th is exhaustive search grows factorial ly , since it ’ s a function of the choose operation. There are ( | 𝑛 1 | | 𝑛 2 | ) = | 𝑛 1 | ! | 𝑛 2 | ! | 𝑛 1 − 𝑛 2 | ! subsets , and for eac h of those 𝑛 2 offsets to run th r ough. To give you an idea, one of my first ignorant tests had 𝑛 1 = 88 , 𝑛 2 = 37 , which yields 3 214 ∗ 10 26 iterations Optimistically assuming that each takes a nanosecond , that ’ s 1 0 billion years. So ... no. N ot very practical It does work well when it ’ s don e , though! If you find contours whose difference of number of c urves is small, then the Reduction algorithm is actually still quite feasible in terms of time This makes sense – the closer the number of curves, the closer you ’ re getting to as king about a bijective mapping , meaning you must be “ more decisive ” or “ more constrained ” in your options. [ You may a lso notice that this is similar to the exhaustive approach of the first part of the glyph mapping algorithm . Why is it feasible there and not here ? Well, glyphs tend to have at most 3 conto urs, so the number s just stay so small that the factorial doesn ’ t matter .] If we give up on constrain ts, can we make it faste r? ... How about greedily? 5.1.2 Greedy Reduction In computer science, a “ g reedy ” algorithm is one which only ever picks the local ly optimal choice , but this always leads to a globally optimal solution Well, as much as I banged my head against it, I couldn ’ t figure out a way to greedily pick while guaranteeing that the order of the c urves would be maintained. Still, I t ried it out The idea is to calculate the MSE (or w hatever other scoring system you like – there might be a better one! ) for every combination of curves. Then, you simply sort the li st a nd pick the highest scoring ( 𝐶 1 𝑐𝑢𝑟𝑣𝑒 , 𝐶 2 𝑐𝑢𝑟𝑣𝑒 ) pair that hasn ’ t already had either of its curves picked yet. And this ... doesn ’ t really work. It simply can ’ t be relied on that the glyphs are similar enough (in general feat ure s or scale) th at this will work in its current form. I tried many variations of mixing greedily picking with random subsets, sorting by the highest score relative to which curve was chosen last, and more. It ’ s likely that there ’ s a way to make greedily choosing feasible. I just couldn ’ t find it. Which lead me to another approach ... 5 2 Pillow Projection The Pillow Projection (PP) method doesn ’ t look a t curves at all It looks at the contour as a whole. Given some number 𝑚 of sample points , PP walks along the contour and places 𝑚 equidistant points . In t his case, 𝑚 = 100 : How do we approximate th e length of the contour ? Well , if you wer e paying attention to the Bezier refresher at t he start , a Bezier curve is drawn at a given accuracy . That accuracy simply determines how many straight lines are drawn to give the impression of drawing a curve . We can use a relatively high accuracy to get a decent approximat ion of each Bezier ’ s length by adding up the lengths of th ose straight line segments . By extension , we can calculate an approximate co ntour length. Y ou might already see where this is going. We literally just match each point on the left with a corresponding point on the right. How do we decide which point goes to which? Here are two ways : 1) Basically do what we did with the rotational offset in the Reduction method and check 100 offsets: first match ing the points ( 1 , 1 ) , ( 2 , 2 ) , ... , ( 100 , 100 ) , then the next offset, doing ( 1 , 2 ) , ( 2 , 3 ) , ... , ( 100 , 1 ) , and so on . See which gives minimal MSE, and ch oose it ; this inherently gives a point mapping as well 2) Choose some “ anchor point ” relative t o each contour ’ s bounding box. I n the example above , it ’ s the upper - left, marked with an “ X ” For each contour, g o through each of its 100 points to find which is closest to the anchor. That point can now be marked as “ point 0 ” on each contour, and every point after 𝐶 𝐻 u pper left 𝐶 𝑇 u pper left it 1, 2, and so on, until 100. We match the point 0 ’ s, the point 1 ’ s, and so on. There might be a better way to choose an a nchor point! ( ∗ ) Now, having a bunch of points is great, but all we can do with points is draw straight lines between them, right? Not great if we want to retain the cool , continuous curves that we had access to before stripping these contours down to discrete points. Luckily, there are algorithms that fit Bezier curves to p re de termined points , l ike this one . We can use that to rebu ild the glyph with curves , so th e process is like this: 1) Find 𝑚 points on each contour 2) Index the points based on some anchor which is relative to the contour ’ s bounding box 3) When lerping, simply lerp a given point on one contour with its corresponding point on the other ; doing this for all points gives 100 lerped points that form a new contour 4) Re - smooth the points of this discrete contour into a c ontinuous contour with a Bezier point - fitting algorithm That ’ s it! The score returned is simply the total MSE of all point pair s T h is technique feels like cheating to me It ’ s dead - simple , but not mathematically satisfying. It ’ s elegant in a sort of naïve wa y. That is, it doesn ’ t really retain the precise abstract points that comprise the two contours – it basically approximates them and then gives you something that looks like the originals, but will probably have pillowed corners everywhere there used to be sharp ones if you zoom in closely enough (hence the name “ Pillow Projection ” ) . When one of the benefits of vector - based graphi cs is basically infinite precision and zooming as much as we please, this feels a bit like ruining a foundation al aspect of the fonts were using That said ... it gives some damn great re sults , and quite quickly ( seconds, not billions of years ) . Here you can see it working even on quite drastically different contours (not on ly in feature proportions, but also scale) : The reasoning as to why is pretty basic , too: if two co ntours are even relatively similar to each other, it ’ s because we identify them as such. How do we i dentify “ similar ” contours? If they have the same relative distances between their main features. The line - and - circle structure of a “ p ” or “ q ” is pretty co nsistent, for example. It ’ s fast, simpl e, and doesn ’ t care about disc repancies in scale. If one font happens to have one character look reall y small compared to the same character in another font, Pillow Projection will gladly find the same mapping. 5 3 Relative Projection My final attempt, the Relativ e Projection (RP) method was supposed to remedy some of the imperfect math s happening in Pillow Projection. It ’ s a bit more involved, and, if I ’ m being honest, I haven ’ t even fully implemented it , but it ’ s my favorite of all of these contour mapping algorithms . There ’ s yet a bit of debugging to do, but I ’ m fairly sure it ’ s mostly working as intended. H ere ’ s t he concept , again on our fr iends 𝐶 𝐻 and 𝐶 𝑇 Begin by getting 𝑚 sample points on each contour , as before , and find some “ agreement point ” that ’ s determined relative to some anchor point . Again, we ’ re usi ng the upper left of the contour ’ s bounding box. W e now have all of these sample points labelled from 0 to 𝑚 − 1 . Unli ke PP , we don ’ t use the indices to match the points. Instead, we use them to create a new unit: the “ contour - fraction ” (oooh, e x citing) The contour - fraction is simply the fraction along the contour a given point is, relati ve to the position of s ample point 0 Suppose t hat in the following examples, the large black dot on each contour is what ’ s been determined to be sample point 0 . W e ’ ll see so me points along each contour labelled with their contour - fraction values. T he next step is to now mark each Bezier ’ s start point with its contour - fraction value . Since these are polygons, the numbers will turn out quite even, but don ’ t let th at distract from how generic the values can be: Now comes the magic : we ’ ve put the points into the same terms! We can mark each contour with the other contour ’ s contour - fractions to get two “ marked contours ” Note this crucial fact: we have the same number of marked points on both contours : in this case 6 + 3 = 9 points.