WritingSystem.h header

#include <ew/core/WritingSystem.h>

Namespace ew::core

CompositionSlot struct

struct ew::core::CompositionSlot

One position within an assembled glyph: where a primitive is placed and which primitives may go there.

Members

double ew::core::conlang::CompositionSlot::offsetX = 0.0

Where the primitive's box is placed horizontally, as a fraction of the glyph box (0.0-1.0).

double ew::core::conlang::CompositionSlot::offsetY = 0.0

Where the primitive's box is placed vertically, as a fraction of the glyph box.

double ew::core::conlang::CompositionSlot::scale = 1.0

How the primitive is scaled into the slot (1.0 = the full glyph box).

QString ew::core::conlang::CompositionSlot::fills

Which primitives may fill this slot, matched against GlyphPrimitive::featureTags. Empty means any primitive.

bool ew::core::conlang::CompositionSlot::operator==(const CompositionSlot &other) const =default

Value equality over all fields.

Glyph struct

struct ew::core::Glyph

One glyph of a constructed writing system: a named symbol that spells a grapheme, with an optional vector outline. The grapheme is the romanized spelling this glyph writes (so a word can be converted to glyphs); svgPath is the glyph's shape as SVG path data (empty for a glyph that has been named/mapped but not yet drawn), which lets the script be rendered and exported as artwork.

Members

QString ew::core::conlang::Glyph::name

The glyph's name/identifier (e.g. "aleph", "vowel-a").

QString ew::core::conlang::Glyph::grapheme

The romanized grapheme this glyph writes (e.g. "a", "sh"); empty for a decorative glyph.

QString ew::core::conlang::Glyph::svgPath

The glyph outline as SVG path data (the d attribute), in a nominal 100x100 em box; empty when the glyph has not been drawn yet.

QString ew::core::conlang::Glyph::variantOf

The glyph this one is a variant OF (its name), when the script writes a related sense with the same drawing in another orientation. Empty for a base glyph.

With transform this says "one symbol, two orientations" rather than forcing a second hand-rotated svgPath – which would double the inventory and assert the script has two unrelated symbols where it has one.

QString ew::core::conlang::Glyph::transform

How this variant is derived from the glyph it varies; empty for a base glyph or an independently drawn one. A renderer applies it to the base glyph's path rather than needing its own outline. Use a GlyphTransform token (see toToken) – the axis each names is defined there, because "mirror" alone is exactly the ambiguity that gets a flip built as a rotation.

std::vector<GlyphVariant> ew::core::conlang::Glyph::variants

The named alternate orientations of this glyph, each a transform of the base outline.

Preferred over a second glyph pointing back via variantOf when one symbol simply has several orientations: it keeps them on the one glyph they belong to, so they cannot drift apart and the inventory does not double.

std::vector<QString> ew::core::conlang::Glyph::primitiveIds

The primitives this glyph is assembled from, by GlyphPrimitive::id, in slot order. Empty for a glyph drawn directly as svgPath.

QString ew::core::conlang::Glyph::unit

What this glyph stands for – the sound, syllable, root or word it writes, in the language's own terms. Distinct from grapheme, which is how that unit is spelled in the Latin alphabet: a syllabary glyph's unit is "ka" while its grapheme may be several letters, and a logograph's unit is a whole word.

std::vector<Stroke> ew::core::conlang::Glyph::strokes

The glyph as DRAWN: its strokes, in the order they are written.

This is the authored form, and it supersedes svgPath, which is a rendering of it. A glyph with strokes renders from them; one without renders its path, so a script authored before the editor existed, or imported from SVG, keeps working untouched.

double ew::core::conlang::Glyph::advance = 1.0

How far the pen advances after this glyph, as a fraction of the glyph box. 1.0 sets glyphs edge to edge; less overlaps them, more spaces them out.

QString ew::core::conlang::Glyph::notes

Free-form notes about the glyph – what it derives from, why it is shaped this way.

bool ew::core::conlang::Glyph::isDrawn() const

Whether the glyph has been drawn at all (by strokes, by an outline, or by composition).

bool ew::core::conlang::Glyph::operator==(const Glyph &other) const =default

Value equality over all fields.

