PDF Dies ist eine ältere Version — zur aktuellen Version 0.4.0

blockst0.3.0

Render Scratch-style blocks for educational documents.

Introduction#

About blockst#

blockst renders Scratch-style programming blocks directly in Typst documents. It is designed for worksheets, tutorials, teaching material, and visual programming explanations — anything where Scratch-like block syntax needs to appear in print or online documentation.

The current renderer uses a text-to-WASM pipeline: Typst passes Scratch text to a bundled WASM plugin, the plugin parses and renders SVG, and Typst embeds the SVG output in the document.

Core Design
  • Fully text-based: write Scratch blocks as plain text, get rendered blocks.
  • 26 languages supported via built-in WASM locale data, including right-to-left scripts.
  • Localized block rendering follows official Scratch translations.
  • Turtle graphics execution engine for demonstrating program flow visually.
  • SB3 import helpers for reading real Scratch project files.
Breaking Change since 0.2.0
The old pre-0.2.0 native-Typst renderer syntax is removed. From 0.2.0 onward, blockst uses only the text-to-WASM pipeline. Documents that still rely on the previous syntax must be migrated.

Quick start#

#import "@preview/blockst:0.3.0": blockst, scratch, raw-scratch, sb3

#scratch("
when green flag clicked
move (10) steps
turn cw (15) degrees
")

Package information#

Core Rendering API#

scratch() — Render Scratch Blocks#

The primary function for rendering Scratch blocks. It parses Scratch-like text and produces visual blocks.

#scratch("when green flag clicked\nmove (10) steps")

Signature#

#scratch(
  text,
  language: "en",
  theme: auto,
  scale: auto,
  line-numbers: auto,
  line-number-start: auto,
  line-number-gutter: auto,
  inset-scale: auto,
)

Parameters#

NameDefaultDescription
text (required)Scratch block text. Supports blocks, reporters, booleans, inputs, dropdowns, and nested control structures.
language"en"Locale for block text. See Languages for available options.
themeautoVisual theme: "normal", "high-contrast", "print", "grayscale", or auto (global default).
scaleautoOverall size multiplier (e.g. 80%, 0.8, 50%).
fontautoFont for block labels. Set it here rather than with #set text(font: ...): the renderer measures each label and writes the font into the SVG, so a document-level set text changes what is drawn but not what was measured, and the labels overlap.
line-numbersautoShow line numbers (true or false).
line-number-startautoStarting line number (integer, default 1).
line-number-gutterautoWidth of line number gutter (pt, default 24).
inset-scaleautoProportional block geometry scaling. 100% = default, 60% = thin blocks, 125% = thick blocks. Does not affect text size.

Supported block syntax#

The text parser supports the full Scratch 3 block vocabulary in all 26 locales. Key syntax patterns:

PatternExampleDescription
Stack blockmove (10) stepsStandard command block
Hat blockwhen green flag clickedEvent trigger
Reporter(x position)Value reporter (round ends)
Boolean<mouse down?>Predicate (pointy ends)
Input: number(10)Numeric input field
Input: string[hello]String input field
Input: dropdown[random position v]Dropdown selector
C-blockrepeat (4)\n...\nendBlock with body
C-block + elseif <...> then\n...\nelse\n...\nendConditional with else
Line labelsmove (10) steps #stepNamed label (see Labels)
Category prefix@motion free textForce category color (see category)
Category prefix (render)#text("@category") + textMatches real block when text fits

set-blockst() — Global Defaults#

Sets global rendering options for all subsequent scratch() and SB3 render calls.

#set-blockst(
  theme: none,
  scale: none,
  stroke-width: none,
  font: none,
  line-numbers: none,
  line-number-start: none,
  line-number-gutter: none,
  inset-scale: none,
)

All parameters are optional. Only provided values override the current defaults.

Themes#

ThemeDescription
"normal"Default — full color Scratch style.
"high-contrast"Lighter, high-contrast variant for accessibility.
"print"Every block white with a black outline. The lightest on ink, but all categories look alike.
"grayscale"One distinct grey per category, so a control block still reads differently from an operator on a monochrome page.
#set-blockst(theme: "print", scale: 70%)
#scratch("when green flag clicked\nmove (10) steps")

