Summary#
The platform ships mechanism, never components. Osysharp.Barcode is a complete barcode and QR scanner built on
that mechanism, distributed as an ordinary kit — you depend on it in one line and nothing is copied into your
project:
app Warehouse {
use Osysharp.Barcode@1;
model "model/**/*.osy";
}It opens the camera, watches the picture, and raises scanned(text, format) when it reads a code. Everything about
reading a symbol — the camera, the frame loop, the decoder — is inside the control.
⚠️ This is not the same thing as camera and microphone. Camera.Start() / Camera.Capture() are for taking a
photograph or a recording and keeping the file. This is for reading what a picture says and keeping the text. An
app can use both; they do not interfere.
Signature#
BarcodeScanner(scanned: Found) // everything, back camera, four looks a second
BarcodeScanner(scanned: Found, formats: qr) // QR only — materially faster on a phone
BarcodeScanner(scanned: Found, active: armed) // false releases the camerascanned is an event, so it binds to an action taking (string text, string format). The control has no default
size: put it in a box that has one.
Description#
The whole thing, at a call site#
// ⚠ This example DECLARES the control inline, because a documentation example compiles on its own with no manifest
// to carry a `use`. In your app you write `use Osysharp.Barcode@1;` instead and delete this block — the declaration
// arrives with the kit. What follows is `scan.osy`, trimmed to what this page uses.
control BarcodeScanner {
contractVersion "1.1"
participation headless
props {
bool active = true;
[Values(any, qr, linear)] string formats = "any";
[Values(back, front)] string facing = "back";
int intervalMs = 250;
int repeatAfterMs = 2000;
}
events {
scanned(string text, string format);
failed(string reason);
}
chunks { Core; Wasm; }
probe { bool scanning; int scans; string? last; string? failure; }
}
[Page("/scan")] [AllowAnonymous]
component ScanPage() {
string code = "";
string problem = "";
bool armed = true;
action Found(string text, string format) { code = text; armed = false; }
action Broke(string reason) { problem = reason; }
action Again() { code = ""; problem = ""; armed = true; }
render {
Stack(gap: 3, p: 4) {
Stack(h: "16rem") {
BarcodeScanner(scanned: Found, failed: Broke, active: armed, formats: qr);
}
if (problem != "") { Text(problem); }
if (code != "") {
Text(code);
Button("Scan another", onPress: Again);
}
}
}
}Note active: armed. A scanner that cannot be switched off is a camera that is always on — turning it off after a
read is both the polite thing and the thing that puts the browser's recording indicator out.
repeatAfterMs is the prop that makes it usable#
A label held in front of the camera is read on every pass — four times a second at the default interval. So the
same text is reported once and then suppressed for repeatAfterMs (2 seconds by default). Without that, one label
held for ten seconds produces forty events, and an app that appends a row per scan collects forty rows.
Set it to 0 if you genuinely want every read — a counter, or a diagnostic.
Narrowing formats is a real speed-up#
| value | what it looks for |
|---|---|
any | every format the decoder knows |
qr | QR and Micro QR |
linear | the retail and logistics 1-D family — EAN, UPC, Code 39/93/128, ITF, Codabar, DataBar |
The decoder spends time proportional to how many families it must try, so qr on a phone is noticeably faster than
any. format on the scanned event names the exact symbology it found (QRCode, EAN-13, …), not the family
you asked for.
When it cannot run, it says so#
failed(reason) carries words meant for a person: the camera was refused, there is no camera, another app holds it,
the decoder would not load. It is raised once per cause, not once per frame — so an app can show it as a message
without four of them arriving a second.
Like camera and microphone, this needs a secure page: browsers give no camera to plain http:// beyond localhost.
What it costs, and when#
| Size | When it loads | |
|---|---|---|
| the control | 3 KB | on mount |
| the decoder glue | 37 KB | the first time a scanner is activated |
| the decoder itself | 1.07 MB | the same moment, as WebAssembly |
A page that mounts a scanner with active: false and never arms it downloads neither chunk. An app that never
writes the use carries nothing at all.
It is also the proof that a control may ship WebAssembly#
The decoder is ZXing's C++ library compiled to WebAssembly, and it reaches the browser through nothing special: a
.wasm is an ordinary control asset, pinned by content address, served same-origin, and reached through the ordinary
chunks — assets a control loads on demand mechanism. If you are shipping a control of your own around a native library, this kit is the
worked example — see its README.md for the one function everything turns on.
See also#
- camera and microphone — the camera and microphone as verbs, for taking a photograph or a recording
- chunks — assets a control loads on demand — how an asset a control loads on demand is declared and served
- control — foreign UI controls (charts, grids, maps) — declaring a foreign control of your own
- The markdown editor kit — a rich editor you opt into — the other bundled control kit, and the first one
- upload — a file a person chooses, which is the other way bytes arrive from a browser