GlyphComposition struct

struct ew::core::GlyphComposition

How a script assembles primitives into one glyph: the ordered placements a symbol's parts occupy.

With this the glyph inventory is GENERATED rather than authored, which is both the only tractable way to express a script with a four-figure symbol count and the only way to keep the relationship between the symbols – the thing the script actually encodes – recorded rather than implied.

Members

std::vector<CompositionSlot> ew::core::conlang::GlyphComposition::placements

The slots a symbol's parts occupy, in the order the parts are given.

bool ew::core::conlang::GlyphComposition::joined = false

Whether the assembled parts are drawn as one continuous joined shape rather than as separate marks sitting side by side.

bool ew::core::conlang::GlyphComposition::isEmpty() const

Whether the composition carries no slots (so a script composes nothing).

bool ew::core::conlang::GlyphComposition::operator==(const GlyphComposition &other) const =default

Value equality over all fields.

GlyphPrimitive struct

struct ew::core::GlyphPrimitive

One featural primitive: the smallest drawn unit a glyph is assembled from.

A featural script (Hangul is the familiar example) draws a small set of parts that encode articulatory features and builds every symbol by combining them. Modelling only whole glyphs forces such a script to be hand-authored symbol by symbol – 19 primitives can yield well over a thousand symbols – and, worse, throws away the systematicity that is the entire point of the design, leaving a thousand shapes with no recorded relationship to each other.

Members

QString ew::core::conlang::GlyphPrimitive::id

A stable identifier, referenced when composing a glyph.

QString ew::core::conlang::GlyphPrimitive::name

The primitive's display name (e.g. "closed bilabial").

QString ew::core::conlang::GlyphPrimitive::grapheme

The sound or letter this primitive writes; empty for a purely decorative part.

std::vector<QString> ew::core::conlang::GlyphPrimitive::featureTags

What this part encodes (e.g. "bilabial", "voiced", "tongue-high") – the features the design derives the shape from, and what a composition slot can require.

QString ew::core::conlang::GlyphPrimitive::svgPath

The part's outline as SVG path data, in the same nominal 100x100 em box a glyph uses.

std::vector<Stroke> ew::core::conlang::GlyphPrimitive::strokes

The part as DRAWN, in glyph-box coordinates. Supersedes svgPath the same way a glyph's strokes supersede its path, so a primitive can be drawn in the editor and then edited – and editing it updates every glyph assembled from it.

bool ew::core::conlang::GlyphPrimitive::hasFeature(const QString &tag) const

Whether tag is one of this primitive's feature tags (case-insensitive).

bool ew::core::conlang::GlyphPrimitive::operator==(const GlyphPrimitive &other) const =default

Value equality over all fields.

GlyphVariant struct

struct ew::core::GlyphVariant

One alternate orientation of a glyph: a name for what the orientation MEANS, and the transform that produces it from the base drawing.

Modelled as a variant of one glyph rather than as a second glyph because that is what it is. Drawing it again would double the inventory and assert the script has two unrelated symbols where it has one symbol in two orientations – and would let the two drift apart.

Members

QString ew::core::conlang::GlyphVariant::name

What this orientation means (e.g. "inverted", "sacred").

GlyphTransform ew::core::conlang::GlyphVariant::transform =

How it is derived from the base glyph's outline.

bool ew::core::conlang::GlyphVariant::operator==(const GlyphVariant &other) const =default

Value equality over all fields.

LigatureRule struct

struct ew::core::LigatureRule

A ligature rule: a sequence of graphemes that is written as a single combined glyph rather than the individual glyphs. Modelling ligatures lets a script join letters (as many real scripts do).

Members

QString ew::core::conlang::LigatureRule::sequence

The grapheme sequence that ligates (e.g. "ae", "ll"); matched greedily, longest first.

QString ew::core::conlang::LigatureRule::glyphName

The name of the Glyph that spells the whole sequence; empty when the sequence is COMPOSED from primitiveIds instead of naming a glyph that was drawn in advance.

std::vector<QString> ew::core::conlang::LigatureRule::primitiveIds

The primitives the sequence assembles into, by GlyphPrimitive::id, in slot order.

