Standard library · Graphics and games

gui.typeset

Text for the native UI: fonts, fallback, OpenType shaping, bidi, line breaking, paragraph layout, glyph masks and caret geometry.

import gui.typeset

This package turns styled text into lines of positioned glyphs and draws those glyphs as coverage masks. It is the text half of the native UI runtime: the page language uses it to lay out every heading, paragraph and input, and a painter blends its masks into pixels. It is written in Salam with no outside library, and Persian and mixed Persian and English text are the main case it is built for.

The steps are the ones a browser takes. A FontCollection holds fonts, picks a face for every character from a family list like CSS font-family and falls back per script to other faces, the system fonts included. NewParagraph splits styled spans into bidi, script and font runs and shapes each run once with the font's OpenType tables: joining forms, ligatures, kerning and mark positioning. LayoutParagraph breaks lines at any width without shaping again, reorders each line for right to left text and aligns it. RasterizeGlyph and GlyphCache give 8 bit masks, and HitTest, CaretRect and SelectionRects give the geometry an editor needs.

Things to know

  • TrueType outlines only. Fonts with CFF outlines (most .otf files) are skipped by the collection and refused by LoadFont.
  • Offsets are UTF-8 byte offsets into Paragraph.text, the span texts joined together. SpanAt maps an offset back to its span, and every glyph run carries the tag of its span.
  • Layout math is fixed point in 1/64 pixel and masks are made with integer math, so the numbers and the pixels are the same on every OS and backend.
  • When a font has no Arabic joining features the shaper uses the Unicode presentation forms instead, like std/ai/image always did; that package now uses this one for its fonts.
  • A mask from CachedGlyph belongs to the cache and stays valid until a later call may evict it; draw it before asking for more.
  • The examples read fonts from the books/fonts folder of the Salam repository.

Lay out a mixed Persian and English paragraph

Each span has its own family list and size; the tag lets you find which element a run came from. The paragraph is shaped once, then laid out at two widths. In this right to left paragraph the English word keeps its own left to right order inside the line, and every line is aligned to the right, the start side.

typeset-guide-paragraph.salam
import gui.typeset
import os
import filepath

func main:
    fonts := filepath.Join(filepath.Dir(os.Env("SALAM_STD")), "books/fonts/")
    mut fc := typeset.NewFontCollection()
    typeset.AddFontFile(fc, fonts + "XB Zar.ttf")
    typeset.AddFontFile(fc, fonts + "DejaVuSansMono.ttf")
    mut spans := Vector {} as Vector<typeset.TextSpan>
    spans.push(typeset.TextSpan { text = "زبان برنامه‌نویسی ", families = "XB Zar", size = 20.0, tag = 1 })
    spans.push(typeset.TextSpan { text = "Salam", families = "DejaVu Sans Mono", size = 16.0, tag = 2 })
    spans.push(typeset.TextSpan { text = " متن فارسی را درست می‌چیند.", families = "XB Zar", size = 20.0, tag = 3 })
    mut p := typeset.NewParagraph(fc, spans, typeset.ParagraphStyle {})
    println "min", typeset.MinContentWidth(p), "max", typeset.MaxContentWidth(p)
    each width in [400.0, 160.0]:
        mut l := typeset.LayoutParagraph(p, fc, width)
        println "width", width, "lines", l.lines.len(), "height", l.height
        each line in l.lines:
            print "  baseline ", line.baseline, ":"
            mut k := line.run_start
            until k < line.run_start + line.run_count:
                run := l.runs.get(k)
                if run.glyph_count > 0:
                    print " [tag ", run.tag, " x ", run.x, (run.rtl ? " rtl" : " ltr"), "]"
                end
                k++
            end
            println ""
        end
        typeset.FreeLayout(l)
    end
    typeset.FreeParagraph(p)
    spans.free()
    typeset.FreeFontCollection(fc)
end
Outputmin 77.546875 max 377.84375 width 400 lines 1 height 30.28125 baseline 20.515625 : [tag 3 x 22.15625 rtl ] [tag 3 x 222.734375 rtl ] [tag 2 x 234.78125 ltr ] [tag 1 x 282.984375 rtl ] width 160 lines 3 height 90.84375 baseline 20.515625 : [tag 1 x 48.3125 rtl ] baseline 50.796875 : [tag 3 x 10.421875 rtl ] [tag 3 x 99.75 rtl ] [tag 2 x 111.796875 ltr ] baseline 81.078125 : [tag 3 x 54.078125 rtl ]

MinContentWidth and MaxContentWidth are what a flex or table layout needs before it decides the width. Changing only the width calls LayoutParagraph again; the fonts and the shaping stay cached in the paragraph.

Caret and selection in a text input

An input keeps a TextPosition: a byte offset and whether it sticks to the text before it. HitTest turns a click into a position, MoveVisual moves it as the arrow keys do on screen, and CaretRect says where to draw it. On a line that mixes directions the caret walks through the English letters from right to left of the screen, the way the eye follows them.

typeset-guide-caret.salam
import gui.typeset
import os
import filepath

