2023-03-27 03:37:38 +00:00
|
|
|
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
|
|
|
# SPDX-License-Identifier: Unlicense
|
|
|
|
|
2023-03-30 03:37:14 +00:00
|
|
|
import std/[asyncdispatch, options, os, tables]
|
2023-10-09 19:27:22 +00:00
|
|
|
import preserves, syndicate, syndicate/relays, syndicate/[patterns]
|
2023-03-27 03:37:38 +00:00
|
|
|
import ./protocol
|
|
|
|
|
2023-03-29 22:53:37 +00:00
|
|
|
proc logError(args: varargs[string, `$`]) = writeLine(stderr, args)
|
|
|
|
|
2023-03-27 03:37:38 +00:00
|
|
|
{.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
|
2023-03-29 22:53:37 +00:00
|
|
|
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
|
2023-03-27 03:37:38 +00:00
|
|
|
|
|
|
|
FcConfig = distinct pointer
|
|
|
|
|
2023-03-30 03:37:14 +00:00
|
|
|
FcConstant {.importc.} = ptr object
|
|
|
|
value: cint
|
|
|
|
|
2023-03-29 22:53:37 +00:00
|
|
|
const
|
|
|
|
(FcFalse, FcTrue) = (cint 0, cint 1)
|
|
|
|
|
2023-03-27 03:37:38 +00:00
|
|
|
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.}
|
2023-03-29 22:53:37 +00:00
|
|
|
proc PatternAddString(p: FcPattern; obj: cstring; s: cstring): FcBool {.importFc.}
|
|
|
|
proc PatternAddBool(p: FcPattern; obj: cstring; b: bool): FcBool {.importFc.}
|
2023-03-27 03:37:38 +00:00
|
|
|
proc DefaultSubstitute(p: FcPattern) {.importFc.}
|
2023-03-30 03:37:14 +00:00
|
|
|
# proc PatternPrint(p: FcPattern) {.importFc.}
|
2023-03-27 03:37:38 +00:00
|
|
|
|
|
|
|
proc FontMatch(cfg: FcConfig; p: FcPattern; r: var FcResult): FcPattern {.importFc.}
|
|
|
|
|
2023-03-29 22:53:37 +00:00
|
|
|
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.}
|
|
|
|
|
2023-03-30 03:37:14 +00:00
|
|
|
# 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".}
|
|
|
|
|
2023-03-29 22:53:37 +00:00
|
|
|
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)
|
2023-03-30 03:37:14 +00:00
|
|
|
of pkSymbol:
|
|
|
|
let constant = NameGetConstant(val.symbol.cstring)
|
|
|
|
if not constant.isNil: PatternAddInteger(result, key, constant.value)
|
|
|
|
else:
|
|
|
|
logError("invalid constant: ", val)
|
|
|
|
FcFalse
|
2023-03-29 22:53:37 +00:00
|
|
|
else:
|
|
|
|
logError("invalid property: ", val)
|
|
|
|
FcFalse
|
|
|
|
if recognized == FcFalse:
|
2023-03-30 03:37:14 +00:00
|
|
|
logError("unrecognized property {", key, ": ", val, "}")
|
2023-03-29 22:53:37 +00:00
|
|
|
DefaultSubstitute(result)
|
|
|
|
|
|
|
|
template withFontconfig(body: untyped): untyped =
|
|
|
|
## Initialize and deinitialize the Fontconfig library for the scope of `body`.
|
2023-03-27 03:37:38 +00:00
|
|
|
if Init() == FcFalse:
|
2023-03-30 03:37:14 +00:00
|
|
|
logError "Failed to initialize FontConfig"
|
2023-03-29 22:53:37 +00:00
|
|
|
else:
|
|
|
|
body
|
2023-03-27 03:37:38 +00:00
|
|
|
Fini()
|
|
|
|
|
2023-08-16 13:42:13 +00:00
|
|
|
proc serve(ds: Cap; turn: var Turn) =
|
2023-03-27 03:37:38 +00:00
|
|
|
let observation = ?Observe(pattern: !FontAssertion) ?? {0: grabDict()}
|
2023-03-29 22:53:37 +00:00
|
|
|
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)
|
2023-08-16 13:42:13 +00:00
|
|
|
font = FontMatch(FcConfig(nil), pat, res)
|
2023-03-29 22:53:37 +00:00
|
|
|
if res != FcResultMatch:
|
|
|
|
logError "no font matched for ", fontAssert.pattern
|
|
|
|
else:
|
|
|
|
fontAssert.attributes = fillProperties(font)
|
|
|
|
PatternDestroy(font)
|
|
|
|
PatternDestroy(pat)
|
|
|
|
discard publish(turn, ds, fontAssert)
|
2023-03-27 03:37:38 +00:00
|
|
|
|
2023-08-16 13:42:13 +00:00
|
|
|
runActor("main") do (root: Cap; turn: var Turn):
|
2023-10-21 17:35:44 +00:00
|
|
|
connectStdio(turn, root)
|
2023-08-16 13:42:13 +00:00
|
|
|
during(turn, root, ?BootArguments) do (ds: Cap):
|
2023-03-27 03:37:38 +00:00
|
|
|
serve(ds, turn)
|