Text layout for Common Lisp: bidi paragraphs, line breaking and shaping into positioned glyphs.
  • Common Lisp 100%
Find a file
dxtr 17d3b36293
All checks were successful
CI / test (push) Successful in 1m23s
Keep a glyph atlas's glyphs by scale, freed with the last hold
A glyph atlas packs each font's glyphs into an atlas for each scale,
rather than one atlas for every scale, so that a scale's glyphs can go:
cl-atlas frees no single glyph.  HOLD-SCALE and RELEASE-SCALE count the
views that draw at a scale, and the last release frees every font's
atlas at it, as Godot 4.5 frees an oversampling level's glyphs with its
last reference; a scale never held keeps its glyphs as before.  Each
scale is a level with a cache key of its own, under which fonts keep
their atlas at it, forgotten with the last hold, or once the collector
takes the glyph atlas.  SCALE-HOLDS counts a scale's holds.

FONT-ATLAS takes :SCALE, and an atlas's data is the glyph atlas's own:
ATLAS-FONT and ATLAS-SCALE read the font and scale it holds glyphs of.
A glyph's key in its atlas is one fixnum, so finding it needs no key
on the stack, and finding a scale allocates nothing.

The glyph atlas rasterizes with the raster backend's new
RASTERIZE-GLYPH-AFRESH, which draws as RASTERIZE-GLYPH does into an
array the rasterizer does not keep, so that the atlas holds the only
copy of its images, and they go with it.

For Pyssel's step 25, as Kim decided on 2026-10-19.
2026-10-19 21:31:46 +02:00
.forgejo/workflows Run on SBCL only 2026-10-17 04:41:51 +02:00
atlas Keep a glyph atlas's glyphs by scale, freed with the last hold 2026-10-19 21:31:46 +02:00
raster Keep a glyph atlas's glyphs by scale, freed with the last hold 2026-10-19 21:31:46 +02:00
sdf Key what faces and fonts keep by cache keys that can be forgotten 2026-10-19 07:15:55 +02:00
src Lay a layout out again into its own storage 2026-10-19 21:31:46 +02:00
tests Keep a glyph atlas's glyphs by scale, freed with the last hold 2026-10-19 21:31:46 +02:00
.gitattributes Keep LF line endings in every checkout 2026-10-16 15:49:21 +02:00
.gitignore Pin the dependencies with stift in place of qlot 2026-10-16 10:55:17 +02:00
cl-textrender.asd Lay a layout out again into its own storage 2026-10-19 21:31:46 +02:00
CLAUDE.md Keep a glyph atlas's glyphs by scale, freed with the last hold 2026-10-19 21:31:46 +02:00
LICENSE Initial implementation of cl-textrender 2026-10-07 18:33:22 +02:00
README.org Keep a glyph atlas's glyphs by scale, freed with the last hold 2026-10-19 21:31:46 +02:00
stiftfile Move the pins on the workspace's libraries 2026-10-19 21:31:46 +02:00

cl-textrender

Text layout for Common Lisp, built on cl-icu and cl-harfbuzz: paragraphs, bidirectional text, line breaking and shaping, turned into lines of positioned glyphs, with hit testing for carets and selections.

Layout doesn't rasterize anything. It produces glyph IDs and unhinted positions in resolution-independent units, ready for a renderer that draws from outlines (MSDF, Slug, vector output) or from a glyph atlas. FreeType is not needed: HarfBuzz reads TrueType and OpenType (CFF) fonts itself.

Usage

(asdf:load-system :cl-textrender)