Without this a ligature could only point at a glyph that already existed, so it could never help BUILD one – which is useless to a script whose symbols are assembled from parts, where the combined form is exactly what needs deriving rather than drawing.

bool ew::core::conlang::LigatureRule::operator==(const LigatureRule &other) const =default

Value equality over all fields.

PenSettings struct

struct ew::core::PenSettings

How a script's stored skeletons are inked: the pen that turns an ordered list of points into something that looks written rather than plotted.

Every parameter is per-script and live-adjustable, because these are the settings a designer tunes by eye against real words – not values anyone can choose correctly in advance.

Members

double ew::core::conlang::PenSettings::nibWidth = 0.06

The nib's width as a fraction of the glyph box.

double ew::core::conlang::PenSettings::nibAngle = 0.0

The nib's angle in degrees, for broad-nib contrast (0 draws an even line).

PressureModel ew::core::conlang::PenSettings::pressureModel =

How the width varies along a stroke.

double ew::core::conlang::PenSettings::pressureDepth = 0.5

How strongly pressure affects width, 0 (no effect) to 1 (full range).

double ew::core::conlang::PenSettings::taper = 0.0

How far the stroke narrows towards its ends, 0 (no taper) to 1 (to a point).

double ew::core::conlang::PenSettings::taperExponent = 1.0

The exponent shaping the taper: 1 is linear, higher keeps the stroke full for longer before closing sharply.

double ew::core::conlang::PenSettings::tremor = 0.0

How far the inked line wanders from the skeleton, as a fraction of the glyph box.

A slow smooth wander ALONG the path, never per-point jitter, which reads as noise rather than as a hand. It is also deterministic, seeded per glyph: a word that renders differently each time it appears stops being a script.

double ew::core::conlang::PenSettings::tremorScale = 0.25

The wavelength of the tremor along the stroke, as a fraction of its length. Smaller values wobble more often.

bool ew::core::conlang::PenSettings::isEmpty() const

Whether the pen carries nothing but defaults.

bool ew::core::conlang::PenSettings::operator==(const PenSettings &other) const =default

Value equality over all fields.

ScriptDirections struct

struct ew::core::ScriptDirections

The three nested directions a script can run in, which need not agree.

One direction is not enough for a script that assembles glyphs from parts: a real design can run its sounds left-to-right INSIDE a glyph, stack those glyphs top-to-bottom inside a word, and set the words left-to-right across the page. Collapsing that to a single value forces two of the three to be wrong.

Members

TextDirection ew::core::conlang::ScriptDirections::withinGlyph =

How a glyph's own parts are laid out within it.

TextDirection ew::core::conlang::ScriptDirections::withinWord =

How glyphs are laid out within one word.

TextDirection ew::core::conlang::ScriptDirections::acrossPage =

How words run across the page.

bool ew::core::conlang::ScriptDirections::operator==(const ScriptDirections &other) const =default

Value equality over all fields.

ScriptGuide struct

struct ew::core::ScriptGuide

One named guide line on the glyph box.

Guides are per-script and user-defined rather than a fixed Latin set: a Devanagari-like script hangs from a headline, some scripts sit on a centre line, and some have no line at all. Shipping baseline/x-height/cap-height as a hardcoded rule would be wrong for most invented scripts.

Members

QString ew::core::conlang::ScriptGuide::name

What the line is (e.g. "baseline", "x-height", "headline").

GuideOrientation ew::core::conlang::ScriptGuide::orientation =

Which way the line runs.

double ew::core::conlang::ScriptGuide::position = 0.0

Where it sits in glyph-box coordinates (0..1 along the axis it crosses).

bool ew::core::conlang::ScriptGuide::snap = true

Whether a drawn point snaps to this line when it comes close.

bool ew::core::conlang::ScriptGuide::operator==(const ScriptGuide &other) const =default

Value equality over all fields.

ScriptMetrics struct

struct ew::core::ScriptMetrics

The drawing surface a glyph is made on: its guides, its proportions, and the constraints that hold across every glyph of the script.

Members

std::vector<ScriptGuide> ew::core::conlang::ScriptMetrics::guides

The guide lines, in display order.

double ew::core::conlang::ScriptMetrics::boxAspect = 1.0

