sfntly Library Documentation

repository·main·Indexed 19 days ago

https://github.com/googlefonts/sfntly

A library for working with SFNT font files, currently in maintenance mode. It provides tools for font subsetting via a CLI tool, immutable Font object management using a Builder pattern, and utilities for reading TrueType/OpenType primitives through ReadableFontData. The library includes classes for character selection (CharacterPredicate), font metadata aggregation (FontInfo), and merging font subsets (Merger).

Tokens
12.5K
Snippets
42
Records
60
Agent score
67%

What's inside sfntly

  1. Overview of MicroType Express implementation in sfntly

    main

    The sfntly implementation of MicroType Express is currently an encoder only (compression only).

    Key implementation details:

    • Core Logic: Located in MtxWriter.java (within the tools/conversion/eot directory).
    • Supported Features: Implements the MicroType Express format including hdmx tables, push sequences, and jump coding.
    • Missing Features: The VDMX table (vertical device metrics) is not currently implemented, as it is rarely used in web fonts.
    • Algorithm: Uses an LZCOMP entropy coder for match finding.
  2. Understand the Table Building Pipeline

    main

    Building a data table follows a specific pipeline implemented via FontDataTable::Builder::build. The process generally follows these steps:

    1. Data Retrieval: The builder accesses ReadableFontData (via internalReadData()).
    2. Model Check: The system checks if the font model has changed. Accessing the model updates the underlying raw data.
    3. Serialization Check: The builder checks if the subtable is ready to be serialized (subReadyToSerialize()).
    4. Data Preparation: If ready, it calculates the required size (subDataToSerialize()), allocates WritableDataPtr, and performs the serialization (subSerialize).
    5. Table Construction: The final table is instantiated via subBuildTable(data), which adds the necessary table header.
  3. How subtables are lazily built

    main

    To optimize performance and memory, sfntly uses lazy building for subtables.

    Only tables at the font level are fully built by default. Other subtables (like individual CMaps within a CMapTable) are not initialized unless explicitly requested.

    Note for developers: If you call size() on a builder map, it may appear empty even if the font contains CMaps. To access them, you must use methods like getCMapBuilders(). However, high-level methods like numCMaps() will still report the correct count by inspecting the internal font data.

  4. Understand CMapTable and its formats

    main

    A CMapTable (Character Map table) converts code points from a code page into glyph IDs. A CMapTable is a container for multiple CMap objects, where each CMap represents a specific encoding supported by the font.

    Supported formats in the C++ port include:

    • CMapFormat0: A basic byte encoding table for 256 characters (e.g., ASCII or ISO 8859-x).
    • CMapFormat2: A high-byte mapping through table, used for 2-byte encodings like Shift-JIS (common in CJK languages).
    • CMapFormat4: A segment mapping to delta values. This is the preferred format for Unicode Basic Multilingual Plane (BMP) encodings. It uses binary search over sorted segments defined by startCode, endCode, idDelta, and idRangeOffset to find glyph IDs.
    • CMapFormat12: Supported, but specific details are not provided in this documentation.
  5. How Font Data Tables are created and managed

    main

    In sfntly, font data tables are designed for thread safety. To achieve this, tables are immutable once they are created.

    Users cannot instantiate tables directly; they must be created using their respective nested Builder class. This ensures that once a table is built, its state cannot be modified, making it safe for concurrent access.

  6. How smart pointers and ref-counting work in the sfntly C++ port

    main

    The sfntly C++ port uses a COM-like reference counting mechanism to manage object lifetimes.

    • RefCounted<T>: The base class for ref-countable objects. It provides addRef() and release() methods.
    • Ptr<T>: A smart pointer class used to hold RefCounted objects. It automatically handles incrementing and decrementing the reference count as the pointer is copied or goes out of scope.

    Key Lifecycle Methods

    • attach(ptr): Takes ownership of an existing pointer without incrementing the reference count.
    • detach(): Releases ownership of the managed object without decrementing the reference count (often used when returning a newly created object from a factory method).
    • Assignment (=): Bumps the reference count of the object being assigned to.
    • Scope Exit: When a Ptr<T> goes out of scope, it decrements the reference count. If the count reaches zero, the object is destroyed.
    class Foo : public RefCounted<Foo> {
     public:
      static Foo* CreateInstance() {
        Ptr<Foo> obj = new Foo();  // ref count = 1
        return obj.detach();       // Giving away the control of this instance.
      }
    };
    typedef Ptr<Foo> FooPtr;
    
    FooPtr obj;
    obj.attach(Foo::CreateInstance()); // ref count = 1 (takes over control without bumping)
    {
      FooPtr obj2 = obj;               // ref count = 2 (assignment bumps count)
    }                                 // ref count = 1 (obj2 out of scope)
    
    obj.release();                   // ref count = 0, object destroyed
  7. Build Environment Requirements for sfntly C++

    main

    To build the sfntly C++ port, ensure your environment meets the following requirements:

    • CMake: version 2.6 or above.
    • C++ Compiler:
      • Windows: Visual C++ 2008 or Visual C++ 2010.
      • Linux: g++ 4.3 or above (must support built-in atomic operations and include libstd++).
      • Mac: Apple Xcode 3.2.5 or above.
    • External Dependencies:
      • Google C++ Testing Framework (gTest): Tested with version 1.6.0. The package must be extracted to the ext directory and renamed or symbolic-linked to gtest.
      • ICU:
        • Linux: Use system default ICU headers (e.g., sudo apt-get install libicu-dev on Ubuntu).
        • Windows: Download from the ICU project site or extract from ext/redist. You must provide icudt.dll and may need to adjust include/library paths.
        • Mac: Download and install the ICU source tarball via standard ICU documentation.
  8. Build sfntly C++ on Windows

    main

    To build on Windows, follow these steps (assuming the source is at d:\src\sfntly):

    1. Prepare Dependencies: Extract cmake, gtest, and icu into their respective subdirectories in cpp/ext/ (e.g., d:\src\sfntly\cpp\ext\cmake, d:\src\sfntly\cpp\ext\gtest, and d:\src\sfntly\cpp\ext\icu).
    2. Generate Solution: Create a build directory inside cpp and run CMake.
    3. Configure Tests: Copy test fonts from d:\src\sfntly\cpp\data\ext\ to d:\src\sfntly\cpp\build\bin\Debug so the unit tests can access them.
    4. Build: Open sfntly.sln in Visual Studio and build. If icuuc.dll is not found, add d:\src\sfntly\cpp\ext\icu\bin to your system PATH.
    d:
    cd d:\src\sfntly\cpp
    md build
    cd build
    ..\ext\cmake\bin\cmake ..
  9. Configure Debug and Release builds

    main

    By default, sfntly builds in Debug mode. To switch to a Release build:

    • Linux/Mac: Set the CMAKE_BUILD_TYPE environment variable to Release before running cmake.
    • Windows: Switch the configuration directly within the Visual Studio IDE.
    export CMAKE_BUILD_TYPE=Release
    cmake ..
  10. Best practices and constraints for using smart pointers

    main

    To avoid crashes and memory leaks when using the sfntly C++ smart pointer system, follow these rules:

    • Heap Allocation Only: All RefCounted objects must be instantiated on the heap. Allocating them on the stack will cause a crash.
    • Inheritance Pattern: Avoid complex multiple inheritance with smart pointers. The preferred pattern is to have a common interface and specific implementations that both inherit from RefCounted separately:
      class I; // common interface
      class A : public I, public RefCounted<A>;
      class B : public I, public RefCounted<B>;
    • Function Parameters/Returns: Smart pointers are poor candidates for function parameters or return values. Use "dumb" (raw) pointers when passing objects over the stack.
    • Attach vs. Assign:
      • Use attach() when you want to take ownership of a pointer without incrementing its count (e.g., when receiving a pointer from a factory method).
      • Use assignment (=) when you want to share ownership with an existing smart pointer.
      • Check the function declaration to determine the correct idiom.