(defpackage #:example
  (:use #:cl)
  (:local-nicknames (#:tr #:cl-textrender)))
(in-package #:example)

(tr:with-font (font "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
                    :size 16)                   ; 16 layout units per em
  (let ((layout (tr:layout-text "Hello שלום world" font
                                :width 120 :align :start)))
    ;; Draw: every glyph, already placed.  Here "drawing" prints; your
    ;; renderer takes the glyph's font, ID and position the same way.
    (tr:do-glyphs (g layout)
      (format t "glyph ~D at ~,2F, ~,2F~%"
              (tr:glyph-id g) (tr:glyph-x g) (tr:glyph-y g)))
    ;; Edit: carets, clicks and selections.
    (print (list (multiple-value-list (tr:caret-position layout 6))
                                                ; => (81.046875 0.0 18.625): x, top, bottom
                 (tr:hit-test layout 40.0 10.0)         ; => 5, a text index
                 (tr:selection-rectangles layout 3 9)   ; => a list of RECTs
                 (tr:next-caret-index layout 6)))))     ; => 7, the next grapheme

The package has no global nickname, which would take a name from every package in the image: give it a local nickname in your own, as above. The examples below call it tr.

Faces and fonts

A face is a typeface: one face of a font file, by :index in a collection, with the :variations of a variable font it is drawn with, such as '(("wght" . 700)). A font is a face at a size, in layout units per em. make-face takes a file name, pathname or octet vector, and face-font gives the face at a size, the same font each time for one size. make-font makes a font with a face of its own, for when one size is all you need.

(let* ((face (tr:make-face "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"))
       (body (tr:face-font face 16))
       (title (tr:face-font face 32)))
  (list (tr:font-line-height body) (tr:font-line-height title)))
;; => (18.625 37.25)

font-glyph-id gives the glyph ID a font draws a character with, and font-glyph-advance a glyph's advance in layout units. Use them for single glyphs such as a caret or a bullet, and layout-text for text. A font's glyph IDs run from 0 below font-glyph-count. Every function that takes a glyph ID draws any other ID as the font's .notdef, glyph 0, as a font draws a character it lacks; font-glyph-or-notdef gives the ID that is drawn.

Faces and fonts hold foreign objects, which are freed once nothing holds them, as Godot frees a font once nothing refers to it. A font holds its face, a face the fonts it gave, and a layout its fonts, so no font is freed while text laid out with it is around. destroy-face and destroy-font free them at once, and with-face and with-font when their body is left; destroy-font frees the face make-font made with it too.

Renderers keep what they make from a face or font with it, as Godot's fonts keep their glyph caches, so that it goes with the font: face-cache and font-cache return what one keeps under its cache key, from make-cache-key, made the first time by a function, which may also return a function that releases its foreign or GPU parts when the face or font goes. A renderer that goes first forgets its key, so that faces and fonts that outlive it keep nothing of it, nor the renderer, which those functions may refer to:

(let ((key (tr:make-cache-key)))
  (tr:font-cache font key (lambda () (make-hash-table)))  ; made once, kept by FONT
  (tr:forget-cache-key key))                              ; FONT forgets it

The raster backend, the glyph atlas and the distance field atlas below keep theirs so, under keys forgotten once the collector takes them, and finding what was kept allocates nothing.

Styled text and font fallback

Wherever a font is taken, a list of fonts works too. The first is the primary, and later ones are fallbacks for characters it lacks:

(tr:layout-text text (list latin-font cjk-font emoji-font)
  :spans '((0 5 :data :red)                        ; anything, passed to the glyphs
           (6 12 :font bold-font)                  ; a font or a fallback list
           (13 20 :features ("smcp" "-liga"))))    ; OpenType features
  • Fallback. Each grapheme is drawn from the first font that has glyphs for all of it, so a letter is never split from its accents. A grapheme stays in the previous grapheme's font when that font has it, so spaces in CJK text don't switch back to the primary. Joiners and variation selectors don't count, and characters no font has show as the primary's .notdef.
  • Spans. (start end &key font features data). Later spans override earlier ones where they overlap, key by key. A span's data (a color, say) is on every glyph it covers (glyph-data) and on its runs (run-data).
  • Mixed metrics. Each glyph records the font it actually came from (glyph-font), and runs split wherever the font or style changes. Each line is as tall as the largest fonts on it: line-ascent and line-descent give the values used.

Outlines

For renderers that draw from outlines (MSDF, Slug), glyph-outline returns a glyph's outline. It is in layout units of the font's size, in font orientation (y up), relative to the glyph's origin on the baseline. Make a font of size 1 to get outlines in ems.

  • Segments. Contours are closed lists of segments with explicit start points: (:line x0 y0 x1 y1), (:quadratic x0 y0 cx cy x1 y1) and (:cubic x0 y0 c1x c1y c2x c2y x1 y1). TrueType fonts give quadratics; CFF (.otf) fonts give cubics.
  • Bounds. outline-xmin, -ymin, -xmax and -ymax bound every point, control points included.
  • Quadratic only. :quadratic t approximates cubics with quadratics, within :tolerance layout units (a thousandth of an em by default).
  • Orientation. TrueType glyphs run clockwise (outer contours; holes the other way) and CFF glyphs counterclockwise. outline-orientation tells which, from outline-signed-area, which is exact for all three segment kinds. orient-outline returns the outline in the direction you choose, for an MSDF generator that needs a consistent sign.
  • Packed for Slug. outline-quadratics packs an outline into one single-float vector with six floats per curve (x0 y0 cx cy x1 y1). Lines become quadratics with the control point at their middle.
(tr:cached-quadratics font glyph-id)   ; => #(x0 y0 cx cy x1 y1 ...)
(tr:cached-outline font glyph-id)      ; => OUTLINE, for MSDF

cached-outline and cached-quadratics compute each glyph once: the font keeps them, and frees them with itself. Finding one again allocates nothing, so a renderer can look glyphs up every frame, from any thread.

Units and axes

  • Units. A font's size is in layout units per em. Every position a layout returns is a single-float in those units: pixels for UI text, world units for text in a scene.
  • Y axis. :y-axis :down (the default) is screen space: the first line's top is at 0 and lines go down, with the baseline at the font's ascent. :y-axis :up is world space: every y coordinate is negated, so the text hangs below the origin. Hit testing takes coordinates in the same convention, and a rect's y is always its smallest coordinate.
  • Glyph outlines are always in font orientation (y up) relative to the glyph's origin on the baseline. An outline point (ox, oy) goes at (glyph-x + ox, glyph-y - oy) in a :down layout and at (glyph-x + ox, glyph-y + oy) in an :up layout. A glyph whose glyph-rotated-p is true (sideways text in a vertical layout) is turned 90° clockwise: the point goes at (glyph-x + oy, glyph-y + ox) in a :down layout and at (glyph-x + oy, glyph-y - ox) in an :up layout.

Layout

layout-text takes:

  • :width: the longest a line may be (the column height, in vertical text). Lines wrap at it; nil keeps each paragraph on one line, as long as it is.
  • :wrap: nil keeps each paragraph on one line within :width, cut short where it is longer, as below.
  • :align: :start, :end, :left, :right, :center or :justify. :start and :end follow each paragraph's direction.
  • :direction: :auto, :ltr or :rtl.
  • :line-spacing: a multiplier, with the extra space split above and below each line.
  • :tab-stops, :max-lines, :ellipsis and :orientation, below.
  • :spans, above, and :y-axis.
  • :into: a layout made before, laid out again in place and returned, its lines, runs and glyphs reused, for text that changes every frame, as a timer's. A simple line, without spans or tabs, left to right in one script beside digits and punctuation, each character a grapheme with a glyph in the first font, is laid out again so without allocating.

It works one paragraph at a time:

  1. Bidi. Paragraphs are split at newlines, and each resolves its own direction.
  2. Line breaking. Lines break at ICU's break opportunities, or between graphemes when a single word is wider than the line. Trailing spaces hang past the margin. With :wrap nil, lines break only where a break is mandatory.
  3. Reordering. Each line is reordered on its own (UAX #9).
  4. Shaping. Each line is cut into runs of one direction and one script, and each run is shaped with the whole paragraph as context.

A layout has lines, top to bottom. Each line has its text range, its paragraph direction, its baseline, and runs in visual order. Its line-box is the rectangle of its share of the layout: the layout's width by the line's height. Its line-content is the rectangle its visible text takes up: the same height, but only from its first glyph to its last, trailing spaces left out. Each run holds glyphs left to right, and its run-box is the rectangle the run takes up. All three are rect objects, whose x and y are their smallest coordinates.

Tabs, justification and ellipsis

  • Tabs. :tab-stops is an interval, or a list of increasing positions along the line, after which stops continue every eight spaces of the first font. The default is every eight spaces. Stops are measured from the line's start edge, so from the right in right-to-left paragraphs. A tab is drawn as a space glyph.
  • Justification. :align :justify widens the spaces of every line except a paragraph's last line and lines ended by a hard break, so they fill :width exactly. Lines without spaces stay at the start.
  • Ellipsis. :max-lines limits the number of lines; paragraphs count as lines. Text that doesn't fit is left out, and the last kept line ends in :ellipsis ("…" by default; nil for none). The line is cut at a grapheme boundary so the ellipsis fits within :width, and the ellipsis goes at the line's logical end: the right in left-to-right text, the left in right-to-left text. It takes the style of the last character kept, and each of its graphemes comes from that style's fallback list, as text does. layout-truncated-p and line-truncated-p report it. The ellipsis is a run, or one run per font, whose start equals its end. It has no caret stops, and indexes in the hidden text map to the end of the last line.
  • Cutting lines short. :wrap nil keeps each paragraph on one line, and cuts a line longer than :width at the last grapheme boundary where it fits with the :ellipsis after it, as a cell of a table or a tree cuts its text. Each paragraph is cut on its own, and with :ellipsis nil a line keeps all that fits. line-truncated-p is true for a line cut short, which covers only the text it shows: the text cut off belongs to no line, and a caret in it is at the cut, before the ellipsis. Wrapping with :max-lines 1 cuts at a word instead:

    (flet ((shown (layout)                  ; the text of its first line
             (let ((line (aref (tr:layout-lines layout) 0)))
               (subseq (tr:layout-string layout)
                       (tr:line-start line) (tr:line-end line)))))
      (list (shown (tr:layout-text "Node number 12" font :width 100 :wrap nil))
            (shown (tr:layout-text "Node number 12" font :width 100 :max-lines 1))))
    ;; => ("Node num" "Node"), each drawn with "…" after it

Vertical text

:orientation :vertical sets text in columns, read top to bottom, with columns running right to left, as in traditional Chinese and Japanese.

  • Upright or sideways. Each character's Unicode Vertical_Orientation (UTR #50) decides how it is set. CJK and other upright characters stand upright, and are shaped top to bottom so the font's vertical forms (its vert feature) apply. The rest (Latin, Arabic, …) are turned 90° clockwise and marked glyph-rotated-p.
  • Lines are columns. layout-width is the total width of the columns and layout-height the column length. A column's line-box is the whole column, its line-content the stretch of it its text takes up, and its baseline the x of its central baseline.
  • The same features apply. :width is the column height, and :align works along it (:start is the top). Hit testing, selections and caret movement work along the columns.

Hit testing

  • Caret stops are grapheme boundaries, so a caret never splits a letter from its accents, or an emoji sequence.
  • Carets in ligatures are spread evenly across the ligature.
  • caret-position draws the caret at index i at the leading edge of character i: its left edge in left-to-right text, its right edge in right-to-left text, its top in vertical text. It returns the position along the line and the line's two edges across it. caret-segment returns the same caret as a segment x0 y0 x1 y1.
  • hit-test clamps to the nearest line and character. On a wrapped line, a point past the end stays on that line.
  • selection-rectangles returns one rectangle per contiguous visual span, so selections across bidi runs come out in pieces.
  • move-caret moves the caret like an editor's arrow keys, with :left, :right, :up or :down.

    • Along a line, it moves visually. It steps one grapheme at a time through bidi text in screen order, and continues onto the next or previous line at a line's end.
    • Across lines, it keeps a goal. It goes to the caret stop on the adjacent line nearest a goal position, and returns the goal as a second value. Pass it back on the next move, so moving through a short line doesn't lose the column.
    • In vertical text the axes swap: up and down move along a column, left and right between columns.
    • Logical moves: next-caret-index and previous-caret-index move in logical order instead.

Raster backend

The optional cl-textrender/raster system draws layouts on the CPU with FreeType, through cl-ft2. It's useful for debugging, images, and small text where outline-based GPU rendering looks soft.

(asdf:load-system :cl-textrender/raster)

(let ((r (cl-textrender/raster:make-rasterizer)))         ; :hinting :light or :none
  (cl-textrender/raster:render-layout r layout :scale 2)  ; => 2D octet coverage image
  (cl-textrender/raster:map-layout-glyphs
   (lambda (coverage x y glyph)           ; composite yourself, e.g. in the span's color:
     (blend coverage x y (tr:glyph-data glyph)))     ; BLEND is yours
   r layout :scale 2 :x 10 :y 20))

Coverage is a 2D array of octets indexed by row, then column. map-layout-glyphs passes each glyph's coverage with the whole pixel column and row of its top-left corner, for a layout drawn at :scale pixels per layout unit with its top left at :x, :y. rasterize-glyph draws one glyph and returns its coverage, plus the offsets of its top-left corner from the glyph's origin; rasterize-glyph-afresh draws it into a new array the rasterizer does not keep, for a cache of your own.

  • Hinting. FreeType's light hinting aligns glyphs to the pixel grid vertically only, so the layout's unhinted advances still hold. :none turns hinting off.
  • Subpixel positioning. Each glyph is drawn at the nearest quarter of a pixel horizontally, and its bitmaps are cached per offset. Drawing a layout whose glyphs were drawn before allocates nothing.
  • Orientation. Images and map-layout-glyphs positions grow downwards from the layout's top, whether it was laid out :down or :up. Rotated glyphs in vertical text are drawn turned.
  • Kept with the font. Each face keeps a FreeType library and face of its own for the rasterizer, set to each glyph's size as it is drawn, and each font keeps its glyph images; both go with them, or with the rasterizer once the collector takes it. A rasterizer holds nothing foreign, so it needs no releasing.
  • Threads. A rasterizer may be used from any number of threads at once. FreeType draws with a face from one thread at a time, so the glyphs of one face are drawn one at a time.
  • Color glyphs. Color bitmap glyphs give their alpha as coverage.

Glyph atlas

The optional cl-textrender/atlas system is for renderers that draw text from a texture: it rasterizes each glyph once, with cl-textrender/raster, and packs it into a cl-atlas atlas of one byte of coverage a texel, whose pages the renderer uploads. Each font has an atlas of its own for each scale, which it keeps, as Godot's fonts keep their glyph textures, and which goes with it, or with the last view that draws at its scale.

(asdf:load-system :cl-textrender/atlas)

(let ((glyphs (cl-textrender/atlas:make-glyph-atlas))   ; :page-size, :hinting
      (quads '()))
  (cl-textrender/atlas:map-layout-glyphs
   (lambda (region x y glyph)      ; draw REGION of its page with its top left at X, Y
     (declare (ignore glyph))
     (push (list (cl-atlas:page-index (cl-atlas:region-page region)) x y) quads))
   glyphs layout :scale 2 :x 10 :y 20)
  (nreverse quads))
;; => ((0 13 27) (0 37 26)) for "Hi" in DejaVu Sans at size 16

map-layout-glyphs gives each inked glyph's region and the whole pixel where the region's top left goes, for a layout drawn at :scale pixels per layout unit with its top left at :x, :y; positions are those the raster backend's map-layout-glyphs gives, a quarter of a pixel horizontally. glyph-region looks up one glyph, and glyph-image-offset says where its image sits relative to its origin. Once a layout's glyphs are packed, map-layout-glyphs allocates nothing, so a renderer can call it every frame. font-atlas is a font's cl-atlas atlas at a :scale, :page-size texels square (512 by default): before drawing, upload what cl-atlas:take-page-changes reports of the pages you draw from. atlas-font and atlas-scale tell what an atlas holds, so a renderer holding one of its pages reaches the font, and keeps it alive.

A view that draws at a scale holds it with hold-scale, and releases it with release-scale when it draws at another or goes. Holds are counted, and the last release frees every font's atlas at that scale, as Godot 4.5 frees an oversampling level's glyphs with its last reference, so a zoom no view draws at any more keeps no glyphs:

(cl-textrender/atlas:hold-scale glyphs 2)      ; a view starts drawing at 2
;; ...draw with :scale 2...
(cl-textrender/atlas:release-scale glyphs 2)   ; => T: the scale's glyphs went

A glyph atlas may be used from any number of threads at once; release a scale while no thread asks for glyphs at it. What it makes is kept with the fonts it draws, and goes with them, with the last hold on its scale, or with the glyph atlas once the collector takes it, so it holds nothing foreign and needs no releasing.

Distance field atlas

The optional cl-textrender/sdf system is for renderers that draw text from distance fields, which stay sharp over a range of sizes and give outlines, glows and shadows cheaply: it makes each glyph into a multi-channel true signed distance field (MTSDF) once, with cl-sdf, and packs it into a cl-atlas atlas of four bytes a texel, whose pages the renderer uploads as RGBA8 textures. Each face has an atlas of its own, which it keeps and which goes with it.

(asdf:load-system :cl-textrender/sdf)

(let* ((fields (cl-textrender/sdf:make-field-atlas))  ; :page-size, :texels-per-em, :spread
       (region (cl-textrender/sdf:glyph-region fields font (tr:font-glyph-id font #\A))))
  (list (cl-atlas:region-width region) (cl-atlas:region-height region)
        (multiple-value-list (cl-textrender/sdf:glyph-field-bounds region))))
;; => (38 40 (-0.2421875 -0.25 0.9453125 1.0)) for DejaVu Sans

Fields are made in ems, :texels-per-em texels to the em (32 by default), so one field serves every size of a face: the fonts face-font gives of one face share them. face-atlas is a face's cl-atlas atlas, :page-size texels square (512 by default), and its atlas-data the face. glyph-field-bounds gives the rectangle of ems a region covers, y up from the glyph's origin, which reaches :spread ems (a quarter by default) beyond the outline on every side. Red, green and blue hold the multi-channel distance, whose median draws the glyph with sharp corners; alpha holds the true distance, for effects. Each channel maps -spread ems to 0, the edge to 0.5 and +spread ems to 1, positive inside.

To draw a layout, map-layout-fields gives each inked glyph's region and the quad its field goes on, in pixels: the corner of the field's first texel, the vector its rows run along and the vector its columns run down. Draw the region's texture coordinates on that quad. Glyphs turned in vertical text come with their quads turned, so they need no special case:

(let ((quads '()))
  (cl-textrender/sdf:map-layout-fields
   (lambda (region x y ux uy vx vy glyph)       ; from X, Y along U and down V
     (declare (ignore region glyph))
     (push (list x y ux uy vx vy) quads))
   fields layout :scale 2 :x 10 :y 20)
  (nreverse quads))
;; => ((5.140625 17.703125 34.0 0.0 0.0 40.0)
;;     (29.078125 16.703125 19.0 0.0 0.0 41.0)) for "Hi" in DejaVu Sans at size 16

Making a field takes a few milliseconds, so a renderer that must not stall calls glyph-region on worker threads first: a field atlas may be used from any number of threads at once, and fields for different glyphs are made in parallel. Finding a field made before allocates nothing. What a field atlas makes is kept with the faces it draws, and goes with them, or with the field atlas once the collector takes it, so it needs no releasing.

Errors

(handler-case (tr:layout-text "abc" font :spans '((2 9 :data :red)))
  (tr:textrender-error (e)
    (princ-to-string e)))
;; => "The span (2 9 :DATA :RED) ends at 9, past the end of the text, which has 3 characters.
;;     A span is (START END &key FONT FEATURES DATA), where START and END are
;;     character indexes with 0 <= START <= END <= the length of the text."

cl-textrender signals textrender-error for every failure: a font file that cannot be read, a destroyed face or font, a span or index outside the text, an OpenType feature that cannot be parsed. A glyph ID a font does not have is no failure: it is drawn as the font's .notdef. Errors from the libraries underneath are reported in cl-textrender's terms, so you never need to handle a cl-harfbuzz or cl-ft2 condition. The report says what went wrong, then what to do; textrender-error-message and textrender-error-hint return the two parts.

An argument of the wrong type, or a keyword argument that is not one of the documented choices, signals a standard type-error as the function is entered: every exported function declares its arguments' types, which SBCL checks before the function runs. A constant of the wrong type is caught sooner, as SBCL compiles the call: it warns that the constant conflicts with the declared type.

Not yet covered

  • Hyphenation.
  • Justification without spaces. Inter-character justification (CJK) and kashida stretching (Arabic) are not included.
  • Finer vertical-text details. Upright and transformed characters are shaped top to bottom: a Tr character the font has no vertical form for stands upright rather than turning. Short horizontal runs inside a column (tate-chū-yoko, such as two-digit numbers) are not set specially.
  • Color glyphs in the raster backend. Color bitmap glyphs draw as their alpha only.

Development

Dependencies are pinned in the stiftfile with stift, and tests use parachute.

stift test

The tests need HarfBuzz, ICU, FreeType, DejaVu Sans, URW Bookman and Droid Sans Fallback (Debian/Ubuntu: libharfbuzz0b libicu-dev libfreetype6 fonts-dejavu-core fonts-urw-base35 fonts-droid-fallback). To use the fonts from other locations, set CL_TEXTRENDER_TEST_FONT, CL_TEXTRENDER_TEST_CFF_FONT and CL_TEXTRENDER_TEST_CJK_FONT to their paths.

License

MIT. See LICENSE.