func main:
    mut fc := typeset.NewFontCollection()
    typeset.AddFontFile(fc, filepath.Join(filepath.Dir(os.Env("SALAM_STD")), "books/fonts/XB Zar.ttf"))
    mut spans := Vector {} as Vector<typeset.TextSpan>
    spans.push(typeset.TextSpan { text = "سلام ABC دنیا", families = "XB Zar", size = 20.0 })
    mut st := typeset.ParagraphStyle {}
    st.white_space = typeset.WhiteSpace.PreWrap
    mut p := typeset.NewParagraph(fc, spans, st)
    mut l := typeset.LayoutParagraph(p, fc, 0.0)
    mut pos := typeset.HitTest(p, l, l.width, 10.0)
    println "click at the right edge: offset", pos.offset
    repeat 6:
        pos = typeset.MoveVisual(p, l, pos, false)
        caret := typeset.CaretRect(p, l, pos)
        println "left arrow: offset", pos.offset, "caret x", caret.x
    end
    rects := typeset.SelectionRects(p, l, 6, 15)
    println "selection of 'م ABC د' needs", rects.len(), "rectangles"
    rects.free()
    typeset.FreeLayout(l)
    typeset.FreeParagraph(p)
    spans.free()
    typeset.FreeFontCollection(fc)
end
Outputclick at the right edge: offset 0 left arrow: offset 2 caret x 95.625 left arrow: offset 4 caret x 89.140625 left arrow: offset 6 caret x 82.640625 left arrow: offset 8 caret x 74.65625 left arrow: offset 9 caret x 69.328125 left arrow: offset 11 caret x 55.25 selection of 'م ABC د' needs 1 rectangles

The selection of a range that crosses a direction change can need more than one rectangle; here it stays one because the selected pieces sit next to each other on screen. NextGrapheme and NextWordEnd give the logical moves for Backspace, Delete and Ctrl with an arrow key.

Types

FontFace

struct FontFace
One face of a font collection; system faces load on first use.
NameTypeDescription
pathstr = ""The file the face comes from, empty for bytes.
indexint = 0The face number inside the file.
familystr = ""The family name.
stylestr = ""The style name.
weightint = 400The weight.
italicbool = falseThe face is italic.
loadedbool = falseThe font data is in memory.
failedbool = falseLoading the file failed; the face is skipped.
fontFontThe loaded font.
gui-typeset-Fonts.salam
import gui.typeset
import os
import filepath
import archive
import compress

func font_path(name: str): str:
    ret filepath.Join(filepath.Dir(os.Env("SALAM_STD")), "books/fonts/" + name)
end

func main:
    mut ok := false
    mut f := typeset.LoadFont(font_path("XB Zar.ttf"), ok)
    m := typeset.Metrics(f)
    println typeset.FamilyName(f), typeset.StyleName(f), typeset.FullName(f), typeset.NameString(f, 1)
    println typeset.Weight(f), typeset.IsItalic(f), typeset.UnitsPerEm(f), typeset.NumGlyphs(f), typeset.FontValid(f)
    println m.ascent, m.descent, m.line_gap, m.x_height, m.cap_height, m.underline_position, m.underline_thickness, m.units_per_em
    println typeset.ScaleFactor(f, 20.48), typeset.AscentPixels(f, 20.48), typeset.DescentPixels(f, 20.48), typeset.LineHeightPixels(f, 20.48)
    beh := typeset.GlyphIndex(f, 0x0628)
    mut x0 := 0
    mut y0 := 0
    mut x1 := 0
    mut y1 := 0
    println beh, typeset.GlyphAdvance(f, beh), typeset.GlyphBox(f, beh, x0, y0, x1, y1), x0, y0, x1, y1, typeset.GlyphHasOutline(f, beh)
    println typeset.KernAdvance(f, beh, beh)
    typeset.FreeFont(f)
    mut bok := false
    mut buf := archive.ReadBinaryFile(font_path("XB Zar Bd.ttf"), bok)
    println typeset.FaceCount(buf.data, buf.len), typeset.IsCFFFont(buf.data, buf.len)
    mut pok := false
    bold := typeset.ParseFontIndex(buf.data, buf.len, 0, pok)
    mut fc := typeset.NewFontCollection()
    typeset.AddFont(fc, bold)
    typeset.AddFontBytes(fc, buf.data, buf.len)
    compress.BufFree(buf)
    typeset.AddFontFile(fc, font_path("XB Zar.ttf"))
    typeset.AddFontFile(fc, font_path("DejaVuSansMono.ttf"))
    println typeset.FaceCountIn(fc), typeset.FindFace(fc, "XB Zar", 400, false), typeset.FindFace(fc, "xb zar", 700, false)
    println typeset.FaceForCodepoint(fc, "DejaVu Sans Mono, XB Zar", 400, false, 0x41), typeset.FaceForCodepoint(fc, "monospace", 400, false, 0x06F4)
    face := fc.faces.get(2)
    println face.family, face.style, face.weight, face.italic, typeset.Weight(typeset.FaceFont(fc, 0))
    typeset.FreeFontCollection(fc)
    mut lf := typeset.LoadFontIndex(font_path("XB Zar It.ttf"), 0, ok)
    println typeset.IsItalic(lf)
    typeset.FreeFont(lf)
    mut more := typeset.NewFontCollection()
    println typeset.AddFontDir(more, filepath.Dir(font_path("x"))) > 90
    dirs := typeset.SystemFontDirs()
    println dirs.len() > 0, typeset.AddSystemFonts(more) >= 0
    dirs.free()
    typeset.FreeFontCollection(more)
    mut pf := typeset.ParseFont(null, 0, ok)
    println ok
    typeset.FreeFont(pf)
end
OutputXB Zar Regular XB Zar XB Zar 400 false 2048 949 true 2100 1000 0 934 1393 -570 60 2048 0.01 21 -10 31 358 1771 true 0 -507 1603 823 true 0 1 false 4 2 0 3 3 XB Zar Regular 400 false 700 true true true true false