The glyph box's width divided by its height. 1.0 is square; a tall narrow script uses less.

double ew::core::conlang::ScriptMetrics::baseline = 0.65

Where the writing line sits, as a fraction down the glyph box: the line letters are written ON, with the space beneath it for anything that descends.

Deliberately not at the foot of the box. A baseline flush with the bottom leaves nowhere for the tail of a g or a q to go, and a designer cannot tell how large to draw without a line to draw against in the first place. Always painted, unlike the user-defined guides – it is the one line every script has – but per-script, because scripts sit differently.

bool ew::core::conlang::ScriptMetrics::anchoredEnds = false

Whether every glyph's first and last point are pinned to startAnchor and endAnchor.

This is what makes a connected script connect: with it on, one glyph's end anchor IS the next glyph's start anchor, so a word renders as one continuous line rather than as separate marks. The anchors are drawn and cannot be moved; every point the user places is interior.

StrokePoint ew::core::conlang::ScriptMetrics::startAnchor {.x = 0.0, .y = 1.0, .pressure = 1.0, .corner = false}

Where an anchored glyph's first point is pinned, in glyph-box coordinates.

StrokePoint ew::core::conlang::ScriptMetrics::endAnchor {.x = 1.0, .y = 1.0, .pressure = 1.0, .corner = false}

Where an anchored glyph's last point is pinned, in glyph-box coordinates.

int ew::core::conlang::ScriptMetrics::gridDivisions = 8

How many divisions the drawing grid has along each axis; 0 for no grid.

A drawing surface defaults to HAVING one. With no grid the glyph box is an empty rectangle that says nothing about how large a mark should be or where it sits relative to the last one, which is the first question a script designer asks and the hardest to answer by eye. Eight divisions read as halves, quarters and eighths without becoming a texture.

bool ew::core::conlang::ScriptMetrics::snapToGrid = false

Whether a drawn point snaps to the grid.

Independent of gridDivisions on purpose: seeing the grid and being held to it are different requests, and a designer laying out proportions wants the first far more often than the second. Freehand pulled onto an eighth is not freehand.

double ew::core::conlang::ScriptMetrics::snapAngle = 0.0

The angle increment, in degrees, a drawn segment snaps to; 0 for free angles.

bool ew::core::conlang::ScriptMetrics::isEmpty() const

Whether the metrics carry nothing but defaults, so a script can omit them.

const ScriptGuide * ew::core::conlang::ScriptMetrics::guide(const QString &name) const

The guide named name, or nullptr when the script has none.

bool ew::core::conlang::ScriptMetrics::operator==(const ScriptMetrics &other) const =default

Value equality over all fields.

Stroke struct

struct ew::core::Stroke

One stroke of a glyph: an ordered run of points and how they are joined.

A glyph is a LIST of these, not one path. Latin "i" is two strokes and a Chinese character can be twenty, so a single-path model cannot express most scripts; and the ORDER is data, not presentation – stroke order is taught, tested and culturally fixed in several writing systems, and a model that loses it cannot produce teaching material.

Members

std::vector<StrokePoint> ew::core::conlang::Stroke::points

The points, in the order they are drawn.

bool ew::core::conlang::Stroke::closed = false

Whether the last point joins back to the first (a closed shape such as a ring).

StrokeSmoothing ew::core::conlang::Stroke::smoothing =

How the points are joined when drawn.

double ew::core::conlang::Stroke::penScale = 1.0

A per-stroke multiplier on the pen's nib width, so one stroke of a glyph can be heavier than its neighbours without changing the script's pen.

bool ew::core::conlang::Stroke::hidden = false

Whether the stroke is hidden while editing. A working state, kept with the stroke so it survives a reload – hiding a stroke to draw behind it and losing that on save is the kind of small betrayal that makes an editor feel unreliable.

bool ew::core::conlang::Stroke::locked = false

Whether the stroke is locked against editing.

bool ew::core::conlang::Stroke::isEmpty() const

Whether the stroke has no points at all.

bool ew::core::conlang::Stroke::operator==(const Stroke &other) const =default

Value equality over all fields.

StrokePoint struct

struct ew::core::StrokePoint

