fontconfig_actor/src/fontconfig_actor.nim

173 lines
5.9 KiB
Nim

# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, options, os, tables]
import preserves, syndicate, syndicate/relays, syndicate/[patterns]
import ./protocol
proc logError(args: varargs[string, `$`]) = writeLine(stderr, args)
{.passC: staticExec("pkg-config --cflags fontconfig").}
{.passL: staticExec("pkg-config --libs fontconfig").}
{.pragma: fcHeader, header: "<fontconfig/fontconfig.h>".}
{.pragma: importFc, fcHeader, importc: "Fc$1".}
type
FcChar8* = uint8
FcBool* = cint
FcResult {.importc.} = enum
FcResultMatch, FcResultNoMatch, FcResultTypeMismatch, FcResultNoId,
FcResultOutOfMemory
FcPattern = distinct pointer
FcPatternIter {.importc.} = object
FcValue {.importc.} = object
`type`: FcType
u: FcValueUnion
FcType {.importc} = enum
FcTypeUnknown = -1,
FcTypeVoid,
FcTypeInteger,
FcTypeDouble,
FcTypeString,
FcTypeBool,
FcTypeMatrix,
FcTypeCharSet,
FcTypeFTFace,
FcTypeLangSet,
FcTypeRange
FcValueUnion {.importc.} = object
s: cstring
i: cint
b: FcBool
d: cdouble
FcConfig = distinct pointer
FcConstant {.importc.} = ptr object
value: cint
const
(FcFalse, FcTrue) = (cint 0, cint 1)
proc Init(): FcBool {.importFc.} ## Initialize fontconfig library
proc Fini() {.importFc.} ## Finalize fontconfig library.
proc PatternCreate(): FcPattern {.importFc.}
proc PatternDestroy(p: FcPattern) {.importFc.}
proc PatternAddInteger(p: FcPattern; obj: cstring; i: cint): FcBool {.importFc.}
proc PatternAddDouble(p: FcPattern; obj: cstring; d: cdouble): FcBool {.importFc.}
proc PatternAddString(p: FcPattern; obj: cstring; s: cstring): FcBool {.importFc.}
proc PatternAddBool(p: FcPattern; obj: cstring; b: bool): FcBool {.importFc.}
proc DefaultSubstitute(p: FcPattern) {.importFc.}
# proc PatternPrint(p: FcPattern) {.importFc.}
proc FontMatch(cfg: FcConfig; p: FcPattern; r: var FcResult): FcPattern {.importFc.}
proc PatternIterStart(p: FcPattern; iter: ptr FcPatternIter) {.importFc.}
proc PatternIterNext(p: FcPattern; iter: ptr FcPatternIter): FcBool {.importFc.}
proc PatternIterValueCount(p: FcPattern; iter: ptr FcPatternIter): cint {.importFc.}
proc PatternIterIsValid(p: FcPattern; iter: ptr FcPatternIter): FcBool {.importFc.}
proc PatternIterGetObject(p: FcPattern; iter: ptr FcPatternIter): cstring {.importFc.}
proc PatternIterGetValue(p: FcPattern; iter: ptr FcPatternIter; id: cint; v: ptr FcValue; b: pointer): FcResult {.importFc.}
# proc NameGetConstantFor(str: ptr FcChar8; obj: cstring): FcConstant {.importFc.}
proc NameGetConstant(str: cstring): FcConstant {.importFc,
deprecated: "update to Fontconfig 2.14.2 API and use NameGetConstantFor".}
proc getValue(pat: FcPattern; iter: ptr FcPatternIter; i: cint): Option[Preserve[void]] =
var fcVal: FcValue
if PatternIterGetValue(pat, iter, i, addr fcVal, nil) == FcResultMatch:
case fcVal.`type`
of FcTypeInteger:
result = some fcVal.u.i.toPreserve
of FcTypeDouble:
result = some fcVal.u.d.toPreserve
of FcTypeString:
result = some ($fcVal.u.s).toPreserve
of FcTypeBool:
result = some (fcVal.u.b == FcTrue).toPreserve
else: discard
proc fillProperties(pat: FcPattern): Properties =
var iter: FcPatternIter
PatternIterStart(pat, addr iter)
while PatternIterIsValid(pat, addr iter) == FcTrue:
let vc = PatternIterValueCount(pat, addr iter)
if vc > 0:
var key = Symbol $PatternIterGetObject(pat, addr iter)
if vc == 1:
var val = getValue(pat, addr iter, 0)
if val.isSome: result[key] = get val
else:
var val = initSequenceOfCap(vc)
for i in 0..<vc:
let opt = getValue(pat, addr iter, i)
if opt.isSome: add(val.sequence, get(opt))
if val.sequence.len > 0:
result[key] = val
if PatternIterNext(pat, addr iter) != FcTrue:
break
proc fillPattern(properties: Properties): FcPattern =
result = PatternCreate()
for sym, val in properties.pairs:
let key = sym.string
let recognized =
case val.kind
of pkBoolean: PatternAddBool(result, key, val.bool)
of pkFloat: PatternAddDouble(result, key, val.float)
of pkDouble: PatternAddDouble(result, key, val.double)
of pkSignedInteger: PatternAddInteger(result, key, cint val.int)
of pkString:
PatternAddString(result, key, val.string)
of pkSymbol:
let constant = NameGetConstant(val.symbol.cstring)
if not constant.isNil: PatternAddInteger(result, key, constant.value)
else:
logError("invalid constant: ", val)
FcFalse
else:
logError("invalid property: ", val)
FcFalse
if recognized == FcFalse:
logError("unrecognized property {", key, ": ", val, "}")
DefaultSubstitute(result)
template withFontconfig(body: untyped): untyped =
## Initialize and deinitialize the Fontconfig library for the scope of `body`.
if Init() == FcFalse:
logError "Failed to initialize FontConfig"
else:
body
Fini()
proc serve(ds: Cap; turn: var Turn) =
let observation = ?Observe(pattern: !FontAssertion) ?? {0: grabDict()}
during(turn, ds, observation) do (properties: Preserve[void]):
var fontAssert: FontAssertion
for key, val in properties.pairs:
if key.isSymbol and val.isRecord("lit", 1):
fontAssert.pattern[key.symbol] = val[0]
withFontconfig:
var
res = FcResultNoMatch
pat = fillPattern(fontAssert.pattern)
font = FontMatch(FcConfig(nil), pat, res)
if res != FcResultMatch:
logError "no font matched for ", fontAssert.pattern
else:
fontAssert.attributes = fillProperties(font)
PatternDestroy(font)
PatternDestroy(pat)
discard publish(turn, ds, fontAssert)
runActor("main") do (root: Cap; turn: var Turn):
connectStdio(turn, root)
during(turn, root, ?BootArguments) do (ds: Cap):
serve(ds, turn)