FontCollection

struct FontCollection
A set of faces used for font matching and fallback.
NameTypeDescription
facesVectorThe faces in the order they were added.
fallbackHashMapCached fallback answers per character.
memoHashMapCached face choices per family list, weight, style and character, so shaping a page does not match family names again for every character.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

ShapedGlyph

struct ShapedGlyph
One glyph out of the shaper.
NameTypeDescription
idint = 0The glyph id.
clusterint = 0The UTF-8 byte offset of the text it came from.
x_advanceint = 0The advance in font units.
x_offsetint = 0The horizontal offset in font units.
y_offsetint = 0The vertical offset in font units, up is positive.
gui-typeset-Shape.salam
import gui.typeset
import os
import filepath

func main:
    mut ok := false
    mut f := typeset.LoadFont(filepath.Join(filepath.Dir(os.Env("SALAM_STD")), "books/fonts/XB Zar.ttf"), ok)
    glyphs := typeset.ShapeText(f, "می‌خواهم", true, "", "fa")
    each g in glyphs:
        println g.id, g.cluster, g.x_advance, g.x_offset, g.y_offset
    end
    glyphs.free()
    println typeset.ShapeArabic("سلام")
    println typeset.IsRTLText("سلام"), typeset.BaseDirection("Salam سلام") == typeset.TextDirLTR
    println typeset.ToVisualOrder("abc سلام", typeset.TextDirAuto)
    cps := typeset.VisualCodepoints("لا", typeset.TextDirRTL, true)
    println cps.len(), cps.get(0)
    cps.free()
    typeset.FreeFont(f)
end
Output444 15 871 0 0 453 13 1040 0 0 356 11 465 0 0 456 9 764 0 0 382 7 1344 0 0 3 4 0 0 0 883 2 1069 0 0 445 0 836 0 0 ﺳﻼﻡ true true abc ﻡﻼﺳ 1 65275

TextAlign

enum TextAlign
How lines sit in the box, like CSS text-align.
NameTypeDescription
StartAt the start side of the paragraph direction.
EndAt the end side.
LeftLeft.
RightRight.
CenterCentered.
JustifySpaces stretch so lines fill the width, except the last line.
gui-typeset-Layout.salam
import gui.typeset
import os
import filepath

func main:
    fonts := filepath.Join(filepath.Dir(os.Env("SALAM_STD")), "books/fonts/")
    mut fc := typeset.NewFontCollection()
    typeset.AddFontFile(fc, fonts + "XB Zar.ttf")
    typeset.AddFontFile(fc, fonts + "DejaVuSansMono.ttf")
    mut spans := Vector {} as Vector<typeset.TextSpan>
    spans.push(typeset.TextSpan { text = "قیمت این کتاب ", families = "XB Zar", size = 18.0, tag = 1, lang = "fa" })
    spans.push(typeset.TextSpan { text = "12 USD", families = "DejaVu Sans Mono", size = 14.0, tag = 2, letter_spacing = 0.5 })
    spans.push(typeset.TextSpan { text = " است.", families = "XB Zar", size = 18.0, tag = 3, word_spacing = 2.0, line_height = 30.0, line_height_scale = 0.0 })
    mut st := typeset.ParagraphStyle {}
    st.align = typeset.TextAlign.Justify
    st.direction = typeset.TextDirAuto
    st.white_space = typeset.WhiteSpace.Normal
    st.overflow_wrap = typeset.OverflowWrap.BreakWord
    st.max_lines = 3
    st.ellipsis = true
    st.lang = "fa"
    mut p := typeset.NewParagraph(fc, spans, st)
    println typeset.MinContentWidth(p), typeset.MaxContentWidth(p), p.min_content, p.max_content
    println p.text, p.style.max_lines, p.spans.len(), p.span_starts.get(1), typeset.SpanAt(p, 30)
    c := p.chars.get(0)
    println c.offset, c.cp, c.span, c.level, c.base, c.flags, c.advance, c.brk, c.face
    r := p.runs.get(0)
    println r.cp_start, r.cp_end, r.glyph_start, r.glyph_end, r.level, r.face, r.span, r.size64
    g := p.glyphs.get(0)
    println g.id, g.cp, g.advance, g.x_offset, g.y_offset
    mut l := typeset.LayoutParagraph(p, fc, 110.0)
    println l.width, l.height, l.max_width, l.truncated, l.lines.len()
    each lb in l.lines:
        println lb.start, lb.end, lb.cp_start, lb.cp_end, lb.trailing_cp, lb.top, lb.baseline, lb.height, lb.ascent, lb.descent, lb.x, lb.width, lb.run_start, lb.run_count, lb.hard_break, lb.rtl, lb.space_extra
    end
    each run in l.runs:
        println run.face, run.size, run.x, run.width, run.start, run.end, run.cp_start, run.cp_end, run.rtl, run.level, run.tag, run.span, run.glyph_start, run.glyph_count, run.ellipsis
    end
    pg := l.glyphs.get(0)
    println pg.id, pg.x, pg.y, pg.cluster, pg.advance
    typeset.FreeLayout(l)
    typeset.FreeParagraph(p)
    spans.free()
    typeset.FreeFontCollection(fc)