One sampled point of a stroke, in glyph-box coordinates.

Coordinates are NORMALISED to the glyph box (0..1 on each axis) rather than stored in pixels or em units, so the script renders at any size and the metrics can be changed later without rewriting a single glyph.

Members

double ew::core::conlang::StrokePoint::x = 0.0

Horizontal position within the glyph box, 0 at the left edge and 1 at the right.

double ew::core::conlang::StrokePoint::y = 0.0

Vertical position within the glyph box, 0 at the top edge and 1 at the bottom.

double ew::core::conlang::StrokePoint::pressure = 1.0

Pen pressure when the point was captured, 0..1; 1.0 for a point that was placed rather than drawn. Pressure is the single biggest contributor to a drawn script looking drawn, so it is captured per point rather than synthesised for the whole stroke.

bool ew::core::conlang::StrokePoint::corner = false

Whether the path turns a hard corner here rather than passing smoothly through. A smoothing curve is broken at a corner point, which is how one stroke can hold both a curve and an angle without being split in two.

bool ew::core::conlang::StrokePoint::operator==(const StrokePoint &other) const =default

Value equality over all fields.

VariantTrigger struct

struct ew::core::VariantTrigger

A mark that asks for a glyph in a named orientation.

A script can invert a word's MEANING by flipping its symbol, which makes the orientation grammatical rather than decorative – so the text has to be able to request one. Modelled as a list because the mark is the script's own choice (a diacritic, a control character, a bracket) and a script may have more than one such contrast.

Members

QString ew::core::conlang::VariantTrigger::marker

The mark that requests the orientation, as it appears in the text.

QString ew::core::conlang::VariantTrigger::variantName

The GlyphVariant::name it selects.

bool ew::core::conlang::VariantTrigger::operator==(const VariantTrigger &other) const =default

Value equality over all fields.

WritingSystem struct

struct ew::core::WritingSystem

A constructed writing system (conscript): a named set of glyphs, the writing direction, and ligatures. It layers over the language's romanization – each glyph maps a grapheme to a drawn form – so a word can be converted into a glyph sequence, rendered on screen, and exported as artwork. Empty for a language written only in the Latin alphabet.

Members

QString ew::core::conlang::WritingSystem::name

The script's display name (e.g. "Keshan runes").

TextDirection ew::core::conlang::WritingSystem::direction =

The writing direction across the page. Kept as the script's primary direction; directions carries the nested ones for a script whose parts, glyphs and words do not all run the same way.

ScriptDirections ew::core::conlang::WritingSystem::directions

The three nested directions (within a glyph, within a word, across the page).

std::vector<Glyph> ew::core::conlang::WritingSystem::glyphs

The glyphs, in the script's own order.

std::vector<GlyphPrimitive> ew::core::conlang::WritingSystem::primitives

The featural primitives glyphs are assembled from; empty for a script drawn glyph by glyph.

GlyphComposition ew::core::conlang::WritingSystem::composition

How primitives assemble into one glyph; empty for a script drawn glyph by glyph.

bool ew::core::conlang::WritingSystem::noDescenders = false

Whether every glyph sits entirely above the writing line (no descenders).

Recorded because a script can depend on it: flipping a symbol across the horizontal axis stays clean only when nothing hangs below the line, so this is a real constraint of such a design rather than a stylistic note.

std::vector<LigatureRule> ew::core::conlang::WritingSystem::ligatures

The ligature rules.

ScriptUnitKind ew::core::conlang::WritingSystem::unitKind =

What one glyph of this script stands for, which decides the candidate list the inventory offers.

ScriptMetrics ew::core::conlang::WritingSystem::metrics

The drawing surface: guides, proportions, anchors, grid and snapping.

PenSettings ew::core::conlang::WritingSystem::pen

The pen the script's stored skeletons are inked with.

std::vector<VariantTrigger> ew::core::conlang::WritingSystem::variantTriggers

The marks a text can use to ask for a glyph in a named orientation.

bool ew::core::conlang::WritingSystem::isEmpty() const

Whether the writing system carries no glyphs, ligatures, or name (only defaults).

const GlyphPrimitive * ew::core::conlang::WritingSystem::primitiveById(const QString &id) const

