Pictograms must be international in how they show gender, ethnicity and age. Following Unicode's emoji model (UTS #51): - Skin. Every person in Mulberry and OpenMoji is drawn in a generic, non-realistic colour -- UTS #51's own example, #FFCC22 (OpenMoji's lemon would melt into Mulberry's lemon hair, clothes and objects) -- or in the skin tone the person chooses: one of the five modifiers U+1F3FB-U+1F3FF, in OpenMoji's colours (aac_core:🔣:SkinTone). The build writes skin as the CSS custom property --aac-skin (OpenMoji's skin shadow as --aac-skin-shade) over a neutral fill, so a renderer without the style still draws a person neutral, never black. - Mulberry: its authors' aac-skin-fill markers where present; for the rest, a reviewed decision per symbol in people.toml (a cake's icing and a computer case share the skin colour). The build stops on a missing or stale decision. - OpenMoji: its skin layers, checked element by element against its own toned variants. A visual audit of all 397 mapped symbols with the skin colour shown as magenta, and an adversarial second look, confirmed every call; people-review.md lists every symbol that draws a person and how. - Gender and age. Where Mulberry drew a man's face or a child for a word that names neither (the feelings, Speak, eat, drink, doctor, teacher, police, hi...), OpenMoji's emoji face (no gender, age or skin) or Unicode's gender-neutral person is used instead; police wears a cap worn worldwide, not a British helmet. - Hair keeps its colour whatever the tone. - App: Settings > Symbols > People in pictures, and the same choice in setup; kept with the symbol set and applied on the app root. - SPEC 4.3 (proposed): people in symbols. Co-Authored-By: Claude Opus 5.5 <[email protected]>
8.8 KiB
Symbol sets
build.py turns three openly licensed symbol sets into one SVG sprite per set
plus a concept → symbol index, so that every cell can show a picture. The app
build never downloads symbols: it only reads the committed outputs.
| set | licence | role |
|---|---|---|
Mulberry Symbols 3.6.1 (commit 9cbab9f4) |
CC BY-SA 4.0, and the symbols themselves must never be charged for | default style |
| OpenMoji 17.0.0 (colour SVG) | CC BY-SA 4.0 | second style |
| Blissymbolics BCI-AV 2026-07-25 | CC BY-SA 4.0 | third style; covers function words the pictographic sets cannot draw |
Licences were checked at the primary sources on 2026-09-29; the URLs and full
attribution are in crates/aac-web/public/symbols/LICENSES.md. Sets under
NC or ND terms (ARASAAC, Sclera) and proprietary sets (PCS, SymbolStix,
Widgit) are never used.
Everything this tool writes from the sets is itself CC BY-SA 4.0, not the
workspace's Apache-2.0: the sprites, the contact sheets (review/*.png, each
titled with its set's attribution) and mapping-review.md. Mulberry's
condition also binds the product: the symbols, and symbols derived from them,
must stay free of cost and freely available to all, so they can never sit
behind a paid tier or a paid hosted library. Only added value (hosting,
curation) may be charged for.
Outputs
| path | what |
|---|---|
crates/aac-web/public/symbols/<set>.<sha256-12>.svg |
sprite: <symbol id="<set>-<concept-slug>" viewBox="…"> per symbol |
crates/aac-web/public/symbols/LICENSES.md |
attribution for every set |
crates/aac-core/src/symbols/index.json |
sets (licence, attribution, file) and concept → {set: slug} |
crates/aac-core/src/symbols/normalize_cases.json |
parity fixture for normalize_concept in Python and Rust |
tools/symbols/mapping-review.md |
every concept, the symbol each set uses, and how it was chosen |
tools/symbols/people-review.md |
every symbol that draws a person, and whether it takes the chosen skin tone |
tools/symbols/review/<set>.png |
contact sheet of every mapped concept, labelled |
Board cells are drawn with aac_core::symbols::lookup_for_cell(label, action, preferred) (plain lookup(label, preferred) for a bare word); the sprite file
comes from set_info(set).file. Symbol ids carry their set as a prefix
(mulberry-home), so sprites can be inlined into one page without clashes;
referencing them by file (<use href="symbols/<file>#<id>">) works too.
Rebuilding
cd aac/aac-rs
(cd tools/symbols && npm ci) # once: svgo from package-lock.json
python3.14 tools/symbols/build.py # everything above
python3.14 tools/symbols/build.py --review-pages /tmp/p # plus 48-per-page sheets for close review
cargo test -p aac-core symbols && cargo test -p aac-web --test symbol_coverage
Needs Python ≥ 3.11 (standard library only), npm (svgo 4.1.0 and its whole
dependency tree are pinned by package-lock.json; npm ci installs exactly
that into the git-ignored node_modules/, and the build runs it with
svgo.config.mjs), rsvg-convert for the contact sheets, and optionally the
brotli CLI for size reporting. Sources are downloaded once into
tools/symbols/.cache/ (git-ignored) and checked against the SHA-256 pinned
in build.py on every run; a mismatch stops the build. The sprites, index,
fixture and mapping-review.md are deterministic: rebuilding from the same
sources reproduces the same hashes on any machine. The contact sheets depend
on the local rasteriser and fonts (committed ones: rsvg-convert 2.63.2 with
cairo 1.18.6, pango 1.58.2) and are review aids, not reproducible outputs. A
rebuild deletes the previous hashed sprite of each set.
To move to a new release of a set, update its URL and SHA-256 in SOURCES
(and the version in SET_INFO), rebuild, and review the contact sheets.
How concepts are mapped
- Concepts are extracted from the code, not listed by hand: every cell
label of every layout in
crates/aac-web/src/defaults.rsand every word of the core vocabulary incrates/aac-core/src/vocabulary.rs, normalised withnormalize_concept(lowercase, trim, collapse whitespace, strip trailing?!.,;). - Automatic candidates come from each set's own index:
- Mulberry: the symbol name (
eat_,_to→ "eat"; numbered variants rank after the plain one). Letters, flags, maps and anatomically rated symbols are excluded — Mulberry's "I" is the letter, not the pronoun. - OpenMoji: the CLDR annotation. Tags are listed in
mapping-review.mdas proposals but never accepted automatically (the tag "you" is on the waving hand). Skin-tone variants, flags and brand logos are excluded — the annotation "apple" is the Apple logo. - Bliss: the BCI-AV glosses, first gloss before later ones, verb forms
(
-(to)) for verbs. Gendered forms,(OLD)forms and letters are excluded. Where a vocabulary word is a verb, verb forms are preferred.
- Mulberry: the symbol name (
overrides.tomldecides everything ambiguous, one entry per concept per set with a reason:symbol = "<native id>"ornone = trueto reject a wrong automatic match. Where one label means different things on different boards, the default-board sense wins ("break" is the rest break of "I need a break", "chicken" is food).- Cell concepts. A cell whose picture must come from what it does gets
its own concept instead of its label's: message-bar action cells are keyed
by their action (
action:speakall,action:clear), and navigation cells whose label is a homograph by their navigation sense (nav:back, an arrow; plainbackstays the body part of "my back hurts").cell_concept()inbuild.pyandlookup_for_cellin Rust implement the same rule; add a label toNAV_SENSESwhen another navigation label needs its own sense.
A wrong picture is worse than none, so a concept stays unmapped in a set unless the match is confident; the runtime then falls back to the next set.
Sprite safety
Each symbol is optimised by svgo (styles inlined into presentation
attributes), then rebuilt from an allow-list of drawing elements and
attributes: no <script>, <style>, <foreignObject>, <image>, classes,
event attributes or external references survive, and internal ids are
prefixed with the symbol's slug so symbols sharing the sprite cannot collide.
check_sprite() re-verifies the result and the Rust test
sprites_exist_and_contain_every_symbol checks it again.
Locale-sensitive choices (Mulberry's beckoning "come", thumbs for good/bad,
the left arrow for Back in right-to-left scripts) are listed at the top of
overrides.toml for per-locale overrides later.
Known limits: three symbols carry text drawn in the viewer's fallback font (Mulberry's ambulance lettering and runner number, and Bliss 'count').
People
A board is used all over the world, so its people default to no one's skin, gender or age (SPEC §4.3), following Unicode's emoji model:
- Skin. Skin is written as the CSS custom property
--aac-skinover a non-realistic neutral,#ffcc22(UTS #51 §2.4's own example; OpenMoji's lemon#fcea2bwould melt into Mulberry's lemon hair, clothes and objects), so a client draws every person in the skin tone the person chooses (aac_core::symbols::SkinTone: OpenMoji's colours for the five modifiers U+1F3FB–U+1F3FF). The neutral colour stays as thefillattribute under the property, so a renderer that ignores the style still draws a person neutral, never black. Hair keeps its colour whatever the tone: no hair colour belongs to a skin tone.- Mulberry draws skin in one colour,
#ffeec8. Where its authors mark the skin (class="aac-skin-fill"), the build tones exactly those elements. Every other mapped symbol that uses#ffeec8needs a decision inpeople.toml, checked on a contact sheet with that colour shown as magenta:tone = truewhen all of it is skin,tone = falsewith a reason when some is not (a cake's icing, a computer case). The build stops on a symbol without a decision, and on a decision for a symbol no longer drawn. - OpenMoji needs no list: its own skin-tone variants draw the same
elements in the same order, so the build tones exactly the elements they
change to the skin and shadow colours (
--aac-skin,--aac-skin-shade), and leaves a badge that shares the skin's yellow alone. A person Unicode gives no skin tones (a family, a silhouette) is listed as drawn.
- Mulberry draws skin in one colour,
- Gender and age. A word that names no gender or age gets a neutral
picture: where the default set draws a man's face or a child for such a
word,
overrides.tomlmaps it to OpenMoji's emoji face (no gender, age or skin) or to Unicode's gender-neutral person, and the feelings stay one face style. Mulberry's generic figure (bald, shirt and trousers) is kept.
people-review.md lists every symbol that draws a person, and how it is
drawn.