end
Output34.828125 197.453125 2229 12637 قیمت این کتاب 12 USD است. 3 3 25 1 0 1602 0 1 1 20 417 0 0 0 14 0 14 1 0 0 1152 433 0 417 0 0 110 58.640625 110 false 2 0 25 0 14 13 0 18.453125 27.25 18.453125 8.796875 0 110 0 2 false true 417 25 39 14 25 25 27.25 48.46875 31.390625 21.21875 10.171875 14.3125 95.6875 2 5 false true 0 0 18 0 0 24 25 13 14 true 1 1 0 0 0 false 0 18 0 110 0 24 0 13 true 1 1 0 0 13 false 0 18 14.3125 34.453125 32 39 21 25 true 1 3 2 13 4 false 1 18 48.765625 12.84375 31 32 20 21 true 1 3 2 17 1 false 1 14 61.609375 26.765625 28 31 17 20 false 2 2 1 18 3 false 0 14 88.375 4.21875 27 28 16 17 true 1 2 1 21 1 false 0 14 92.59375 17.40625 25 27 14 16 false 2 2 1 22 2 false 358 0 18.453125 22 15.5625

WhiteSpace

enum WhiteSpace
How spaces and new lines are treated, like CSS white-space.
NameTypeDescription
NormalSpaces collapse, new lines are spaces, lines wrap.
NoWrapLike normal but lines never wrap.
PreSpaces and new lines are kept, no wrapping.
PreWrapSpaces and new lines are kept and lines wrap; trailing spaces hang.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

OverflowWrap

enum OverflowWrap
Whether a word longer than the line may be split, like CSS overflow-wrap.
NameTypeDescription
NormalLong words overflow.
AnywhereSplit at any letter; min-content counts single letters.
BreakWordSplit only when needed; min-content keeps whole words.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

TextSpan

struct TextSpan
A piece of text with one style.
NameTypeDescription
textstr = ""The text, UTF-8.
familiesstr = "sans-serif"A family list as in CSS font-family, such as "Vazirmatn, sans-serif".
sizef64 = 16Font size in pixels.
weightint = 400Font weight, 400 regular.
italicbool = falseAsk for an italic face.
letter_spacingf64 = 0Extra pixels after every letter; turns off optional ligatures.
word_spacingf64 = 0Extra pixels after every space.
line_heightf64 = 0Line height in pixels; 0 means normal.
line_height_scalef64 = 0Line height as a multiple of the font size, used when line_height is 0.
tagint = 0Your own number, copied to every glyph run so you can map runs back to elements.
langstr = ""Language such as fa, used to pick the font's language rules.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

ParagraphStyle

struct ParagraphStyle
Settings of a whole paragraph.
NameTypeDescription
alignTextAlignLine alignment.
directionint = 0Base direction: TextDirAuto, TextDirLTR or TextDirRTL.
white_spaceWhiteSpaceSpace handling.
overflow_wrapOverflowWrapLong word handling.
max_linesint = 0Keep at most this many lines; 0 means no limit.
ellipsisbool = falseCut overflowing or hidden text with an ellipsis on the end side.
langstr = ""Language for spans that do not set one.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

CharInfo

struct CharInfo
What the paragraph knows about one character.
NameTypeDescription
offsetint = 0UTF-8 byte offset in the paragraph text.
cpint = 0The code point.
spanint = 0The span it belongs to.
levelint = 0The bidi embedding level; odd is right to left.
baseint = 0The paragraph level of its bidi paragraph.
flagsint = 0Internal bits: hidden, hard break, grapheme start, space, word letter.
advanceint = 0Its share of the shaped width, in 1/64 pixel.
brkint = 0Line break before it: 0 none, 1 allowed, 2 required.
faceint = -1The face chosen for it in the collection.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

ShapedRun

struct ShapedRun
Characters shaped together: one span, face, script and bidi level.
NameTypeDescription
cp_startint = 0First character index.
cp_endint = 0One past the last character index.
glyph_startint = 0First glyph in the paragraph's glyphs.
glyph_endint = 0One past its last glyph.
levelint = 0Its bidi level.
faceint = -1Its face.
spanint = 0Its span.
size64int = 0Font size in 1/64 pixel.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

ParagraphGlyph

struct ParagraphGlyph
A shaped glyph in logical order, in 1/64 pixel.
NameTypeDescription
idint = 0The glyph id.
cpint = 0The character index of its cluster.
advanceint = 0Advance with letter and word spacing.
x_offsetint = 0Horizontal offset.
y_offsetint = 0Vertical offset, up is positive.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

Paragraph

struct Paragraph
Styled text after itemizing and shaping; lay it out at any width without shaping again.
NameTypeDescription
textstr = ""All span texts joined; offsets point into it.
styleParagraphStyleThe paragraph style.
spansVectorThe spans.
span_startsVectorThe byte offset where each span starts.
charsVectorOne entry per code point.
runsVectorThe shaped runs in logical order.
glyphsVectorAll shaped glyphs.
min_contentint = 0The min-content width in 1/64 pixel.
max_contentint = 0The max-content width in 1/64 pixel.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

PlacedGlyph

struct PlacedGlyph
A glyph ready to draw.
NameTypeDescription
idint = 0The glyph id.
xf64 = 0The pen x of the glyph in pixels.
yf64 = 0The baseline y with the glyph offset, in pixels.
clusterint = 0Byte offset of the text it shows.
advancef64 = 0Its advance in pixels.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

GlyphRun

