zerosleeps

Since 2010

Sancoale Slab

As threatened, I slapped down 45 Australian Rupees and purchased a “Worry-Free” licence for Sancoale Slab (normal regular variant) from Fontspring.

Isn’t it beautiful? 😍

Buying a font

So now that version… 5…? of zerosleeps.com has been up-and-running for a while I’ve started thinking about changes and improvements, and one of the things near the top of that list is the typeface. Right now, all text is as per a default Bulma setup: sans-serif and web-safe. It’s all very comfortable but boring.

I’ve always enjoyed the font used on Bunder’s website which is apparently called “Sancoale Slab Norm Regular”. Shoving that search term into DuckDuckGo results in page after page of results for sites with names like “onlinewebfontsonline.xyz”. I wanna know who created this font and buy it from them, dammit.

Some more searching indicates that the Sancoale family of fonts was created by a company called Insigne and sure enough, in amongst the list of hundreds of things created by this company is Sancoale Slab. The product page has a lot of text (not rendered in the font I want to buy) and a few images showing samples, but no indication on how I can give these people my money.

Oh no! Wait! Right at the bottom of the product page there’s a sentence which reads “80% off for a limited time!” but it doesn’t look like a link… no! Wait again! It is a link! And it goes to a site called “myfonts.com” 🙄

Well I guess this is the “official” way of buying this font, and myfonts.com does list prices so I’m getting somewhere. “From” $45.66 (presumably USD). Let’s click on “Buying Choices”. I won’t be using this for desktop publishing or in a mobile app, so I guess I want the “Webfonts” option… wait “10,000 pageviews per month”? “Licence: Annual”.

A bit more digging indicates that I won’t be able to host the font myself - I’ll have to use a “webfont kit”, and “every time the webpage using the webfont kit is loaded … the counting system counts a single pageview for each webfont within the webfont kit”.

Sod that.

One of the other sites that keeps coming up in search results is Fontspring and apparently I can give them $32 (USD?) for a “Worry-Free” licence:

All of Fontspring licenses are perpetual, which means you pay once and use it forever. Fontspring does not require you to install any cumbersome pageview tracking scripts. We trust you.

That sounds way better. But doesn’t the publisher (“foundry”) of the font set the terms? I dunno how this stuff works.

I hate wanting to give someone my money, and not being given an easy way of doing that.

My website broke a different website

I’ve been using Feedbin for 8 years to follow blogs/sites/YouTube channels. A few weeks back Feedbin stopped showing me anything once I’d logged in though. I was getting nothing but an HTTP 500 error and no HTML content at all.

After about 24 hours of this I contacted Feedbin and less than 30 minutes later Ben Ubois (who created and runs the service) replied:

Feedbin had a bug that was actually triggered by: https://zerosleeps.com/feed.json.

The “author” key just has a string value, but Feedbin was expecting an object like author: {name: …}, as the spec describes.

I subscribe to my own feed in Feedbin to make sure it works as expected, and it turns out that I wasn’t following the JSON Feed version 1.1 specification. I’d been off-spec for years, but a change to Feedbin’s code brought the problem to the surface.

Ben had it fixed within half-an-hour, and I immediately fixed by side of the problem as well:

-    "author": "{{ site.author }}",
+    "authors": [
+        {
+            "name": "{{ site.author }}"
+        }
+    ],

I thought it was rather fun that my website could break someone else’s website!

Search!

Something that bugged me for the entire duration that this site was just a pile of static files was that I didn’t have a good solution for search. The previous incarnation of zerosleeps did have a search form on every blog page, but it just sent the user off to DuckDuckGo and performed a “site:zerosleeps.com” search there.

The results were never great, presumably because DuckDuckGo had no reason to make a decent index of a tiny site like this. Plus it was an external dependency which I’m never a fan of.

Well that changes today: blog posts are now searchable entirely in-house. I’m using PostgreSQL’s full-text search functionality, which is made a little easier by Django’s support for it in django.contrib.postgres. Just blog posts at the moment, but I want to get the reading log in there as well and have one global search.

Post titles carry more weight than the post body, and the results are sorted by whatever rank PostgreSQL comes up with. I’m also using the “headline” function to show the most relevant snippets. Behind the scenes it looks a little something like this:

vector = SearchVector("title", weight="A") + SearchVector("body")
return (
    Post.objects.annotate(
        search=vector,
        rank=SearchRank(vector, query),
        headline=SearchHeadline(
            Concat("title", Value(" "), "body"), query, max_fragments=2
        ),
    )
    .filter(search=query)
    .order_by("-rank")
)

It’s not perfect but it’s absolutely good enough. Better than good enough. Makes me glad I chose PostgreSQL over my other choice - SQLite - as well. SQLite does have full-text search built-in, but from what I can tell it involves creating virtual tables and keeping them up-to-date with the real content. Seems messy. This solution instead boils down to just one (slightly verbose and repetitive but who cares) SQL query which contains:

SELECT
    ts_rank(
        (
            setweight(to_tsvector(COALESCE("blog_post"."title", '')), 'A')
            ||
            to_tsvector(COALESCE("blog_post"."body", ''))
        ),
        plainto_tsquery('search term')
    ) AS "rank",
    ts_headline(
        CONCAT("blog_post"."title", ' ', "blog_post"."body"),
        plainto_tsquery('search term'),
        'MaxFragments=2'
    ) AS "headline"
FROM
    "blog_post"
WHERE
    (
        setweight(
            to_tsvector(COALESCE("blog_post"."title", '')), 'A')
            ||
            to_tsvector(COALESCE("blog_post"."body", ''))
    ) @@ (plainto_tsquery('search term'))
ORDER BY "rank" DESC;