Skip to content

Latest commit

 

History

History
709 lines (536 loc) · 30.8 KB

File metadata and controls

709 lines (536 loc) · 30.8 KB

DasherCore C API

Overview

The DasherCore C API (src/dasher.h / src/CAPI.cpp) wraps the C++ predictive text engine so that any language — Swift, Kotlin, C#, JavaScript (via WASM), Rust — can integrate Dasher without touching C++ directly.

The API is designed around a single opaque session handle (dasher_ctx) with a frame-based rendering loop: you feed pointer input, call dasher_frame() to advance the engine, and receive an array of draw commands to render with your platform's canvas API.

Header: src/dasher.h
Implementation: src/CAPI.cpp
Tests: tests/test_capi.cpp

Architecture

Frontend (Swift / Kotlin / C# / ...)
         │
    dasher.h  (C API)
         │
    CAPI.cpp
         │
    ┌────────────────────────────────────────────┐
    │  dasher_ctx::Interface                     │
    │    extends CDashIntfScreenMsgs             │
    │      extends CDashIntfSettings             │
    │        extends CDasherInterfaceBase        │
    └────────────────────────────────────────────┘
         │                    │
    CommandScreen        PointerInput
    (captures draw       (wraps pointer
     commands into       position into
     int32 arrays)       Dasher input system)

Design Principles

  • Single opaque handle — one dasher_ctx* per session
  • Not thread-safe — one thread per context
  • Zero-copy rendering — draw commands returned as raw int32 arrays (6 ints per command), valid only until the next dasher_frame() call
  • Ephemeral string pointers — all const char* returns are valid only until the next API call on the same context
  • Null-safe — every function handles NULL ctx gracefully (returns empty/zero, never crashes)

Building

The C API is built with CMake:

mkdir build && cd build
cmake .. -DBUILD_CAPI=ON -DBUILD_TESTS=ON -DTEST_DATA_DIR=/path/to/Data
cmake --build .

Build Options

Option Default Description
BUILD_CAPI ON Build the C API shared library (libdasher)
BUILD_TESTS ON Build unit tests (requires BUILD_CAPI=ON)
TEST_DATA_DIR (none) Compile-time path to Data/ directory for tests

Targets

Target Type Output
DasherCore Static library Core C++ engine
dasher Shared library C API (src/CAPI.cpp)
dasher_*_tests Executables Unit tests (one executable per test file, e.g. dasher_capi_tests)

Code Generation

settings_manifest.json is processed by Scripts/generate_parameters.py at configure time to auto-generate src/DasherCore/Parameters.cpp. Python 3 is required.

Quick Start

#include "dasher.h"

// 1. Create session
char* error = NULL;
dasher_ctx* ctx = dasher_create("/path/to/Data", "/path/to/user_dir", &error);
if (!ctx) {
    fprintf(stderr, "Failed: %s\n", error);
    return 1;
}

// 2. Set screen size (triggers engine initialization)
dasher_set_screen_size(ctx, 800, 600);

// 3. Configure (optional)
dasher_set_speed_percent(ctx, 120);
dasher_set_alphabet_id(ctx, "English");
dasher_set_locale(ctx, "de");

// 4. Main loop
while (running) {
    dasher_mouse_move(ctx, pointer_x, pointer_y);

    int* cmds;    int cmd_count;
    char** strs;  int str_count;
    dasher_frame(ctx, time_ms, &cmds, &cmd_count, &strs, &str_count);

    // Render draw commands
    for (int i = 0; i < cmd_count; i += 6) {
        render_command(cmds[i], cmds[i+1], cmds[i+2],
                       cmds[i+3], cmds[i+4], cmds[i+5], strs);
    }
}

// 5. Cleanup
dasher_save_settings(ctx);
dasher_destroy(ctx);

Session Lifecycle

dasher_create

dasher_ctx* dasher_create(const char* data_dir, const char* user_dir, char** out_error);

Creates a new Dasher session.

  • data_dir — Path to DasherCore's Data/ directory (alphabets, colours, training files). Must be readable.
  • user_dir — Writable directory for settings. If NULL, data_dir is used. Settings are stored in <user_dir>/dasher_settings.xml.
  • out_error — If not NULL, set to a human-readable error string on failure. Do NOT free. Valid until next API call.
  • Returns — Session handle, or NULL on failure.

dasher_destroy

void dasher_destroy(dasher_ctx* ctx);

Destroys a session and frees all resources. Safe to call with NULL.

dasher_set_screen_size

void dasher_set_screen_size(dasher_ctx* ctx, int width, int height);

Sets the canvas dimensions. Must be called before dasher_frame() — it triggers engine initialization (Realize()). Call again on window resize.

Input

Pointer Input

void dasher_mouse_move(dasher_ctx* ctx, float x, float y);
void dasher_mouse_down(dasher_ctx* ctx);
void dasher_mouse_up(dasher_ctx* ctx);
  • mouse_move — Feed pointer coordinates (mouse, touch, eyetracker). Origin top-left, pixels. Values are clamped to screen bounds.
  • mouse_down — Signal pointer press (starts Dasher zooming).
  • mouse_up — Signal pointer release (pauses Dasher zooming).

Key Input

void dasher_key_event(dasher_ctx* ctx, int key, int pressed);

For switch access, keyboard, or button input. Key values (DASHER_KEY_* constants in dasher.h):

Key Constant Value
Start/Stop DASHER_KEY_START_STOP 0
Buttons 1–4 DASHER_KEY_BUTTON_1..4 1–4
Primary 100
Secondary 101
Tertiary 102

pressed: 1 for key down, 0 for key up.

Rendering

dasher_frame

void dasher_frame(dasher_ctx* ctx, int64_t time_ms,
                  int** out_commands, int* out_command_count,
                  char*** out_strings, int* out_string_count);

Advances one frame and returns draw commands.

  • time_ms — Current time in milliseconds. If <= 0, treated as 0.
  • out_commands — Set to internal command buffer (array of int). Valid until next dasher_frame(). Do NOT free.
  • out_command_count — Set to total number of ints (not number of commands — divide by 6 for command count).
  • out_strings — Array of char* string pointers. Valid until next dasher_frame().
  • out_string_count — Number of strings.

Draw Command Format

Each command is 6 int32_t values: [opcode, a, b, c, d, argb]. Opcode constants (DASHER_CMD_*) are defined in dasher.h — prefer them over raw numbers.

Opcode Constant Fields Description
0 DASHER_CMD_CLEAR argb = background colour Fill entire canvas
1 DASHER_CMD_CIRCLE a=x, b=y, c=radius, d=1 filled / 0 outline, argb Circle shape
2 DASHER_CMD_LINE a=x1, b=y1, c=x2, d=y2, argb Line segment
3 DASHER_CMD_RECT_OUTLINE a=x1, b=y1, c=x2, d=y2, argb Rectangle outline
4 DASHER_CMD_RECT_FILL a=x1, b=y1, c=x2, d=y2, argb Filled rectangle
5 DASHER_CMD_TEXT a=x, b=y, c=fontSize, d=stringIndex, argb Render string from strings array

ARGB format: (alpha << 24) | (red << 16) | (green << 8) | blue

Rendering Example

for (int i = 0; i < cmd_count; i += 6) {
    int opcode = cmds[i+0];
    int a = cmds[i+1], b = cmds[i+2], c = cmds[i+3], d = cmds[i+4];
    int argb = cmds[i+5];

    switch (opcode) {
        case DASHER_CMD_CLEAR: fill_background(argb); break;
        case DASHER_CMD_CIRCLE: draw_circle(a, b, c, d == 1, argb); break;
        case DASHER_CMD_LINE: draw_line(a, b, c, d, argb); break;
        case DASHER_CMD_RECT_OUTLINE: draw_rect_outline(a, b, c, d, argb); break;
        case DASHER_CMD_RECT_FILL: draw_rect_filled(a, b, c, d, argb); break;
        case DASHER_CMD_TEXT: draw_text(a, b, c, strs[d], argb); break;
    }
}

Color Utilities

int dasher_color_argb(int alpha, int red, int green, int blue);  // Create ARGB
int dasher_color_rgb(int red, int green, int blue);              // Create opaque (alpha=255)
int dasher_color_get_alpha(int argb);                            // Extract alpha
int dasher_color_get_red(int argb);                              // Extract red
int dasher_color_get_green(int argb);                            // Extract green
int dasher_color_get_blue(int argb);                            // Extract blue

Custom Rendering — Strand 2 (RFC 0013)

The int[] command buffer from dasher_frame() (Strand 1) is 2D painter's algorithm — no depth buffer, no compositing. It can't represent 3D extruded cubes, VR/spatial layouts, or fully custom visualisations. Strand 2 is the alternative: query the visible node tree each frame and render it yourself with your own graphics API.

The two strands coexist. A frontend can use Strand 1 for most rendering and drop into Strand 2 for specific features (e.g. render cube mode as real 3D from the node tree), or render everything from scratch.

Enabling capture

Node capture is off by default — Strand 1-only frontends pay zero overhead. Enable it once at setup:

dasher_set_visible_nodes_enabled(ctx, 1);

After this, each dasher_frame() records the nodes it draws; dasher_get_visible_nodes() returns them.

dasher_get_visible_nodes

typedef struct dasher_node_info {
    int struct_size;            // caller sets to sizeof(dasher_node_info) before the call
    long long dasher_y1;        // node's Dasher-Y range
    long long dasher_y2;
    int symbol;                 // alphabet symbol index (-1 for group/control nodes)
    int has_children;           // 1 if this node has children
    int depth;                  // tree depth from the rendered root (0 = root)
    int is_game_node;           // 1 if on the game-mode path
    int screen_x1, screen_y1;   // node's clipped screen bounds
    int screen_x2, screen_y2;
    int fill_argb;              // node fill colour (from active palette)
    int outline_argb;           // node outline colour
    int label_index;            // index into out_strings (-1 if no label)
} dasher_node_info;

// Returns the number of visible nodes (may exceed max_nodes — grow the buffer
// and re-query if so). Nodes are depth-first (parent before children).
// out_strings / out_string_count hold label text; label_index indexes into it.
// out_nodes and out_strings are engine-owned, valid until the next
// dasher_frame() or dasher_get_visible_nodes() call.
// Returns -1 if capture is disabled, the engine isn't realised, or the caller's
// struct_size is smaller than this engine's struct.
int dasher_get_visible_nodes(dasher_ctx* ctx, dasher_node_info* out_nodes, int max_nodes,
                             char*** out_strings, int* out_string_count);

The captured node set matches exactly what the Strand 1 command buffer draws for the same frame (Strand 1/Strand 2 parity), so a frontend can mix strands without drift. screen_x1..y2 use the same clip formula as the DASHER_CMD_RECT_FILL draw. depth is the source for cube extrusion (deeper nodes recede); symbol/label_index identify what each node represents.

dasher_get_viewport

typedef struct dasher_viewport {
    int struct_size;            // caller sets to sizeof(dasher_viewport) before the call
    long long crosshair_x;      // crosshair Dasher X (fixed at the origin)
    long long crosshair_y;      // crosshair Dasher Y
    long long visible_min_y;    // visible Dasher-Y range
    long long visible_max_y;
    int screen_width;           // canvas size from dasher_set_screen_size
    int screen_height;
} dasher_viewport;

int dasher_get_viewport(dasher_ctx* ctx, dasher_viewport* out);  // 0 on success, -1 on error

Rendering example (Strand 2)

dasher_set_visible_nodes_enabled(ctx, 1);

// each frame:
dasher_frame(ctx, time_ms, &cmds, &cc, &strs, &sc);  // still call it — it advances the engine

dasher_node_info nodes[256];
for (int i = 0; i < 256; ++i) nodes[i].struct_size = sizeof(dasher_node_info);
char** labels = NULL;
int label_count = 0;
int n = dasher_get_visible_nodes(ctx, nodes, 256, &labels, &label_count);
if (n > 256) { /* grew past the buffer — reallocate and re-query */ }

for (int i = 0; i < n && i < 256; ++i) {
    dasher_node_info nd = nodes[i];
    // Render however you like: flat rect, 3D cube extruded by nd.depth, sphere…
    if (nd.label_index >= 0) draw_label(labels[nd.label_index], nd.screen_x1, nd.screen_y1);
    draw_node(nd.screen_x1, nd.screen_y1, nd.screen_x2, nd.screen_y2,
              nd.fill_argb, nd.outline_argb);
}

For a 3D cube frontend, render the same nodes in two passes (flat layout first, then cubes composited on top using depth for the extrusion), or render everything in 3D from the start. LP_SHAPE_TYPE (read via dasher_get_long_parameter) is a hint the frontend can honour or ignore — shape and depth become frontend concerns, not engine concerns.

Output Text

const char* dasher_get_output_text(dasher_ctx* ctx);    // Current buffer
void dasher_reset_output_text(dasher_ctx* ctx);          // Clear text, keep model position
void dasher_reset(dasher_ctx* ctx);                      // Clear text AND reset model to start

Output Callback

typedef void (*dasher_output_callback)(int event_type, const char* text, void* user_data);
void dasher_set_output_callback(dasher_ctx* ctx, dasher_output_callback callback, void* user_data);

Receives real-time text events without polling. Event types (DASHER_EVENT_* constants in dasher.h):

Type Meaning
DASHER_EVENT_OUTPUT (0) Text output (insertion)
DASHER_EVENT_DELETE (1) Text delete (backspace)
DASHER_EVENT_BUFFER_CLEAR (2) Buffer cleared wholesale — dasher_reset, dasher_reset_output_text, or an alphabet change (which clears the buffer). text is empty; shadow buffers must be cleared, not diffed

The callback fires on the thread calling dasher_frame(). DASHER_EVENT_BUFFER_CLEAR may also fire on the thread calling the reset function itself.

Text Measurement Callback

The engine positions node labels (including the anti-overlap "shunting" that pushes a child label past its parent) using reported text widths. Real glyph advances differ substantially from any estimate, and estimation errors compound down the label chain — at deep zoom this surfaced to users as jumbled, overlapping letters (issue #56). Frontends rendering the command buffer should register a measurement callback that uses the same font they draw text commands with:

typedef int (*dasher_text_size_callback)(const char* text, int font_size,
                                         int* out_width, int* out_height,
                                         void* user_data);

void dasher_set_text_size_callback(dasher_ctx* ctx, dasher_text_size_callback callback, void* user_data);
void dasher_text_metrics_changed(dasher_ctx* ctx);
  • Fill *out_width/*out_height in pixels and return 0; return non-zero to fall back to the engine's estimate (the failure is not cached — the next frame retries).
  • The callback fires on the thread calling dasher_frame(); results are cached per label and font size, so steady-state frames don't re-measure.
  • Registering before dasher_set_screen_size is fine — the callback is forwarded when the screen is created.
  • After the canvas font changes (e.g. SP_DASHER_FONT), call dasher_text_metrics_changed() to invalidate the cache.
  • Wrapped labels (the paused/lock message) are not measured through the callback; they always use the estimate.
  • Without a callback the engine estimates width as utf8_codepoints × fontSize / 2 (code points, not bytes — accented text previously over-measured). Good enough to run; not good enough for deep zoom.

Message Callback

typedef void (*dasher_message_callback)(int message_type, const char* text, void* user_data);
void dasher_set_message_callback(dasher_ctx* ctx, dasher_message_callback callback, void* user_data);

Receives engine messages (warnings, errors, info). When registered, default canvas-message rendering is suppressed — the frontend handles display. Message types:

Type Meaning
0 Informational (non-modal)
1 Warning (modal, pauses entry)

Alphabets

int         dasher_get_alphabet_count(dasher_ctx* ctx);
const char* dasher_get_alphabet_name(dasher_ctx* ctx, int index);
const char* dasher_get_alphabet_id(dasher_ctx* ctx);
void        dasher_set_alphabet_id(dasher_ctx* ctx, const char* alphabet_id);
  • Alphabet IDs are human-readable names like "English with limited punctuation".
  • Setting a new alphabet clears the edit buffer.
  • If called before dasher_set_screen_size, the alphabet is stored and applied after initialization.

Language Models

int         dasher_get_language_model_count(void);             // Number of registered LMs
int         dasher_get_language_model_id_at(int index);        // ID by index (0..count-1)
const char* dasher_get_language_model_name(int id);            // Display name
const char* dasher_get_language_model_description(int id);     // Description
int         dasher_get_language_model_id(dasher_ctx* ctx);     // Current active LM
void        dasher_set_language_model_id(dasher_ctx* ctx, int model_id);  // Switch LM
int         dasher_get_language_model_param_count(int id);     // LM-specific params
int         dasher_get_language_model_param_key(int id, int index);

Built-in Language Models

ID Name Description
0 PPM Prediction by Partial Match (default)
2 Word Word-level language model
3 Mixture PPM + Dictionary blend
4 CTW Context Tree Weighting

Note: ID 1 is unused (historical). IDs 5+ are available for external LMs.

LM-Specific Parameters

Each LM exposes relevant tuning parameters via dasher_get_language_model_param_count/key:

LM Parameters
PPM LP_LM_ALPHA, LP_LM_BETA, LP_LM_MAX_ORDER, LP_LM_EXCLUSION, LP_LM_UPDATE_EXCLUSION
Word LP_LM_WORD_ALPHA, LP_LM_MAX_ORDER
Mixture All PPM params + LP_LM_MIXTURE, LP_LM_WORD_ALPHA
CTW LP_LM_MAX_ORDER

See LM_REGISTRY.md for details on registering custom LMs.

Speed Control

int  dasher_get_speed_percent(dasher_ctx* ctx);
void dasher_set_speed_percent(dasher_ctx* ctx, int percent);
  • Range: the engine's declared LP_MAX_BITRATE bounds (raw 1–1000 → ~1–625 %, in whole-percent steps of the raw unit). The previous fixed 20–400 clamp truncated Dasher v5's top speeds (v5 allowed raw 10–800 = up to 500 %); values are now clamped to the manifest range instead. Default: 100.
  • Internally maps to LP_MAX_BITRATE: bitrate = percent / 100.0 * 160

Parameters

DasherCore has a self-describing parameter schema with 99 parameters across three types:

Type Prefix Count Accessors
Boolean BP_* 30 dasher_get/set_bool_parameter
Long LP_* 56 dasher_get/set_long_parameter
String SP_* 13 dasher_get/set_string_parameter

Generic Parameter Access

int         dasher_get_bool_parameter(dasher_ctx* ctx, int key);
void        dasher_set_bool_parameter(dasher_ctx* ctx, int key, int value);
long        dasher_get_long_parameter(dasher_ctx* ctx, int key);
void        dasher_set_long_parameter(dasher_ctx* ctx, int key, long value);
const char* dasher_get_string_parameter(dasher_ctx* ctx, int key);
void        dasher_set_string_parameter(dasher_ctx* ctx, int key, const char* value);
int         dasher_find_parameter_key(const char* enum_key_name);

Parameter keys are defined in src/DasherCore/Parameters.h. Use dasher_find_parameter_key to look up keys by name (e.g. "LP_LANGUAGE_MODEL_ID").

Parameter Introspection

Frontends can build settings UIs dynamically:

typedef struct dasher_parameter_info {
    int key;                // BP_*/LP_*/SP_* enum value
    const char* name;       // human-readable name
    const char* desc;       // human-readable description
    int type;               // 0=bool, 1=long, 2=string
    int ui_type;            // 0=none, 1=switch, 2=slider, 3=step, 4=enum, 5=textField
    long min_val;           // minimum value (numeric)
    long max_val;           // maximum value (numeric)
    long step;              // step size
    int advanced;           // 1 if advanced/expert setting
    const char* group;      // "Input", "Language", "Appearance", "Customization", "Output", "Game Mode"
    const char* subgroup;   // filter class (e.g. "CSmoothingFilter")
} dasher_parameter_info;

int dasher_get_parameter_count(void);
int dasher_get_parameter_info(int index, dasher_parameter_info* out);

Enum Values

For parameters with ui_type == 4 (enum):

int         dasher_get_parameter_enum_count(int key);
const char* dasher_get_parameter_enum_name(int key, int index);
int         dasher_get_parameter_enum_value(int key, int index);

String Values

For string parameters (e.g. alphabet list, palette list):

int dasher_get_parameter_string_values(dasher_ctx* ctx, int key, const char** out_names, int max_out);

Returns count; copies up to max_out pointers. Pointers valid until next API call.

Parameter Groups

Parameters are organized into groups for UI presentation:

Group Contents
Input Input filters, control modes, smoothing, buttons
Language Alphabet, language model, LM tuning, adaptive learning
Appearance Font, node shape, geometry, transparency
Customization Mouse line, colour palette, zoom steps, margins
Output Speed, framerate, clipboard, speech
Game Mode Help path drawing, distance/time settings

Settings Schema

Parameters are defined in settings_manifest.json and auto-generated into Parameters.cpp. Each entry includes:

  • Key, storage name, type, default value
  • Label and description (localizable)
  • UI type hint, min/max/step
  • Group and subgroup
  • Tier (common / advanced / expert)
  • Optional: dependsOn, platformDefaults, enumValues, persistence

Colour Palettes

int         dasher_get_palette_count(dasher_ctx* ctx);
const char* dasher_get_palette_name(dasher_ctx* ctx, int index);
const char* dasher_get_current_palette(dasher_ctx* ctx);
int         dasher_get_palette_preview_colors(dasher_ctx* ctx, int index, int* out_colors);
void        dasher_set_palette(dasher_ctx* ctx, const char* palette_name);

dasher_get_palette_preview_colors writes 4 ARGB preview colours into out_colors (must have room for 4 ints). Returns 0 on success.

Appearance / dark mode (RFC 0007)

DasherCore owns a light/dark appearance model so frontends don't each reinvent the System/Light/Dark toggle, the companion lookup, or the palette-preference storage. State persists to <user_dir>/appearance_settings.xml. The active palette (returned by dasher_get_current_palette) is derived from mode + system input + preferences, so an auto-switch can never overwrite the user's explicit choice across restarts.

int         dasher_get_palette_appearance(dasher_ctx* ctx, int index);   // DASHER_PALETTE_APPEARANCE_*; -1=oor
const char* dasher_find_companion_palette(dasher_ctx* ctx, const char* palette_name); // "" if none (v2; was NULL)

int         dasher_get_appearance_mode(dasher_ctx* ctx);                 // DASHER_APPEARANCE_MODE_*
void        dasher_set_appearance_mode(dasher_ctx* ctx, int mode);
int         dasher_get_system_appearance(dasher_ctx* ctx);               // DASHER_PALETTE_APPEARANCE_LIGHT/DARK (transient)
void        dasher_set_system_appearance(dasher_ctx* ctx, int appearance);

const char* dasher_get_light_palette(dasher_ctx* ctx);                   // persisted preferences
const char* dasher_get_dark_palette(dasher_ctx* ctx);
void        dasher_set_light_palette(dasher_ctx* ctx, const char* name);
void        dasher_set_dark_palette(dasher_ctx* ctx, const char* name);
void        dasher_set_user_palette(dasher_ctx* ctx, const char* name);  // sets current side + defaults other
  • Mode SYSTEM follows dasher_set_system_appearance; LIGHT/DARK force that side.
  • Two preferences (light_palette, dark_palette) are stored independently, so a user can mix — e.g. Rainbow for light, TurboLUT Dark for dark. dasher_set_user_palette sets the current effective side and defaults the other to the chosen palette's companion.
  • dasher_set_palette routes through dasher_set_user_palette, so existing pickers stay correct within the model.

Typical frontend usage:

// On launch and whenever the OS appearance changes:
dasher_set_system_appearance(ctx, os_is_dark ? DASHER_PALETTE_APPEARANCE_DARK : DASHER_PALETTE_APPEARANCE_LIGHT);

// Settings UI: System / Light / Dark control
dasher_set_appearance_mode(ctx, mode);  // DASHER_APPEARANCE_MODE_*

Game Mode

int         dasher_enter_game_mode(dasher_ctx* ctx);       // Returns 0 on success, -1 if no text
void        dasher_leave_game_mode(dasher_ctx* ctx);
int         dasher_game_mode_active(dasher_ctx* ctx);      // 1=on, 0=off
void        dasher_game_set_canvas_text(dasher_ctx* ctx, int enabled);  // Suppress canvas text
const char* dasher_game_get_target_text(dasher_ctx* ctx);  // Target sentence
int         dasher_game_get_correct_count(dasher_ctx* ctx); // Correct symbols (or -1)
int         dasher_game_get_target_length(dasher_ctx* ctx); // Total symbols (or -1)
const char* dasher_game_get_wrong_text(dasher_ctx* ctx);   // Wrong text since last correct

Game mode provides a typing tutor where users type a target sentence. The frontend can either:

  • Let the engine render game UI on canvas (default)
  • Suppress canvas text with dasher_game_set_canvas_text(ctx, 0) and render its own UI using the game query functions

Localization

int         dasher_set_locale(dasher_ctx* ctx, const char* locale);
const char* dasher_get_locale(dasher_ctx* ctx);
void        dasher_set_string_override(dasher_ctx* ctx, const char* key, const char* value);
const char* dasher_get_localized_string(dasher_ctx* ctx, const char* key);
  • dasher_set_locale loads Data/Strings/strings_{locale}.json. Returns 0 on success, -1 if not found. NULL or "en" resets to English defaults.
  • dasher_set_string_override overrides a specific translatable string by key (e.g. "BP_DRAW_MOUSE_LINE.label"). Pass NULL value to clear. Overrides take precedence over locale files.
  • dasher_get_localized_string returns the string for a key (checks overrides first, then locale file). Returns NULL if not found.

String Key Format

Keys follow the pattern: {PARAM_KEY}.label, {PARAM_KEY}.description, {PARAM_KEY}.enum.{Enum Label}

Examples:

  • "BP_DRAW_MOUSE_LINE.label" → "Draw Mouse Line"
  • "LP_GEOMETRY.enum.Old Style" → translated enum label

Persistence

void dasher_save_settings(dasher_ctx* ctx);

Saves current settings to the XML file specified at creation (<user_dir>/dasher_settings.xml).

void dasher_reset_settings(dasher_ctx* ctx);

Resets every parameter to its built-in default value (from Parameters.h). It routes through the typed SetBoolParameter / SetLongParameter / SetStringParameter methods, so the normal parameter-change notifications fire and a live engine reconfigures itself (alphabet, colour palette, and language model are reloaded, etc.).

This only affects the in-memory session — it does not delete the persisted files. Frontends that want persisted defaults should delete dasher_settings.xml (and appearance_settings.xml for the RFC 0007 appearance sidecar) from the user directory before calling, so defaults also load on the next launch. Null-safe: a no-op for NULL ctx.

Training data

const char* dasher_get_training_path(dasher_ctx* ctx);   // absolute path, engine-owned
int dasher_import_training_text(dasher_ctx* ctx, const char* text);
int dasher_capi_version(void);                           // 1 = user-dir training scan
  • dasher_get_training_path — the single file adaptive learning appends to for the current alphabet (<user_dir>/training_<alphabet>.txt at the user-dir root). Frontend training UIs (export / size / reset) must read this path, never a derived one. Empty when no model is realized. Pointer valid until the next API call on the context.
  • Since CAPI version 1, the startup training load scans the per-context user data directory (in addition to the bundled data dir) — learning accumulated in previous sessions is loaded automatically for split-dir frontends (Android, GTK, Apple). Single-dir setups (data_dir == user_dir, Windows today) are scanned once, never double-trained. Frontends that previously re-imported the training file after dasher_create as a stopgap must gate that on dasher_capi_version() >= 1 being false, or the same text will be counted twice.

Frontend Integration Examples

Swift (iOS)

var error: UnsafeMutablePointer<CChar>?
let ctx = dasher_create(bundlePath, docPath, &error)
dasher_set_screen_size(ctx, 800, 600)
dasher_set_output_callback(ctx, { eventType, text, userData in
    // inject text into UITextView
}, nil)

// In display link callback:
dasher_mouse_move(ctx, Float(touch.x), Float(touch.y))
var cmds: UnsafeMutablePointer<Int32>?; var cmdCount: Int32 = 0
var strs: UnsafeMutablePointer<UnsafePointer<CChar>?>?; var strCount: Int32 = 0
dasher_frame(ctx, Int64(Date().timeIntervalSince1970 * 1000), &cmds, &cmdCount, &strs, &strCount)
// render with UIKit/CoreGraphics

Kotlin (Android)

val ctx = dasher_create(dataDir, userDir, null)
dasher_set_screen_size(ctx, width, height)

// In render loop:
dasher_mouse_move(ctx, x.toFloat(), y.toFloat())
val cmds = IntArray(10000)
val cmdCount = intArrayOf(0)
dasher_frame(ctx, System.currentTimeMillis(), cmds, cmdCount, null, null)
// render with Canvas API

Important Notes

  1. dasher_set_screen_size must be called before dasher_frame — it triggers engine initialization.
  2. out_command_count is total int count, not command count — divide by 6 for command count.
  3. String pointers are ephemeral — copy immediately if you need the value beyond the current API call.
  4. Localization state is global — changing locale in one context affects all contexts (shared static state).
  5. Speed percent mapping — 100% = LP_MAX_BITRATE of 160, clamped to the engine's declared LP_MAX_BITRATE range (not a fixed percent cap).
  6. Language model ID gap — IDs are 0, 2, 3, 4 (no ID 1). Historical.
  7. No font rendering — but text measurement is frontend-supplied — the engine never rasterises text; frontends draw DASHER_CMD_TEXT commands with a real font. Register dasher_set_text_size_callback so label layout uses that font's actual metrics; without it, width is estimated as utf8_codepoints × fontSize / 2 (see Text Measurement Callback, issue #56).
  8. Polygons are decomposed into line segments — no filled polygon opcode.
  9. Fully transparent elements are skipped — commands with alpha=0 are not emitted.