struct GlyphRun
Glyphs of one font and direction on a line, in visual order.
NameTypeDescription
faceint = -1The face to draw with.
sizef64 = 0Font size in pixels.
xf64 = 0Left edge in pixels.
widthf64 = 0Width in pixels.
startint = 0First byte offset.
endint = 0One past the last byte offset.
cp_startint = 0First character index.
cp_endint = 0One past the last character index.
rtlbool = falseThe run goes right to left.
levelint = 0Its bidi level.
tagint = 0The tag of its span.
spanint = 0Its span.
glyph_startint = 0First glyph in the layout's glyphs.
glyph_countint = 0How many glyphs.
ellipsisbool = falseThis run is the ellipsis that ends a cut line.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

LineBox

struct LineBox
One line of a layout.
NameTypeDescription
startint = 0First byte offset.
endint = 0One past the last byte offset, the trailing spaces included.
cp_startint = 0First character index.
cp_endint = 0One past the last character index.
trailing_cpint = 0Where the trailing spaces start.
topf64 = 0Top of the line in pixels.
baselinef64 = 0Baseline in pixels.
heightf64 = 0Line height in pixels.
ascentf64 = 0Space above the baseline, half leading included.
descentf64 = 0Space below the baseline, half leading included.
xf64 = 0Left edge of the content after alignment.
widthf64 = 0Content width without hanging spaces.
run_startint = 0First run in the layout's runs.
run_countint = 0How many runs, in visual order.
hard_breakbool = falseThe line ends with a new line character.
rtlbool = falseThe paragraph direction of the line is right to left.
space_extraint = 0Extra width per space from justification, in 1/64 pixel.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

TextLayout

struct TextLayout
Lines and positioned glyphs of a paragraph at one width.
NameTypeDescription
linesVectorThe lines.
runsVectorGlyph runs of all lines.
glyphsVectorAll glyphs.
widthf64 = 0The width: the max width, or the widest line when there was none.
heightf64 = 0The total height.
max_widthf64 = 0The width asked for.
truncatedbool = falseLines were dropped or cut.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

TextPosition

struct TextPosition
A caret position: a byte offset and which side it sticks to.
NameTypeDescription
offsetint = 0Byte offset in the paragraph text.
upstreambool = falseSticks to the text before it, such as the end of a wrapped line.
lineint = 0The line found by hit testing or movement.
gui-typeset-Editing.salam
import gui.typeset
import os
import filepath

func main:
    mut fc := typeset.NewFontCollection()
    typeset.AddFontFile(fc, filepath.Join(filepath.Dir(os.Env("SALAM_STD")), "books/fonts/XB Zar.ttf"))
    mut spans := Vector {} as Vector<typeset.TextSpan>
    spans.push(typeset.TextSpan { text = "نام Salam است", families = "XB Zar", size = 20.0 })
    mut st := typeset.ParagraphStyle {}
    st.white_space = typeset.WhiteSpace.PreWrap
    mut p := typeset.NewParagraph(fc, spans, st)
    mut l := typeset.LayoutParagraph(p, fc, 0.0)
    hit := typeset.HitTest(p, l, 40.0, 10.0)
    println hit.offset, hit.upstream, hit.line
    caret := typeset.CaretRect(p, l, hit)
    println caret.x, caret.y, caret.width, caret.height
    rects := typeset.SelectionRects(p, l, 3, 12)
    each r in rects:
        println r.x, r.width
    end
    rects.free()
    println typeset.NextGrapheme(p, 0), typeset.PrevGrapheme(p, 4), typeset.NextWordEnd(p, 0), typeset.PrevWordStart(p, 12)
    mut pos := typeset.TextPosition { offset = 0 }
    repeat 4:
        pos = typeset.MoveVisual(p, l, pos, false)
        print pos.offset, " "
    end
    println ""
    typeset.FreeLayout(l)
    typeset.FreeParagraph(p)
    spans.free()
    typeset.FreeFontCollection(fc)
end
Output7 false 0 37.59375 0 0 30.28125 37.59375 67.765625 2 2 6 7 2 4 6 7

TextRect

struct TextRect
A rectangle in pixels.
NameTypeDescription
xf64 = 0Left.
yf64 = 0Top.
widthf64 = 0Width.
heightf64 = 0Height.

The example for this item is shown with the ones above: gui-typeset-Editing.salam

GlyphMask

struct GlyphMask
An 8 bit coverage mask of one glyph, row by row.
NameTypeDescription
datavoid*Coverage bytes, 0 empty and 255 full.
widthint = 0Width in pixels.
heightint = 0Height in pixels.
strideint = 0Bytes per row.
leftint = 0Columns from the pen x to the left edge.
topint = 0Rows from the top edge down to the baseline.
gui-typeset-Raster.salam
import gui.typeset
import os
import filepath