Fonts#

#set-blockst(font: "Nimbus Sans")

The default font is Helvetica Neue. On systems without it, set an alternative.

Line numbers#

#set-blockst(line-numbers: true, line-number-start: 1, line-number-gutter: 24)

Inset scale#

Controls the visual thickness/slimness of blocks without changing text size.

#set-blockst(inset-scale: 60%)   // compact blocks
#set-blockst(inset-scale: 125%)  // generous blocks

blockst() — Group Override Container#

Optional wrapper for grouped blocks that differ from global settings.

#blockst(
  theme: auto,
  scale: auto,
  line-numbers: auto,
  line-number-start: auto,
  line-number-gutter: auto,
  inset-scale: auto,
  spacing: 1.5em,
  body,
)

All parameters match scratch() but apply only within the body. Use when a specific group of blocks needs different scaling or theme.

#blockst(theme: "high-contrast")[
  #scratch("when green flag clicked\nmove (10) steps")
]

Languages#

blockst supports 26 languages via built-in WASM locale data. Set via scratch(..., language: "de") or set-blockst(...).

CodeLanguageCodeLanguage
"en"English"de"German
"fr"French"es"Spanish
"it"Italian"nl"Dutch
"pt"Portuguese"pl"Polish
"ru"Russian"ja"Japanese
"ca"Catalan"cs"Czech
"cy"Welsh"el"Greek
"fa"Persian"gd"Scottish Gaelic
"he"Hebrew"hi"Hindi
"hr"Croatian"hu"Hungarian
"id"Indonesian"nb"Norwegian (Bokmål)
"ro"Romanian"sl"Slovenian
"tr"Turkish"ar"Arabic
#set-blockst(scale: 67.5%)
#scratch("
Wenn die grüne Flagge angeklickt
wiederhole (4) mal
  gehe (30) er Schritt
  drehe dich nach rechts um (90) Grad
end
", language: "de")

Block text must match the chosen locale’s vocabulary — the parser matches against official Scratch translations.

Right-to-left languages#

Arabic ("ar"), Hebrew ("he") and Persian ("fa") render right-to-left. Nothing has to be switched on: each locale declares its own direction, and the renderer mirrors the layout — the notch, the hat dome, the C-block mouth, the loop arrow, the pen badge, the define hat and the order of the labels all move to the reading edge.

#scratch("
عند نقر @greenFlag
تحرك (10) خطوة
كرِّر (4) مرة
استدر @turnRight (90) درجة
نهاية
", language: "ar")
What is mirrored, and what is not
Layout is mirrored; meaning is not. The loop arrow points back to the top of the loop, which is a claim about the drawing, so it flips with the drawing. @turnRight and @turnLeft are never mirrored — their direction is what the sprite is being told to do, and a mirrored @turnRight would tell the reader to turn the other way.

Arabic short vowels are optional and their order is not canonical, so blocks match whether or not you type the harakat: كرِّر and كرر both find the repeat block.

Labels and Line Numbers#

blockst provides a label system for creating line-aware worksheets. Lines ending with #label-name are tagged and can be referenced later.

scratch-labels() — Extract Labels#

Parses Scratch text and returns a dictionary mapping label names to line numbers.

#let labels = scratch-labels("
repeat (4) #loop
  move (20) steps #step
  turn cw (90) degrees
end
")
// labels = ("loop": 1, "step": 2)

blockst-labels() — Query Labels#

Queries globally collected labels from all rendered scratch() calls.

#blockst-labels("loop")   // → line number or "NaN"
#blockst-labels()         // → full label → line dictionary

blockst-register-labels() — Pre-register Labels#

Register labels globally without rendering blocks. Useful when labels are needed before the first block output.

#blockst-register-labels("
repeat (4) #loop
  move (20) steps #step
end
")

@category — Quick Color Defaults#

Use @category prefix to force a block’s category color, even without matching full localized syntax.

