DatumHuedatumhue.com
Contents

Reference

Your registry lists your packages in the datumhue.app store under your namespace and, while it runs, delivers them to the people who install them. It takes new versions from the people your publish rules allow, and keeps a record of who bought each paid package, so that it delivers the package to its buyers alone.

A version is published two ways: from a folder of packages when the registry starts (--seed-from), or with pkg push from the Platform workstation. Both publish a sealed #true app as compiled code, never its Lua source. A published (@scope, name, version) never changes: to publish new content, raise the version. pkg yank withdraws a version from the store and keeps its content and history.

Your registry answers for one namespace: the @scope you claimed when you subscribed, which namespace.capability admits it for. The first page walks through the first start and everyday publishing; this page is the reference behind it. Examples use the Linux and macOS spelling: on Windows the two programs carry .exe.

The launcher

The platform accepts a registry of its current release only, and refuses an older one when it connects. The launcher keeps yours current. On every start it:

  1. Reads where releases are published from release-feed.txt beside it, or from DATUMHUE_RELEASE_FEED_URL when that variable is set. Set to an empty value, the variable turns updates off; then install each release yourself, since the platform refuses an older registry.
  2. Installs a newer launcher and a newer registry when the feed has them, each only once its signature and download check out, and keeps the previous programs beside them as datumhue-launcher.bak and datumhue-registry.bak.
  3. Starts the datumhue-registry beside it, the program release-feed.txt names, with every argument you gave it. DATUMHUE_CLIENT_PATH names a registry installed elsewhere.

When an update fails, because the machine is offline or a download does not check out, the launcher prints why and starts the registry you have.

The platform moves to each new release at once and stops admitting the release before it. A running registry then stops and hands over to its launcher, which waits up to five minutes for the new release on the feed, installs it, and starts the registry again with the same arguments. A service manager or a container sees no restart, so this works however the registry runs. A registry started without its launcher, or with updates turned off, stops instead; see Troubleshooting.

Keep the registry's folder writable by the user that runs it: the launcher installs releases there.

Subcommands

Every argument after datumhue-launcher reaches the registry unchanged:

text
datumhue-launcher keygen      [-o <path>] [--plaintext] [--env] [--force]
datumhue-launcher show-pubkey [-f <path>] [--env]
datumhue-launcher serve       [-c <config>] [--seed-from <dir>]
                              [-a <aggregator-url>]

Run keygen and show-pubkey on ./datumhue-registry directly, as Get started does: they need no update. Run serve through the launcher so every start is current. The flag defaults name the delivered files, so from the registry's folder no path flag is needed.

keygen