func main:
    mut ok := false
    mut f := typeset.LoadFont(filepath.Join(filepath.Dir(os.Env("SALAM_STD")), "books/fonts/XB Zar.ttf"), ok)
    gid := typeset.GlyphIndex(f, 0x0634)
    mut m := typeset.RasterizeGlyph(f, gid, 24.0, typeset.SubpixelOf(12.25))
    println m.width, m.height, m.stride, m.left, m.top, typeset.GlyphMaskHash(m)
    println typeset.GlyphOriginX(12.25, m), typeset.GlyphOriginY(30.0, m)
    bp := m.data as u8*
    repeat m.height in y:
        repeat m.width in x:
            print bp[y * m.stride + x] > (127 as u8) ? "#" : "."
        end
        println ""
    end
    typeset.FreeGlyphMask(m)
    mut cache := typeset.NewGlyphCache(65536)
    a := typeset.CachedGlyph(cache, f, gid, 24.0, 12.25)
    b := typeset.CachedGlyph(cache, f, gid, 24.0, 40.25)
    println a.width == b.width, cache.hits, cache.misses, cache.capacity, cache.bytes > 0, cache.clock, cache.slots.len(), cache.index.size()
    slot := cache.slots.get(0)
    println slot.key > (0 as i64), slot.mask.width, slot.stamp, slot.used
    typeset.FreeGlyphCache(cache)
    mut o := typeset.TransformedOutline(f, gid, 0.01, 0.0, 0.0, -0.01, 2.0, 20.0)
    pt := o.points.get(0)
    println o.points.len(), o.ends.len(), pt.on
    mut cov := typeset.OutlineCoverage(o, 64, 64)
    println cov.x, cov.y, cov.width, cov.height, cov.values.len()
    typeset.FreeCoverageRows(cov)
    typeset.FreeOutline(o)
    typeset.FreeFont(f)
end
Output21 22 21 0 16 -7638933673195106609 12 14 ...............#..... ...............##.... ..................... ...............#.##.. ..............#####.. ...............#..... ..................... ..................... ..................... ...................## ................##.## ...........#...###### ..........##########. ...........####...... ...........#......... ...........#......... #..........#......... #.................... ##........#.......... ####....##........... .########............ ..#####.............. true 1 1 65536 true 2 1 1 true 21 2 true 115 3 true 1 6 19 20 380

CacheSlot

struct CacheSlot
One entry of a glyph cache.
NameTypeDescription
keyi64 = 0Font, size, glyph and quarter pixel packed together.
maskGlyphMaskThe cached mask.
stampi64 = 0When it was last used.
usedbool = falseThe slot holds a mask.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

GlyphCache

struct GlyphCache
A least recently used cache of glyph masks bounded in bytes.
NameTypeDescription
capacityint = 1048576The byte budget.
bytesint = 0Bytes in use.
hitsint = 0Lookups served from the cache.
missesint = 0Lookups that had to rasterize.
clocki64 = 0A counter for the least recently used order.
slotsVectorThe entries.
indexHashMapFrom key to slot.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

FontMetrics

struct FontMetrics
Vertical metrics of a font in font units.
NameTypeDescription
units_per_emint = 0Font units in one em.
ascentint = 0Distance from the baseline up to the top of the line, positive.
descentint = 0Distance from the baseline down to the bottom, positive.
line_gapint = 0Extra space between lines.
x_heightint = 0Height of a lowercase x.
cap_heightint = 0Height of a capital letter.
underline_positionint = 0Where an underline goes, negative below the baseline.
underline_thicknessint = 0The thickness of an underline.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

OutlinePoint

struct OutlinePoint
One point of a glyph outline.
NameTypeDescription
xf64 = 0The x position.
yf64 = 0The y position.
onbool = falseThe point is on the curve, not a control point.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

GlyphOutline

struct GlyphOutline
The points and contours of a glyph.
NameTypeDescription
pointsVectorAll points, contour after contour.
endsVectorWhere each contour ends in points (one past its last point).

The example for this item is shown with the ones above: gui-typeset-Raster.salam

Font

struct Font
A TrueType font loaded into memory, with the offsets of its tables.
NameTypeDescription
datavoid*The font file's bytes, owned by the font.
sizeint = 0The number of bytes in data.
units_per_emint = 0Font units in one em, from the head table.
num_glyphsint = 0The number of glyphs.
ascentint = 0The ascent from the hhea table, in font units.
descentint = 0The descent from the hhea table, negative, in font units.
line_gapint = 0The line gap from the hhea table.
glyfint = 0Where the glyf table starts.
glyf_lenint = 0The length of the glyf table.
locaint = 0Where the loca table starts.
loca_lenint = 0The length of the loca table.
hmtxint = 0Where the hmtx table starts.
kernint = 0Where the old kern table starts, 0 when there is none.
cmap_subint = 0Where the chosen cmap subtable starts.
cmap_symbolbool = falseThe cmap is a Windows symbol one.
num_hmetricsint = 0The number of horizontal metrics.
loca_longbool = falseThe loca table uses 32 bit offsets.
okbool = falseThe font was read correctly.
idint = 0A number unique to this loaded font, used as a cache key.
indexint = 0The face number inside a TTC collection.
headint = 0Where the head table starts.
hheaint = 0Where the hhea table starts.
os2int = 0Where the OS/2 table starts, 0 when there is none.
nameint = 0Where the name table starts.
postint = 0Where the post table starts.
gsubint = 0Where the GSUB table starts, 0 when there is none.
gposint = 0Where the GPOS table starts, 0 when there is none.
gdefint = 0Where the GDEF table starts, 0 when there is none.
kern_lenint = 0The length of the kern table.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

CoverageRows

struct CoverageRows
Floating point coverage of an outline, row by row.
NameTypeDescription
xint = 0The left column.
yint = 0The top row.
widthint = 0The width in pixels.
heightint = 0The height in pixels.
valuesVectorCoverage from 0 to 1, width values per row.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

Constants and variables

TextDirAuto

const TextDirAuto: i32 = 0
Direction from the first strong letter.

The example for this item is shown with the ones above: gui-typeset-Shape.salam

TextDirLTR

const TextDirLTR: i32 = 1
Left to right.

The example for this item is shown with the ones above: gui-typeset-Shape.salam

TextDirRTL

const TextDirRTL: i32 = 2
Right to left.