@motion         // → default motion block
@motion free text  // → unrecognized block in motion color

Available categories:

TokenNormalizedDefault Block
@motionmotionmove (10) steps
@lookslookssay [Hello!]
@soundsoundplay sound [pop v]
@eventseventswhen green flag clicked
@controlcontrolrepeat (10)
@sensingsensingask [What's your name?] and wait
@operatorsoperators(() + ())
@variablevariablesset [var v] to (0)
@listlistsadd (thing) to [list v]
@penpenclear
@eventevents(alias for events)
@operatoroperators(alias for operators)

When @category is followed by text that matches a known block in that category, the actual block is rendered:

@list add (12) to [my list v]    // → DATA_ADDTOLIST
@variable change [score v] by (1) // → DATA_CHANGEVARIABLEBY

If the text does not match any known block, the fallback is an unrecognized block in the forced category color.

Parsing API#

scratch-parse() — Parse to AST#

Parses Scratch text to an abstract syntax tree for programmatic use.

#scratch-parse(
  text,              // Scratch block text
  language: "en",    // locale for parsing
)

Returns a nested structure representing blocks, inputs, and bodies.

Markdown Code Blocks with raw-scratch#

The raw-scratch() show rule converts scratch code fences into rendered blocks automatically.

#show: raw-scratch(language: "en")   // locale for block text

Then use scratch code fences in your document:

when green flag clicked
repeat (4)
  move (30) steps
  turn cw (90) degrees
end

Optionally pass language: raw-scratch(language: „de“) in the show rule.

Theme and Styling Examples#

#let script = "when green flag clicked
go to (random position v)
turn cw (30) degrees"

#blockst(inset-scale: 50%)[#scratch(script)]

#v(5mm)

#blockst(theme: "high-contrast")[#scratch(script)]

#v(5mm)

#blockst(theme: "print")[#scratch(script)]

Turtle Graphics / Executable Scratch#

blockst includes an execution engine that runs Scratch programs visually — ideal for demonstrating program flow and pen drawing.

scratch-run Module#

Import via the scratch-run module:

#import "@preview/blockst:0.3.0": scratch-run, set-scratch-run

#scratch-run.stage("...", scale: 2)
#scratch-run.grid("...", grid: true)

scratch-run.stage() — Stage Canvas#

Renders pen drawing on a stage canvas, like the Scratch stage.

#scratch-run.stage(
  program,
  language: "en",      // locale for block text
  size: auto,          // (width, height) in pixels (default 480×360)
  scale: auto,         // drawing scale multiplier
  start: auto,         // (x: 0, y: 0, angle: 90)
  pen: auto,           // (down: false, color: ..., size: ...)
  background: auto,    // background color (e.g. white, black)
  cursor: auto,        // show/hide turtle cursor (default: true)
  border: auto,        // show/hide stage border (default: true)
)

scratch-run.grid() — Coordinate Grid#

Renders the drawing on a Cartesian coordinate grid with optional axes and grid lines.

#scratch-run.grid(
  program,
  language: "en",      // locale for block text
  x: auto,             // view bounds (tuple, e.g. (-10, 10))
  y: auto,             // view bounds (tuple)
  step: auto,          // grid step size
  scale: auto,         // drawing scale multiplier
  start: auto,         // (x: 0, y: 0, angle: 90)
  pen: auto,           // (down: false, color: ..., size: ...)
  background: auto,    // background color
  axes: auto,          // show axis lines (default: false)
  grid: auto,          // show grid lines (default: false)
  grid-style: auto,    // grid line stroke (default: 0.5pt + gray)
  cursor: auto,        // show/hide turtle cursor (default: true)
  fit: auto,           // auto-fit view to drawing bounds
)

set-scratch-run() — Run Global Defaults#

#set-scratch-run(
  scale: none,         // default scale for all runs
  start: none,         // (x: 0, y: 0, angle: 90)
  pen: none,           // (down: false, color: ..., size: ...)
  background: none,    // default background color
  cursor: none,        // show/hide turtle cursor (default: true)
  stage: none,         // (size: (480, 360), border: true)
  grid: none,          // (visible: false, axes: false, step: auto, style: auto)
)