Generate a new secret key (the registry's long-lived identity) and store it on disk.

text
datumhue-registry keygen [OPTIONS]
FlagDefaultDescription
--output <OUTPUT>, -oidentity.keyPath to write the secret key file, the name the delivered configuration expects
--plaintextoffStore the key unencrypted. Not recommended for production
--envoffRead the encryption passphrase from DATUMHUE_KEYSTORE_PASSPHRASE instead of prompting. Fails if the variable is unset or empty
--forceoffReplace an existing key file at the output path. Without it, keygen refuses to overwrite: the key on disk is the identity the platform admitted, and replacing it means a key rotation

With neither --plaintext nor --env, keygen asks for a passphrase and encrypts the key with it; an empty passphrase writes it plaintext. A lost passphrase cannot be recovered: replace the key instead. keygen prints the public key, which you paste into the onboarding page, or into the rotation page for a replacement key. While the admitted key is in place, make the replacement with --force, or with -o and a new path.

show-pubkey

Print the public key of a key file: to paste it into the rotation page again, or to compare it with the key the platform admitted.

text
datumhue-registry show-pubkey [OPTIONS]
FlagDefaultDescription
--file <FILE>, -fidentity.keyPath to the secret key file
--envoffRead the decryption passphrase from DATUMHUE_KEYSTORE_PASSPHRASE instead of prompting. Plaintext keys ignore it

serve

Start the registry.

text
datumhue-registry serve [OPTIONS]
FlagDefaultDescription
--config <CONFIG>, -cprovider.kdlPath to the configuration file
--seed-from <SEED_FROM>noneLoad every package in this folder into the catalog before serving. Without it, the catalog is the one the last run kept
--aggregator-url <AGGREGATOR_URL>, -anoneOverride the aggregator-url from the configuration file

serve loads the key secret-key-file names. When that file is missing, serve makes a new key and starts with it. The platform has not admitted that key, so it refuses the registry's announcements, and the capability file does not match it. The registry logs the key it uses as Package Provider Node ID: …: when it is refused, compare that key with the public key you onboarded first.

An encrypted key needs DATUMHUE_KEYSTORE_PASSPHRASE set when serve starts, and a plaintext key needs it unset; otherwise the start fails, and the message says which the key is.

serve then waits for the namespace's Write grant, logging Waiting for the Write capability over '@studio'... until the capability file holds one. With --seed-from it loads the folder's packages; a version already published with other content is skipped with a warning ("already published with different content — keeping the published artifact and skipping the seed copy"): raise the version in package.kdl to publish new content. Catalog holds <n> package(s) reports the result.

The registry then announces your namespace to the platform (Announcer started (TTL: <n>s), then Announced namespace '@studio' (TTL: <n>s) at every renewal) and takes publishes, yanks and prices. When the capability file also holds the issuing grant, it sells paid packages (Paid-package entitlement handlers ready …); without it, it logs Paid-package entitlements disabled (no Read issuing capability) and serves free packages only. Ctrl+C, or the SIGTERM a service manager sends, stops it cleanly.

Configuration

The settings file is KDL with one provider block. A key the block does not know stops the start. A relative path resolves against the folder that holds the file.

The provider.kdl you received on activation:

kdl
provider {
    secret-key-file "./identity.key"
    capability-file "./namespace.capability"
    namespace "@studio"
    aggregator-url "https://<platform host>"
    relay-url "https://<platform host>"
    announce-ttl 300
    blob-store-path "./blobs"
    catalog-store-path "./catalog.redb"
    publish-rules {
        // Fill in who may push to your namespace: `login` prints your
        // sign-in's `sub` and issuer URL. For example:
        // issuer "https://accounts.google.com" {
        //     allow claim="sub" equals="<your sub>" rights="rw"
        // }
    }
}
KeyRequiredDefaultDescription
secret-key-fileyesnoneThe registry's key. Made at the start when missing, as a key the platform has not admitted; see serve above
capability-fileyesnoneThe capability file the platform issued for your key. The registry rewrites it as grants renew, so keep it writable: the registry refuses to start otherwise
namespaceyesnoneThe namespace this registry announces: your @scope, exactly as the platform delivered it
aggregator-urlyesnoneHTTPS URL of the platform, as delivered
relay-urlyesnoneThe URL the registry reaches the platform through: the same URL, as delivered. The start stops if the platform does not offer it
discovery-urlnothe platform'sWhere the registry publishes its address and looks up others
announce-ttlno300How long each announcement stays valid: seconds, or a duration such as "5m", at least 2 seconds. The registry announces again after half of it
webrtc-bind-addrnononeA fixed UDP address for direct browser connections, such as "0.0.0.0:7844". Set it with webrtc-public-addr when the registry runs in a container or behind port forwarding; without it, the registry finds its addresses itself
webrtc-public-addrnononeThe host:port browsers reach webrtc-bind-addr at. Every address it resolves to must route to that socket
blob-store-pathno./provider_blobsThe folder of the package files the registry serves (the delivered settings name ./blobs). The registry must be able to write it, or it stops at start
catalog-store-pathno./catalog.redbThe catalog: every published version
entitlement-store-pathno./entitlements.redbWho owns each paid package
publish-rulesnopublishing offThe rules for who may publish, yank and price packages

Selling paid packages needs no setting beyond the capability file; the registry takes purchase records from the datumhue.app store alone.

The capability file

namespace.capability carries three grants for your namespace, each issued for your registry's public key:

  • Announce — admits the registry to the platform and lets it register your namespace.
  • Write — lets the registry add to your catalog. Workstations accept a catalog entry only from a registry that holds this grant, so no one else can publish under your namespace. serve waits at start until it is there.
  • Read (the issuing grant) — lets the registry give buyers proof that they own a paid package. Without it, paid packages cannot be sold.

The running registry renews the grants itself, rewriting the file and logging renewed capability for namespace '@studio', so the file needs no backup. A registry that stays stopped until its grants lapse needs fresh files: the rotation page linked from your connection email sends them by email along with a key rotation, or the platform's operator can issue them again for your current key.

The grants belong to the key they were issued for. After a rotation, replace the old file with the newly delivered namespace.capability beside the new identity.key, keep your provider.kdl, which holds your publish rules, and start the registry again.

publish-rules block

The rules in publish-rules decide, from the publisher's sign-in, who may publish, yank and set prices. With no rules, or an empty block, publishing is off: the registry still serves your catalog, and --seed-from still loads packages at start. The registry reads the rules when it starts, so restart it after changing them.

kdl
provider {
    // ... the other keys ...
    publish-rules {
        issuer "https://accounts.google.com" {
            // You, by the `sub` that `login` prints.
            allow claim="sub" equals="110248495921238986420" rights="rw"
            // A collaborator, limited to one scope.
            allow scope="@studio" claim="email" \
                equals="artist@example.com" rights="rw"
        }
    }
}

The platform signs people in with Google, so the rules sit in the block of Google's issuer URL, https://accounts.google.com. login prints that URL beside your sub, the identifier Google keeps for your account. Bind rules to sub, or to email, the verified address the publisher signed in with. An address Google has not verified matches no rule.

Each allow line gates publishing into a scope. Packages carry a @<scope>/<name> identity; a rule binds to one scope, every scope, or the scopes a token claim lists.

kdl
issuer "<issuer URL>" {
    require claim="<claim-name>" <match-op>=<value>
    allow [scope="@<scope>"] claim="<claim-name>" \
          <match-op>=<value> rights="rw"
    allow [scope="@<scope>"] signed-in rights="rw"
    allow scope-in-claim="<claim>" rights="rw"
}

Every require of the block must hold before any of its rules counts. allow signed-in admits everyone the issuer signs in: on Google, that is every Google account, so bind it to a scope with care.

Scope binding — choose at most one form:

  • No scope key — the rule authorizes publishing under every @scope. Fine for yourself; for collaborators, bind the scope.
  • scope="@<scope>" — binds the rule to one scope. A publisher this rule authorizes may publish only under that @scope.
  • scope-in-claim="<claim>" — an identity owns the scopes its named token claim lists. The requested scope, without its @, must equal (string claim) or appear in (array claim) the named claim. This form takes no claim and no match operator.

Claim matching (for require and the claim= rules) — exactly one match operator:

  • equals="<value>" — the claim string equals the value, or the claim is an array containing the value as a string element
  • equals=#true or equals=#false — the claim is that boolean; a string "true" never matches it
  • array_contains="<value>" — the claim is an array containing the value

Rules add up: any rule that covers the requested scope and matches the claims allows the change. rights is always rw here: a publish, a yank and a price change all write, so the registry refuses to start on a rule that grants r, and quotes the rule. It also refuses allow anyone, since publishing always takes a sign-in, and a rule outside an issuer block.

A refused publisher learns the name of the claim or scope that failed, or iss when no block names its sign-in service, never the value the rule expects. The registry log gives the same reason.

Publish from the workstation

Publish, withdraw and price your packages from the Platform workstation, through its launcher, in the workstation's folder (on macOS the launcher is DatumHue.app/Contents/MacOS/datumhue-launcher). pkg push, pkg update, pkg outdated, pkg show, pkg yank and pkg price address the registry of the package's @scope, which on the platform is the namespace you subscribed. --namespace (or DATUMHUE_BOOTSTRAP_NAMESPACE) names another one, for a package published under a scope other than the namespace that serves it. A dependency in a package's requires resolves in its own @scope's namespace too, so another creator's package needs no namespace=.

shell
# Once; opens the browser (--no-browser prints the address instead).
./datumhue-launcher login
./datumhue-launcher pkg push <package-folder>
./datumhue-launcher pkg yank @studio/name 1.2.0 --reason "broken saves"
./datumhue-launcher pkg yank @studio/name 1.2.0 --undo
# --currency defaults to usd.
./datumhue-launcher pkg price @studio/name 4990
# Shows the listing; needs no sign-in.
./datumhue-launcher pkg price @studio/name

A yanked version leaves the store's listings and no longer matches a version range, but stays downloadable: a package that names that exact version, or already uses it, keeps it. --undo lists it again. A price belongs to the store: change it at any time without a new version. The amount is in the currency's smallest unit.

pkg push exits with code 2 when the platform or your registry rejects the sign-in, 3 when publishing is off or the sign-in fails its issuer's block (no block names its sign-in service, or a require does not hold), 10 when no allow rule of that block lets the person signed in publish into the package's @scope, 5 when the platform could not be reached or the sign-in could not be checked (try again shortly), and 11 when the version is already published with other content. pkg yank exits with the same codes for the same refusals, and pkg price exits 3 where pkg push exits 3 or 10.

Network surface

DirectionPurpose
inbound from workstationsYour catalog, and package downloads; a paid package only to its buyer's registered device
inbound from buyers' devicesProofs of purchase, and their renewals
inbound from publisherspkg push, pkg yank and pkg price
inbound from the platformEach sale and refund; buyers' devices as they are registered and removed
outbound to the platformAnnouncing the namespace, every half announce-ttl; capability renewal; finding the store and your namespace's other registries
outbound to the release feedThe launcher's update check, at every start

The registry opens no TCP port: every connection reaches it through the platform. A workstation and your registry connect directly over UDP where they can reach each other, and through the platform's relay where they cannot, so a registry on a home connection needs no port forwarding. It takes 4096 connections at once; past that a connection is turned away as busy.

Publishers' workstations present a pass the platform signs, valid for at most five minutes. Keep the computer's clock set automatically: the registry refuses a pass dated more than a minute ahead of its clock.

Persistent state

PathOwnerPurpose
secret-key-file (identity.key)youThe registry's identity. A new key needs a key rotation on the rotation page
capability-file (namespace.capability)the platformAdmits this registry and lets it write its catalog. Renews itself while the registry runs; issued again at a key rotation, and when it lapses while the registry is stopped
blob-store-paththis registryThe package files it serves. --seed-from rebuilds those still in its folder
catalog-store-paththis registryThe catalog: every published version and its yank history
entitlement-store-paththis registryWho owns each paid package, and their registered devices
the registry's folderthe launcherInstalled releases, and the previous ones as .bak

Keep every one on persistent storage:

  • Lost package files leave catalog entries that workstations fail to download. --seed-from brings back only the packages still in its folder, so a version published with pkg push whose package is no longer there cannot be downloaded again.
  • A lost key is replaced by a new one at the next start, which the platform has not admitted: it refuses the registry's announcements until you rotate to that key on the rotation page and place the new namespace.capability beside it.
  • Lost ownership records stop buyers downloading and renewing what they bought until the platform sends your registry the sales again: the last 30 days' within an hour, older ones when you ask its operator.

Run it as a service

Linux

/etc/systemd/system/datumhue-registry.service, with the zip unpacked to /opt/datumhue-registry and the delivered files beside the programs:

ini
[Unit]
Description=DatumHue creator registry
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=datumhue
Group=datumhue
WorkingDirectory=/opt/datumhue-registry
ExecStart=/opt/datumhue-registry/datumhue-launcher serve --config provider.kdl
Environment=DATUMHUE_KEYSTORE_PASSPHRASE=
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal

NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/datumhue-registry

[Install]
WantedBy=multi-user.target

ReadWritePaths must cover the registry's folder: the launcher installs updates there, and the registry rewrites namespace.capability and its stores there. If the key is encrypted, DATUMHUE_KEYSTORE_PASSPHRASE must hold its passphrase or the start fails: fill in the line above, or load the passphrase from a credentials file with LoadCredential= or EnvironmentFile=, so that the unit itself does not show it. Drop the line for a plaintext key. identity.key must belong to the service user, with mode 0600.

Docker

dockerfile
FROM debian:bookworm-slim
RUN apt-get update \
    && apt-get install -y --no-install-recommends ca-certificates \
    && rm -rf /var/lib/apt/lists/* \
    && useradd -r -s /usr/sbin/nologin registry \
    && mkdir -p /registry /data && chown registry:registry /registry /data

COPY --chown=registry:registry \
    datumhue-launcher datumhue-registry release-feed.txt /registry/
RUN chmod 755 /registry/datumhue-launcher /registry/datumhue-registry

USER registry
WORKDIR /data
ENTRYPOINT ["/registry/datumhue-launcher"]
CMD ["serve", "--config", "/data/provider.kdl"]

The container expects provider.kdl, identity.key, namespace.capability and the stores on a persistent /data volume. Updates the launcher installs stay inside the container: a new container starts from the release in the image and updates again at its first start. For an encrypted key, pass the passphrase in when the container starts (-e DATUMHUE_KEYSTORE_PASSPHRASE=… or a secret); without it the start fails. When the container's own addresses cannot be reached from outside, set webrtc-bind-addr and webrtc-public-addr and publish that UDP port, so browsers connect to it directly. Without them the registry still serves through the platform's relay.

Windows and macOS

Run the same command from a terminal opened in the registry's folder — on Windows:

shell
.\datumhue-launcher.exe serve --config provider.kdl

To keep the registry running across logins, wrap that command in the scheduled task or service mechanism of your choice, with the working directory set to the registry's folder. The key-file permission check applies on Linux and macOS only; on Windows, keep the registry's folder private to your account.

Security notes

  • Publish rules are the boundary. Anyone a rule allows can publish new versions under the scopes it covers, and yank versions and change prices there. Bind collaborators' rules to a named scope (scope="@studio") rather than to every scope. Published versions never change, so a stolen sign-in cannot alter one, but it can publish new versions and yank existing ones.
  • Your registry never sees a sign-in token. A publisher's pass works only on your registry, only from the workstation that asked for it, and for five minutes at most.
  • Secret-key encryption. The interactive keygen flow encrypts the key at rest. A key generated on first serve is plaintext unless DATUMHUE_KEYSTORE_PASSPHRASE is set; set it for any long-running deployment, or place the key on an encrypted volume.
  • Private key permissions. On Linux and macOS the registry refuses to start if secret-key-file is more permissive than owner-only (0600). keygen and first-run generation write the correct mode; if you copy the file, match it (chmod 600).
  • The key is the credential. identity.key is the one thing the platform trusts; the capability file works only with that key. The platform never asks for the key file. If it is lost, or you suspect someone else has it, replace it on the rotation page: the platform revokes the old key as it admits the new one, so the old key can no longer announce your namespace or renew its grants.
  • Your registry never sees a buyer's sign-in. It knows each buyer by a reference the platform makes for your namespace alone, and each of their devices by a key the device holds for your namespace alone.
  • Releases are verified. The launcher installs only releases signed by DatumHue, whose download matches what was signed, so whoever serves the feed named in release-feed.txt cannot hand it another program.

Troubleshooting

Waiting for the Write capability … never ends. The capability file holds no Write grant for the namespace provider.kdl names. Confirm capability-file points at the delivered namespace.capability, and that namespace in provider.kdl is exactly the namespace in that file.

The platform refuses the registry's announcements. Check that the startup line Package Provider Node ID: … shows the public key you onboarded. A different key means identity.key was missing at a start and the registry made a new one: restore the key from your backup, or rotate to the new key on the rotation page and place the emailed namespace.capability beside it, keeping your provider.kdl. A file that lapsed while the registry was stopped is issued again the same way.

The registry does not start on an encrypted key. The start fails with "the key at … is encrypted but DATUMHUE_KEYSTORE_PASSPHRASE is not set.": set the passphrase in that variable. The opposite message ("… is set but the key at … is stored plaintext.") means the variable is set for a plaintext key: unset it.

… has insecure permissions … The key file is readable by others. chmod 600 identity.key, and make sure the service user owns it.

The registry stops with "network version mismatch …". The platform runs another release. Run serve through datumhue-launcher with updates on: it installs the platform's release and starts the registry again. Started on its own, the registry stops with a message that names the launcher.

the aggregator does not advertise relay … relay-url was edited. Restore the value from the delivered provider.kdl; it is the same URL as aggregator-url.

Workstations cannot find packages. Check that the registry announced: the log shows Announcer started (TTL: <n>s) once at start, then Announced namespace '@studio' (TTL: <n>s) at every renewal. Check that the catalog holds packages: Catalog holds <n> package(s) at start gives the count. With none, run serve --seed-from <folder> once, or check that your publishes arrived.

Publishes denied (pkg push exits 3 or 10). The registry's access log, its lines on the log target datumhue_audit, has an outcome=deny line for the refused publish, with the scope as resource and the claim that failed as claim, or - when every rule is bound to another @scope. On exit 10, check the rule's equals value against the sub or address the publisher signed in with, and a scoped rule's scope against the package's @scope. On exit 3, check that publish-rules is not empty, that its block names https://accounts.google.com, and that the block's require lines hold. Restart the registry after changing the rules.

Buyers cannot buy or download a paid package. If the log at start says Paid-package entitlements disabled (no Read issuing capability), the capability file lacks its third grant: use the whole file the platform delivered. If a buyer who paid cannot download, your registry has not received the sale yet: the platform sends a sale of the last 30 days again within the hour, and its operator can send any sale at once.

The catalog is empty or smaller after a restart. The stores must be on persistent storage, and provider.kdl must still name the same files. A container without a volume loses them at every restart, and --seed-from brings back only the packages in its folder. Move the stores onto persistent storage.

The platform refuses the registry after a long stop. The installed release is older than the platform's, and the launcher could not update it. Check that the launcher reaches the feed (it prints why it skipped an update), that DATUMHUE_RELEASE_FEED_URL is not set to an empty value, and that the registry's folder is writable. If the launcher itself is too old to read the feed, download the current registry zip the way you got this one, and copy your identity.key, namespace.capability, provider.kdl and stores beside the new programs.

The launcher finds no registry to start. datumhue-registry is missing from the registry's folder, or DATUMHUE_CLIENT_PATH names a wrong path: restore the program from the zip, or unset the variable. When the launcher looks for the workstation instead (workstation not found at …), release-feed.txt, which names the program to start, is missing or empty: restore it from the zip.

datumhue.com

Victor Mono is licensed under the SIL Open Font License 1.1
Copyright (c) 2024, Rune Bjørnerås (https://github.com/rubjo)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
    ↑↓ selectEnter openTab filter

    Settings

    Theme
    Text
    Print

    Keyboard shortcuts

    / or Ctrl KSearch
    ↑ ↓ EnterPick a result
    TabNext result filter
    j kNext or previous entry
    [ ]Previous or next page
    yCopy a link to the current entry
    ?These shortcuts
    EscClose

    On this page