The example for this item is shown with the ones above: gui-typeset-Shape.salam

Functions

NewFontCollection

func NewFontCollection(): FontCollection
An empty collection.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

AddFont

func AddFont(fc &: FontCollection, f: Font): int
Adds a loaded font; the collection then owns it. Returns the face number.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

AddFontBytes

func AddFontBytes(fc &: FontCollection, p: void*, n: int): int
Adds every face in font bytes; returns how many.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

AddFontFile

func AddFontFile(fc &: FontCollection, path: str): int
Adds every face of a file, reading only its names now; returns how many.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

SystemFontDirs

func SystemFontDirs(): Vector<str>
The standard font folders of this OS.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

AddFontDir

func AddFontDir(fc &: FontCollection, dir: str): int
Adds every TrueType file under a folder, in sorted order.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

AddSystemFonts

func AddSystemFonts(fc &: FontCollection): int
Adds the fonts of the standard folders on Linux, Windows or macOS, without fontconfig.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

FaceCountIn

func FaceCountIn(fc: FontCollection): int
The number of faces in a collection.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

FaceFont

func FaceFont(fc &: FontCollection, face: int): Font
The font of a face, loading it if needed.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

FindFace

func FindFace(fc &: FontCollection, family: str, weight: int, italic: bool): int
The best face of a family for a weight and italic, or -1.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

FaceForCodepoint

func FaceForCodepoint(fc &: FontCollection, families: str, weight: int, italic: bool, cp: int): int
The face to draw a character with: the CSS-like family list first, then the fallback list of its script, then any face that has it.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

FreeFontCollection

func FreeFontCollection(fc &: FontCollection)
Releases the collection and its fonts.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

ShapeText

func ShapeText(f: Font, text: str, rtl: bool, script: str, lang: str): Vector<ShapedGlyph>
Shapes one run of text with the font's GSUB and GPOS (joining forms, ligatures, kerning, marks); right to left runs come out in visual order like HarfBuzz.

The example for this item is shown with the ones above: gui-typeset-Shape.salam

NewParagraph

func NewParagraph(fc &: FontCollection, spans: Vector<TextSpan>, style: ParagraphStyle): Paragraph
Splits spans into bidi, script and font runs, picks fonts per character cluster and shapes every run once.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

MinContentWidth

func MinContentWidth(p: Paragraph): f64
The narrowest width the paragraph fits in, in pixels.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

MaxContentWidth

func MaxContentWidth(p: Paragraph): f64
The width with no wrapping at all.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

SpanAt

func SpanAt(p: Paragraph, offset: int): int
The span that holds a byte offset.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

FreeParagraph

func FreeParagraph(p &: Paragraph)
Releases a paragraph.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

LayoutParagraph

func LayoutParagraph(p &: Paragraph, fc &: FontCollection, max_width: f64): TextLayout
Breaks lines at a width (0 means no limit), reorders each line for bidi, aligns, and positions glyphs.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

FreeLayout

func FreeLayout(l &: TextLayout)
Releases a layout.

The example for this item is shown with the ones above: gui-typeset-Layout.salam

HitTest

func HitTest(p: Paragraph, l: TextLayout, x: f64, y: f64): TextPosition
The caret position nearest to a point.

The example for this item is shown with the ones above: gui-typeset-Editing.salam

CaretRect

func CaretRect(p: Paragraph, l: TextLayout, pos: TextPosition): TextRect
Where to draw the caret for a position; width 0, line height tall.

The example for this item is shown with the ones above: gui-typeset-Editing.salam

SelectionRects

func SelectionRects(p: Paragraph, l: TextLayout, from: int, to_offset: int): Vector<TextRect>
Rectangles covering a byte range, split where bidi runs separate it.

The example for this item is shown with the ones above: gui-typeset-Editing.salam

NextGrapheme

func NextGrapheme(p: Paragraph, offset: int): int
The next grapheme cluster boundary.

The example for this item is shown with the ones above: gui-typeset-Editing.salam

PrevGrapheme

func PrevGrapheme(p: Paragraph, offset: int): int
The previous grapheme cluster boundary.

The example for this item is shown with the ones above: gui-typeset-Editing.salam

NextWordEnd

func NextWordEnd(p: Paragraph, offset: int): int
The end of the next word; the zero width non-joiner stays inside a word.

The example for this item is shown with the ones above: gui-typeset-Editing.salam

PrevWordStart

func PrevWordStart(p: Paragraph, offset: int): int
The start of the previous word.

The example for this item is shown with the ones above: gui-typeset-Editing.salam

MoveVisual

func MoveVisual(p: Paragraph, l: TextLayout, pos: TextPosition, right: bool): TextPosition
Moves the caret one cluster left or right on screen, across bidi runs and lines.

The example for this item is shown with the ones above: gui-typeset-Editing.salam

ScaleFactor

func ScaleFactor(f: Font, size: f64): f64
Pixels per font unit at a font size.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

AscentPixels

func AscentPixels(f: Font, size: f64): f64
The ascent in pixels at a size.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

DescentPixels

func DescentPixels(f: Font, size: f64): f64
The descent in pixels at a size, negative.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

LineHeightPixels

func LineHeightPixels(f: Font, size: f64): f64
Ascent, descent and line gap together in pixels.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

GlyphAdvance

func GlyphAdvance(f: Font, glyph: int): int
The advance width of a glyph in font units.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

KernAdvance

func KernAdvance(f: Font, glyph1: int, glyph2: int): int
The kerning of two glyphs from the old kern table.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

