# SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-License-Identifier: Unlicense import std/[asyncdispatch, options, os, tables] import preserves, syndicate, 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: "".} {.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.. 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: Ref; 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(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) bootDataspace("main") do (root: Ref; turn: var Turn): connectStdio(root, turn) onPublish(turn, root, ?BootArguments) do (ds: Ref): serve(ds, turn) runForever()