Supported Execution Commands#

CategoryCommands
Motionmove, turn-right, turn-left, set-direction, go-to, set-x, set-y, change-x, change-y
Penpen-down, pen-up, set-pen-color, set-pen-size, change-pen-size, erase-all, stamp, set-pen-param, change-pen-param
Variablesset-variable, change-variable, variable
Operatorsplus, minus, multiply, divide, modulo, random, round, greater, less, equals, op-and, op-or, op-not
Controlrepeat, repeat-until, if-then, if-else, wait
Lookssay, think
Shapessquare, triangle, circle, star, spiral
Germangehe, drehe-rechts, drehe-links, setze-richtung, gehe-zu, stift-ein, stift-aus, setze-stiftfarbe-auf, setze-stiftdicke, setze-variable, aendere-variable, groesser, kleiner, gleich, und, oder, nicht, mal, geteilt, zufallszahl

Complete Example#

#let square-program = "
go to x: (-45) y: (45)
pen down
set pen [color v] to (0)
set pen size to (45)
repeat (4)
  move (90) steps
  turn cw (90) degrees
  change pen [color v] by (25)
end"

#set-scratch-run(
  stage: (size: (300, 240)),
  start: (x: 0, y: 0, angle: 90),
)

#grid(
  columns: (auto, auto),
  gutter: 6mm,
  [#scratch(square-program)],
  [#scratch-run.stage(square-program, scale: 2)],
)

SB3 Import API#

blockst can read real Scratch 3 project files (.sb3) and extract scripts, variables, lists, images, and screen previews.

Basic Workflow#

#let project = read("my-project.sb3", encoding: none)

#sb3.render-sb3-scripts(project, language: "en", target: "Sprite1")

The .sb3 file must be read as raw bytes (encoding: none).

render-sb3-scripts()#

#sb3.render-sb3-scripts(
  sb3-bytes,                   // raw .sb3 file bytes (read with encoding: none)
  script-number: auto,         // global script index (1-based)
  target-script-number: auto,  // script index within selected target
  target: auto,                // filter by target name ("Stage", sprite name, or auto for all)
  sb3-plugin: auto,            // WASM plugin path (auto = bundled plugin)
  language: "en",              // locale for block text
  show-headers: auto,          // show target name header
  header-gap: 1.5mm,           // spacing between target name and scripts
  script-gap: 3mm,             // spacing between individual scripts
)

render-sb3-variables()#

#sb3.render-sb3-variables(
  sb3-bytes,                   // raw .sb3 file bytes
  target: auto,                // filter by target name
  target-variable-name: auto,  // filter by variable name
  target-variable-number: auto,// variable index within target (1-based)
  sb3-plugin: auto,            // WASM plugin path
  language: "en",              // locale for display text
  show-target-headers: auto,   // show target name header
  target-gap: 2mm,             // spacing between targets
  item-gap: 0.8mm,             // spacing between variable items
)

render-sb3-lists()#

#sb3.render-sb3-lists(
  sb3-bytes,                   // raw .sb3 file bytes
  target: auto,                // filter by target name
  target-list-name: auto,      // filter by list name
  target-list-number: auto,    // list index within target (1-based)
  sb3-plugin: auto,            // WASM plugin path
  language: "en",              // locale for display text
  show-target-headers: auto,   // show target name header
  target-gap: 2mm,             // spacing between targets
  item-gap: 0.8mm,             // spacing between list items
)

Standalone Monitors#

list-monitor()#

#list-monitor(
  name: "List",      // display name shown in header
  items: (),         // array of values to display
  width: 5.2cm,      // monitor width
  height: auto,      // auto-grows to fit content
  length-label: auto,// show length indicator (e.g. "length: 3")
)

variable-monitor()#

#variable-monitor(
  name: "Variable",  // display name shown in header
  value: 0,          // current value to display
)

screen-preview()#