ShapeArabic

func ShapeArabic(text: str): str
Replaces Persian and Arabic letters with their joined presentation forms; the fallback shaper for fonts without GSUB.

The example for this item is shown with the ones above: gui-typeset-Shape.salam

RasterizeGlyph

func RasterizeGlyph(f: Font, glyph: int, size: f64, subpixel: int): GlyphMask
Draws a glyph into a new coverage mask with fixed point math, shifted by 0 to 3 quarter pixels; the same bytes on every OS.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

FreeGlyphMask

func FreeGlyphMask(m &: GlyphMask)
Releases a mask you rasterized yourself.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

GlyphMaskHash

func GlyphMaskHash(m: GlyphMask): i64
A hash of the mask bytes, handy in tests.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

SubpixelOf

func SubpixelOf(x: f64): int
The quarter pixel (0 to 3) of a pen x.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

GlyphOriginX

func GlyphOriginX(x: f64, m: GlyphMask): int
The destination column of a mask for a pen x.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

GlyphOriginY

func GlyphOriginY(y: f64, m: GlyphMask): int
The destination row of a mask for a baseline y; y snaps to whole pixels.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

NewGlyphCache

func NewGlyphCache(capacity_bytes: int): GlyphCache
A cache with a byte budget.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

CachedGlyph

func CachedGlyph(c &: GlyphCache, f: Font, glyph: int, size: f64, x: f64): GlyphMask
The mask of a glyph at a size and pen x, from the cache or rasterized; valid until the next call may evict it.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

FreeGlyphCache

func FreeGlyphCache(c &: GlyphCache)
Releases the cache and all its masks.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

NameString

func NameString(f: Font, name_id: int): str
One string of the name table, by name id (1 family, 2 style, 4 full name).

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

FamilyName

func FamilyName(f: Font): str
The family name, as in CSS font-family.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

StyleName

func StyleName(f: Font): str
The style name, such as Regular or Bold Italic.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

FullName

func FullName(f: Font): str
The full name of the face.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

Weight

func Weight(f: Font): int
The weight from 100 to 900; 400 is regular, 700 bold.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

IsItalic

func IsItalic(f: Font): bool
The face is italic or oblique.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

Metrics

func Metrics(f: Font): FontMetrics
The vertical metrics, taking the OS/2 table into account.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

FaceCount

func FaceCount(p: void*, n: int): int
The number of faces in font bytes (more than one for a TTC).

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

GlyphBox

func GlyphBox(f: Font, glyph: int, x0 &: int, y0 &: int, x1 &: int, y1 &: int): bool
The bounding box of a glyph in font units.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

GlyphHasOutline

func GlyphHasOutline(f: Font, glyph: int): bool
The glyph has contours (a space has none).

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

TransformedOutline

func TransformedOutline(f: Font, glyph: int, a: f64, b: f64, c: f64, d: f64, dx: f64, dy: f64): GlyphOutline
The outline of a glyph through a 2 by 2 matrix and an offset, in floating point; std/ai/image draws with it.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

FreeOutline

func FreeOutline(o &: GlyphOutline)
Releases an outline.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

ParseFontIndex

func ParseFontIndex(p: void*, n: int, index: int, ok &: bool): Font
Reads face i of a font or TTC collection from bytes; the bytes are copied.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

ParseFont

func ParseFont(p: void*, n: int, ok &: bool): Font
Reads the first face of a font from bytes.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

LoadFont

func LoadFont(path: str, ok &: bool): Font
Reads a .ttf file.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

LoadFontIndex

func LoadFontIndex(path: str, index: int, ok &: bool): Font
Reads face i of a .ttf or .ttc file.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

FreeFont

func FreeFont(f &: Font)
Releases the font's memory.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

FontValid

func FontValid(f: Font): bool
The font is loaded and usable.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

IsCFFFont

func IsCFFFont(p: void*, n: int): bool
The bytes hold a font with CFF outlines, which this package does not draw.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

UnitsPerEm

func UnitsPerEm(f: Font): int
Font units in one em.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

NumGlyphs

func NumGlyphs(f: Font): int
The number of glyphs.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

GlyphIndex

func GlyphIndex(f: Font, codepoint: int): int
The glyph for a Unicode code point, 0 when the font has none.

The example for this item is shown with the ones above: gui-typeset-Fonts.salam

IsRTLText

func IsRTLText(text: str): bool
The text has right to left letters.

The example for this item is shown with the ones above: gui-typeset-Shape.salam

BaseDirection

func BaseDirection(text: str): int
The direction of the first strong letter.

The example for this item is shown with the ones above: gui-typeset-Shape.salam

ToVisualOrder

func ToVisualOrder(text: str, dir: int): str
The text shaped with presentation forms and reordered for drawing left to right.

The example for this item is shown with the ones above: gui-typeset-Shape.salam

VisualCodepoints

func VisualCodepoints(text: str, dir: int, shape: bool): Vector<int>
The code points of ToVisualOrder, optionally without shaping.

The example for this item is shown with the ones above: gui-typeset-Shape.salam

OutlineCoverage

func OutlineCoverage(o: GlyphOutline, clip_width: int, clip_height: int): CoverageRows
Fills an outline with the image package's scanline filler, clipped to a canvas size.

The example for this item is shown with the ones above: gui-typeset-Raster.salam

FreeCoverageRows

func FreeCoverageRows(c &: CoverageRows)
Releases coverage rows.

The example for this item is shown with the ones above: gui-typeset-Raster.salam