TrueType is a widely used vector font standard for rendering text in web pages, PDFs, operating systems, and applications. Familiar fonts like Helvetica, Garamond, and Monaco are all built on TrueType outlines. The format specifies a hinting interpreter intended to help outlines rasterize faithfully on low-resolution displays. Modern high-resolution displays enable beautiful typography from outlines alone, but TrueType fonts that need hinting to render legibly remain in use and we continue to support them.
Font parsers process data from untrusted sources, making the TrueType hinting interpreter a security-critical attack surface. To make the format more resilient on Apple platforms, we rewrote its hinting interpreter from C to memory-safe Swift for the Fall 2025 releases. In addition to memory safety, we also improved performance: on average, our Swift interpreter runs 13% faster than the C interpreter it replaced.
↫ Scott Perry
This article provides a deep dive into how, exactly they did that.

We don’t often think about font attack surfaces, but it’s a significant one, especially when fonts can be controlled by an attacker using fonts of their choice in documents, PDFs files, webpages, etc. Font handling is complex and exactly the type of code that is dangerous to write in unmanaged C code. While there is tons of resistance to change, IMHO moving this kind of code into memory safe languages makes a lot of sense.
However most of these new safe languages are still being held back by C being the common interface they all share, including when it comes to Rust and Swift, etc. Even basic OOP and namespace interoperability remains muddy despite languages having innovated them decades ago. We really need a new common interface standard that support modern language features and safety without reverting back into primitive C APIs. Reliance on C APIs can undermine a lot of the work being done in terms of robust programming languages.
It’s actually the type of project I’d like to work on, but in practice a new standard would be ineffectual unless it’s backed major industry partners. They have to be on board otherwise it’s just some niche standard nobody uses.
Alfman,
That is a more fundamental issue though. Not just C ABI incompatibilities
First the world has moved on from older “Version 2.0” of OOP, which is abstract/virtual runtime polymorphism, and dynamic dispatch. Long story, but it has too many issues.
The modern “Version 3.o” is spearheaded by C++ templates, the compile time polymorphism.
And that is basically impossible to do with stable runtime interfaces. So Application / Plugin boundaries always have to fall back to some sort of C and dynamic dispatch (ActiveX/OLE in Windows, DBUS in Linux)
That of course drops all type safety, and has to be a rock solid boundary security. Not a big deal for the kernel or system libraries, which has much higher scrutiny, but a mess for random 3rd party ones which might or might not be as vetted.
sukru,
Well at a bare minimum Interfaces should be able to support static OOP and namespaces. I know it’s taboo to criticize K&R because they were at the cutting edge, but even back then they should have foreseen the need for namespaces. I suspect the main reason they didn’t do a better job with it is simply because they didn’t realize their work would go on to become the standard for decades later. The reason they didn’t produce a good standard because they never set out to create one in the first place. It was a byproduct of working on unix. It’s not their fault we are still using C, but the fact that we are still using it keeps us locked into programming interfaces that are inadequate today. Even C++ suffers for it.
This argument kind of comes across as whataboutism to me. Even if dynamic interfaces are out of scope, that’s not really a good justification to not modernize static interfaces. C is holding everyone else back.
I actually do think there would be merit in standardizing dynamic interfaces too, but it’s unnecessary for implementing the static interfaces that most of us use. Static needs to be in the baseline whereas dynamic does not.
Many languages already make static type safety work, even C++ does a good job with this. I hate having to wrap everything around C interfaces, which I’ve had to do recently… boy did that suck. We’re only using C interfaces because they are the de-facto standard and not because they are good. If we’re being honest C interfaces are wholly inadequate for software in virtually every other modern language.
Alfman,
The static is very difficult to move beyond compilation boundaries.
Take something like max(a, b)
If this is dynamic, you have something like an IUknown implemented by a and b numbers, and maybe an GreaterThan operation that can be resolved at runtime
If it is static, the types need to be known at compile time, along with memory layouts, any alignment requirements, and of course the recipe for that comparison operation.
This is why all template libraries are actually just header files, you have to give away all the source.
And anything that goes through a binary boundary has to become dynamic dispatch through a tiny C sized hole.
sukru,
We can talk about dynamic cases where types aren’t known at compile time, but that’s above and beyond what we need for a static interface standard.
Yes the types need to be known at compile time. Implementing unresolved templates across dynamically linked resources is something that even C++ can not do. It would fundamentally require compiling at run time. Granted it could be interesting to have a standard that covers this, however templates functions that are not well defined at compile time is necessarily out of scope for a precompiled library, so we don’t have to address it here.
Nooo…. 🙂
Binding things through C interfaces is antithesis to what I’m saying! Even if you want to support the complex dynamic cases, we should have standards that support these features properly and NOT just be more unsafe hacks on top of C…. that does not and will never deliver the robust interfaces that the industry really should be moving towards.
Aflman,
Again, that is where ActiveX/OLE, DBUS comes in (or CORBA, gRPC, protobuf, SOAP, …)
All have various tradeoffs though.
sukru,
You bring up RPC, which is a relatively different topic as it involves out of process IO and marshaling objects into different address spaces. We can talk about this, but obviously the requirements and use cases for RPC are very different than what we were discussing earlier.
Most RPC implementations I’ve seen like SOAP do not structurally change the language, it’s just auto-generated code using the language’s existing primitives. To this end, I do think a modern function interface standard would be beneficial to RPC libraries. We’d be able to export and share RPC classes across languages with appropriate namespaces and OOP in shared library contexts without having to resort to wrapping modern language features behind C’s perpetually deficient and unsafe interfaces.
Alfman,
They don’t have to be “remote”. In fact most of them are entirely local
The primary discriminator is “marshaling” though. The data does not move verbatim, but goes through some processing. And in some cases there are cross-process object references (like IUnknown)
sukru,
I’m parsing “local” to mean on the same computer. A “remote procedure call” doesn’t necessarily mean a remote computer, it can obviously run services on the local host. What makes it RPC “remote” is that it’s out of process such that the caller and callee are in different address spaces. This is a very different use case than calling local functions in process.
You keep going back to dynamic typing, however there are compelling reasons for system programming languages to not be dynamically typed.
Consider languages like javascript or VB that support dynamic typing, one can pass a string, or float, or object, or array, etc to a function. In theory that function can look at the dynamic types at runtime and change behavior accordingly. In practice though most authors just have an assumed type in their head, they just don’t tell the compiler about it. The compiler has to be capable of letting variables hold all types. The lack of type specificity at compile time has a lot of consequences. It leads to sub-optimal structure layouts, bloated code paths with run-time checks, the existence of bizarre program states that violate the author’s assumptions of what a variable holds. Dynamically typed variables shifts the burden of type checking out of the compiler and onto the author’s plate. The language doesn’t prevent one from setting variables to the wrong type and in the majority of cases this will create runtime bugs.
This is why most system programmers favor static typing. Not only does it leads to more optimal code, but allowing the compiler to verify type information on our behalf is safer too.
In practice static languages can use static constructs without dynamic typing. Take “IUknown” in a a dynamically typed language, this typically equates to NULL in a statically typed language. A language like C# handles Nullable types extremely well and the programmers intent is conveyed to the compiler at compile time. Because of this, neither the compiler nor other programmers have to guess as to what a variable contains. The compiler enforces that all Null cases are handled, which leads to software being a lot more robust.
So while I find the dynamic typing is academically interesting, IMHO it’s generally a bad practice to use it for real system programming – we usually have better and safer alternatives that avoid the downfalls of dynamic typing.