#sb3.sb3-screen-preview(
  sb3-bytes,           // raw .sb3 file bytes
  width: 480,          // stage rendering width in pixels
  height: 360,         // stage rendering height in pixels
  unit: 1,             // size multiplier (2 = double size)
  background: none,    // override background color
  show-border: true,   // show stage border
  show-backdrop: true, // show costume/backdrop image
  monitor-scale: 1.5,  // scale factor for variable/list overlays
  language: auto,      // locale for monitor text
)

Renders a static Scratch stage preview with sprites, backdrop, and monitors.

Image Helpers#

// List all image assets in the project
#sb3.sb3-image-assets-catalog(sb3-bytes, target: auto)

// Render a single image asset
#sb3.sb3-image(
  sb3-bytes,          // raw .sb3 file bytes
  target: auto,       // filter by sprite/stage name
  image-number: auto, // global image index (1-based)
  target-image-number: auto, // image index within target (1-based)
  image-name: auto,   // filter by image name (e.g. "costume1")
  width: auto,        // output width (auto = original size)
  height: auto,       // output height
)

Catalog Helpers#

// Grouped script metadata (targets, scripts, blocks count)
#sb3.sb3-scripts-catalog(sb3-bytes)

// Target states (variables, lists, and sprite properties)
#sb3.sb3-state-catalog(sb3-bytes)

// Convert a specific SB3 script to Scratch text
#sb3.sb3-bytes-to-scratch-text(
  sb3-bytes,          // raw .sb3 file bytes
  script-number: auto,// global script index (1-based)
  language: "en",     // locale for output text
)

Catalog#

The catalog shows every block in each Scratch 3 category, rendered live.

Motion#

BlockCode
move (10) steps
turn cw (15) degrees
turn ccw (15) degrees
point in direction (90)
point towards [mouse-pointer v]
go to x: (0) y: (0)
go to [mouse-pointer v]
glide (1) secs to x: (0) y: (0)
glide (1) secs to [mouse-pointer v]
change x by (10)
set x to (0)
change y by (10)
set y to (0)
if on edge, bounce
set rotation style [left-right v]
(x position)
(y position)
(direction)

Looks#

BlockCode
say [Hello!] for (2) seconds
say [Hello!]
think [Hmm...] for (2) seconds
think [Hmm...]
show
hide
switch costume to [costume1 v]
next costume
switch backdrop to [backdrop1 v]
next backdrop
change [color v] effect by (25)
set [color v] effect to (0)
clear graphic effects
change size by (10)
set size to (100) %
go to [front v] layer
go [forward v] (1) layers
(costume [number v])
(backdrop [number v])
(size)

Sound#

BlockCode
play sound [pop v] until done
start sound [pop v]
stop all sounds
change [pitch v] effect by (10)
set [pitch v] effect to (100)
clear sound effects
change volume by (-10)
set volume to (100) %
(volume)

Pen#