The primitive with GlyphPrimitive::id id, or nullptr if none.

const Glyph * ew::core::conlang::WritingSystem::glyphForGrapheme(const QString &grapheme) const

The glyph that writes grapheme (case-sensitive), or nullptr if none.

const Glyph * ew::core::conlang::WritingSystem::glyphByName(const QString &glyphName) const

The glyph named glyphName, or nullptr if none.

const Glyph * ew::core::conlang::WritingSystem::glyphForUnit(const QString &unit) const

The glyph whose Glyph::unit is unit (case-insensitive), or nullptr if none.

QString ew::core::conlang::WritingSystem::variantForMarker(const QString &marker) const

The orientation marker asks for, or an empty string when it asks for none.

int ew::core::conlang::WritingSystem::drawnGlyphCount() const

How many glyphs have actually been drawn, for the inventory's coverage count.

bool ew::core::conlang::WritingSystem::operator==(const WritingSystem &other) const =default

Value equality over all fields.

Enumerations

enum class GlyphTransform { MirrorY, MirrorX, Rotate180, Rotate90, Rotate270 }

A geometric transform relating one drawn form to another. Serialized as a stable lowercase token.

The distinction between a flip and a rotation is load-bearing, not cosmetic: a script can invert a word's meaning by flipping its symbol, and the flip is chosen precisely BECAUSE it preserves the left-to-right order of the glyph's parts, so the flipped form still reads in the normal direction. A 180-degree rotation would reverse that order and read backwards. Each token below therefore names the axis explicitly rather than leaving "mirror" to be guessed.

enum class GuideOrientation { Horizontal, Vertical }

Whether a guide line runs across the glyph box or up it.

enum class PressureModel { Uniform, Captured, Synthesised, Combined }

How a stroke's width varies along its length.

enum class ScriptUnitKind { Phoneme, Syllable, Morpheme, Word, Free }

What one glyph of a script stands for.

The unit varies by script type, and it decides which candidate list the inventory offers – drawn from the phoneme inventory, the syllable templates or the lexicon the language already holds, so the user never retypes what the language knows.

enum class StrokeSmoothing { None, Quadratic, CatmullRom }

How the points of a stroke are joined when it is drawn.

enum class TextDirection { LeftToRight, RightToLeft, TopToBottom, BottomToTop }

The direction a constructed script is written and read in. Serialized as a stable lowercase token.

Functions

std::optional< GlyphTransform > ew::core::conlang::glyphTransformFromToken(QStringView token)

Parses a glyph transform from its token; std::nullopt if unrecognized.

std::optional< GuideOrientation > ew::core::conlang::guideOrientationFromToken(QStringView token)

Parses a guide orientation from its token; std::nullopt if unrecognized.

std::optional< PressureModel > ew::core::conlang::pressureModelFromToken(QStringView token)

Parses a pressure model from its token; std::nullopt if unrecognized.

std::optional< ScriptUnitKind > ew::core::conlang::scriptUnitKindFromToken(QStringView token)

Parses a script unit kind from its token; std::nullopt if unrecognized.

std::optional< StrokeSmoothing > ew::core::conlang::strokeSmoothingFromToken(QStringView token)

Parses a stroke smoothing from its token; std::nullopt if unrecognized.

std::optional< TextDirection > ew::core::conlang::textDirectionFromToken(QStringView token)

Parses a text direction from its token; std::nullopt if unrecognized.

QString ew::core::conlang::toToken(TextDirection direction)

The stable serialization token for direction (e.g. "rtl").

QString ew::core::conlang::toToken(GlyphTransform transform)

The stable serialization token for transform (e.g. "mirror-y").

QString ew::core::conlang::toToken(StrokeSmoothing smoothing)

The stable serialization token for smoothing (e.g. "catmull").

QString ew::core::conlang::toToken(GuideOrientation orientation)

The stable serialization token for orientation (e.g. "horizontal").

QString ew::core::conlang::toToken(PressureModel model)

The stable serialization token for model (e.g. "captured").

QString ew::core::conlang::toToken(ScriptUnitKind kind)

The stable serialization token for kind (e.g. "syllable").