BlockCode
erase all
stamp
pen down
pen up
set pen color to [#ff0000]
change pen color by (10)
set pen color to (50)
change pen shade by (10)
set pen shade to (50)
change pen size by (1)
set pen size to (1)

Variables#

BlockCode
set [my variable v] to (0)
change [my variable v] by (1)
show variable [my variable v]
hide variable [my variable v]
(my variable)

Lists#

BlockCode
add [thing] to [my list v]
delete (1) of [my list v]
delete all of [my list v]
insert [thing] at (1) of [my list v]
replace item (1) of [my list v] with [thing]
(item (1) of [my list v])
(item # of [thing] in [my list v])
(length of [my list v])
<[my list v] contains [thing]?>
show list [my list v]
hide list [my list v]

Events#

BlockCode
when green flag clicked
when [space v] key pressed
when this sprite clicked
when backdrop switches to [backdrop1 v]
when [loudness v] > (10)
when I receive [message1 v]
broadcast [message1 v]
broadcast [message1 v] and wait

Control#

BlockCode
wait (1) seconds
repeat (10)
end
forever
end
if <> then
end
if <> then
else
end
wait until <>
repeat until <>
end
stop [all v]
when I start as a clone
create clone of [myself v]
delete this clone

Sensing#

BlockCode
<touching [mouse-pointer v]?>
<touching color [#ff0000]?>
<color [#ff0000] is touching [#00ff00]?>
(distance to [mouse-pointer v])
ask [What's your name?] and wait
(answer)
<key [space v] pressed?>
<mouse down?>
(mouse x)
(mouse y)
(loudness)
(timer)
reset timer
([x position v] of [Sprite1 v])
(current [year v])
(days since 2000)
(username)

Operators#

BlockCode
(() + ())
(() - ())
(() * ())
(() / ())
(pick random (1) to (10))
<() > ()>
<() < ()>
<() = ()>
<> and <>
<> or <>
not <>
(join [apple] [banana])
(letter (1) of [apple])
(length of [apple])
([apple] contains [a]?)
(() mod ())
(round ())
([sqrt v] of (9))

Contributing#

Contributions are welcome: bug reports, missing blocks, parser improvements, rendering polish, docs, and new localizations.

API Reference#

The reference is generated from the /// comments in the source. It covers the public rendering API in libs/scratch/api.typ, which lib.typ and package.typ re-export unchanged.

blockst#

blockst(
  theme: auto,
  scale: auto,
  font: auto,
  line-numbering: auto,
  line-numbers: auto,
  line-number-start: auto,
  line-number-first-block: auto,
  line-number-gutter: auto,
  inset-scale: auto,
  language: auto,
  spacing: 1.5em,
  body,
)
Optional container for grouped blocks with theme/scale override. Only needed when a specific group should differ from global settings.
NameVorgabe
themeauto
scaleauto
fontauto
line-numberingauto
line-numbersauto
line-number-startauto
line-number-first-blockauto
line-number-gutterauto
inset-scaleauto
languageauto
spacing1.5em
body

blockst-labels#

blockst-labels(name)
Read globally collected line labels from all previously rendered scratch() blocks. With a name: #blockst-labels("start", default: "?"). Without a name: returns the full label-to-line dictionary.

blockst-register-labels#

blockst-register-labels(text, language: "en")
Register labels globally without rendering blocks. Useful when labels are needed before the first #scratch() output appears.
NameVorgabe
text
language"en"

raw-scratch#

raw-scratch(..args)

Enable scratch code blocks in raw text:

#show: raw-scratch()

With language: #show: raw-scratch(language: "de").

scratch#

scratch(
  text,
  language: auto,
  theme: auto,
  scale: auto,
  font: auto,
  line-numbering: auto,
  line-numbers: auto,
  line-number-start: auto,
  line-number-first-block: auto,
  line-number-gutter: auto,
  inset-scale: auto,
)
Render scratch blocks from text. Works standalone or inside #blockst[...]. Supports 26 languages: en, de, fr, es, it, pt, nl, pl, ru, ja, …
NameVorgabe
text
languageauto
themeauto
scaleauto
fontauto
line-numberingauto
line-numbersauto
line-number-startauto
line-number-first-blockauto
line-number-gutterauto
inset-scaleauto

scratch-execute#

scratch-execute(text, language: "en")
Execute scratch text, producing scratch-run commands.
NameVorgabe
text
language"en"

scratch-labels#

scratch-labels(text, language: "en")
Parse scratch text and return a dictionary mapping #labels to rendered line numbers.
NameVorgabe
text
language"en"

scratch-parse#

scratch-parse(text, language: "en")
Parse scratch text to AST (for programmatic use).
NameVorgabe
text
language"en"

set-blockst#

set-blockst(
  theme: none,
  scale: none,
  stroke-width: none,
  font: none,
  line-numbering: none,
  line-numbers: none,
  line-number-start: none,
  line-number-first-block: none,
  line-number-gutter: none,
  inset-scale: none,
  language: none,
)
Global settings applied to all scratch() and sb3 calls.
NameVorgabe
themenone
scalenone
stroke-widthnone
fontnone
line-numberingnone
line-numbersnone
line-number-startnone
line-number-first-blocknone
line-number-gutternone
inset-scalenone
languagenone