ApasScript.

Content Blocks (.apc)

An .apc file is a lesson, worksheet, question set, notebook or slide deck written as plain text. It is an ordered list of blocks. The same file is shown on the web and printed as a PDF.

Each example below is live: change the source and the result under it follows.

Giving this to an AI model? Use the plain-text version: the same reference in one file, with the plot and diagram functions listed.

Rules every file follows

  1. A file is a flat list of blocks from top to bottom. There is no <page>, <section>, <html> or <body> wrapper; those are errors.
  2. Every line at the top level starts a block or belongs to one that is open. Prose goes in <p>...</p>. Markdown headings (#), bullets (-), numbered lines and code fences are not understood.
  3. Put each block's tags on their own lines, as in the examples. An opening tag and its attributes stay on one line.
  4. Only the tags on this page exist. An unknown tag is an error, not ignored.
  5. Inline formatting is **bold**, *italic*, $maths$ and code between backticks, each on one line.
  6. Numbers are exact: 1/3 stays a fraction, sqrt(2) a surd. Ask for a decimal with decimal(x).
  7. A picture is written, not attached: a plot or diagram is a <script> block that ends with the graph or diagram.
  8. Each <script> and <code> block runs on its own. <calc> and <params> names are shared by the whole page. In a notebook (<cell>) everything is shared.

Text and structure

The blocks every lesson is made of. Headings give the document its outline; there is no page or section wrapper.

<h1> to <h6>

A heading, level 1 to 6.

One line, with the closing tag on the same line. Inline maths and formatting work inside it. The first <h1> is the document title in a PDF.

Example

<h1>Newton's second law</h1>
<h2>Force and acceleration</h2>
<h3>The case $F = m*a$</h3>

<p>

A paragraph of prose with inline maths and formatting.

All prose goes inside <p>...</p>; a bare line of text is an error. A paragraph may run over several lines and ends at </p>.

Inline formatting: **bold**, *italic*, $maths$, and code between backticks. Each is a flat span on one line; they do not nest. Put a backslash before $, * or a backtick to get the character itself. An unclosed $ or * is shown as plain text, never an error.

Write < and > as they are. Text is escaped when it is drawn, so HTML tags inside a paragraph are shown as text, not run.

Example

<p>The net force is $F = m*a$, where $m$ is the **mass** and $a$ the *acceleration*.</p>
<p>Call `sqrt(2)` for a square root. A ticket costs \$5, and 3 < 5.</p>

<ul> and <ol>

A bullet list or a numbered list.

Each item is one <li>...</li> on its own line, with the same inline maths and formatting as a paragraph. Items cannot hold other blocks and lists do not nest; for richer items use a heading or a callout.

Example

<ul>
<li>The discriminant is $D = b^2 - 4*a*c$.</li>
<li>$D > 0$ gives two distinct real roots.</li>
</ul>
<ol>
<li>Compute $D$.</li>
<li>Apply the quadratic formula.</li>
</ol>

<table>

A table written as rows of cells separated by |.

The first row is the header. Every row must have the same number of cells; the leading and trailing | are optional. A cell takes inline maths and formatting, including values computed in a <calc> block. There is no separator row of dashes.

Example

<table>
| Quantity | Symbol | SI unit |
| Displacement | $s$ | m |
| Velocity | $v$ | m/s |
| Acceleration | $a$ | $\mathrm{m/s^2}$ |
</table>

<callout>

A labelled box: definition, theorem, example, note, exercise, answer, or any other label.

One generic box. type is free text and becomes the label ("Definition.", "Worked example."), so a new kind of box needs no new tag. The body is any blocks: paragraphs, maths, tables, scripts, even another callout.

An exercise's answer is simply the next callout with type="answer". Inside a <slide>, a callout with type="speaker notes" is shown only to the presenter.

Attributes

  • type -- Free-text label, title-cased when shown. Optional.
  • cite -- Free-text source shown in brackets after the label, such as "JEE Main 2025". Optional.

Example

<callout type="definition">
<p>**Momentum** is the product of mass and velocity:</p>
$$
p = m*v
$$
</callout>
<callout type="exercise" cite="JEE Main 2025">
<p>Solve $x^2 - 7x + 12 = 0$ by factorisation.</p>
</callout>
<callout type="answer">
<p>$(x-3)(x-4) = 0$, so $x = 3$ or $x = 4$.</p>
</callout>

Mathematics

Maths is written either as a plain expression, the way a calculator or ApasScript takes it ((a+b)/(c+d), sqrt(x), x^2), or as LaTeX (\frac{a}{b}, \vec{F}, \int_0^1). Both work between dollar signs.

$...$

Maths inside a line of text.

Allowed wherever text is: headings, paragraphs, list items, table cells, options, answers and hints. It stays on one line.

A plain expression is typeset for you: (a+b)/2 becomes a fraction, sqrt(x) a root, x^2 a power, * a product. So is an equation whose sides are plain expressions: D = b^2 - 4*a*c, x^2 - 7*x + 12 = 0.

Anything else is passed to the page as LaTeX, exactly as written: vectors, limits, integrals, Greek letters. If either side of an equation is LaTeX, the whole span is. Write a unit as \mathrm{m/s^2}, or it is read as m divided by s squared.

When a <calc> block has given a name a value, $name$ shows the value (see <calc>).

Example

<p>Plain: $(a+b)/(c+d)$, $sqrt(x^2 + 1)$, $3*x^2 - 2*x + 1$, $x^2 - 7*x + 12 = 0$.</p>
<p>LaTeX: $\vec{F} = m\vec{a}$, $\int_0^1 x^2\,dx = \frac{1}{3}$, $v = 12\,\mathrm{m/s}$.</p>

$$ ... $$

Display maths: one equation per line, aligned on the equals sign.

A line holding only $$ opens the block and another closes it. With several lines, each is split on its first = and the equals signs line up.

$$align instead takes the alignment point from an & you place yourself, as LaTeX's align does. Use it when a line has no =, or when the alignment is not at the first one.

Aligned on =

$$
2*x + 3 = 7
2*x = 4
x = 2
$$

Explicit alignment with &

$$align
(x+1)^2 &= x^2 + 2*x + 1
&= x*(x + 2) + 1
$$

<calc>

Silent calculation: computes values and shows nothing; later text can quote them.

The body is ApasScript, one name = expression per line. Nothing is displayed. After it, $name$ anywhere on the page shows the computed value, and an expression such as $2*D$ is worked out from it. All <calc> blocks on a page share one set of names.

Values are exact: 1/3 stays a fraction and sqrt(8) becomes 2sqrt(2). Use $decimal(x)$ to show a decimal.

A span containing = is always shown as a formula and never assigns. Keep calculated names different from letters you use purely as symbols on the same page, or the symbol will be replaced by the value.

Example

<calc>
mass = 12
acc = 3/2
force = mass*acc
</calc>
<p>A body of mass $mass$ kg accelerating at $acc$ $\mathrm{m/s^2}$ needs a force of $force$ N, which is $decimal(force/1000)$ kN.</p>
<table>
| Quantity | Value |
| Mass | $mass$ kg |
| Force | $force$ N |
</table>

<formula>

One formula of a chapter: checked by the engine, shown with its symbols.

The first line is the formula, written as solve takes it (V = I*R, 1/f = 1/v - 1/u). Each line after it describes one symbol as name: meaning | unit; leave the unit out for a pure number. An optional applies: line says when the formula holds. id, topic and name on the tag identify it.

The block is checked where it is compiled: every name in the formula must be described, and with the units given, both sides must come to the same unit. A formula that fails says what is wrong in its place.

Follow it with a worked example for the reader: a <callout type="example"> holding the problem in words, its given values as quantities in <calc>, and the working in <steps>. A student then reads the formula, the values put in with their units and the answer, and never a script.

Example

<formula id="ohms-law" topic="ohms_law" name="Ohm's law">
V = I*R
V: potential difference | V
I: current | A
R: resistance | ohm
applies: a conductor at constant temperature
</formula>
<callout type="example">
<p>A potential difference of 12 V is applied across a resistance of 4 ohm. Find the current.</p>
<calc>
V = quantity(12, "V")
R = quantity(4, "ohm")
</calc>
<steps>
I = solve(V = I*R, I)
</steps>
</callout>

Scripts and code

Three ways to put ApasScript on a page. <script> shows working and pictures; <code> shows a listing and its output; <calc> (above) computes silently.

<script>

Runs ApasScript and shows each line's result: worked calculations, plots and diagrams.

The body is ApasScript source, exactly as in the playground. Each line shows name = value typeset as maths; a line starting with # is prose. A plot or diagram value is drawn as a picture.

Wrap setup lines in quiet { ... } so only the last line is shown; this is the normal shape for a picture (see Plots and Diagrams below).

Each <script> runs on its own: names from one script are not visible in the next, nor in <calc>. In a notebook (a document made of <cell> blocks) all scripts share their names.

Worked calculation

<script>
# Roots of the quadratic
a = 1
b = -7
c = 12
D = b^2 - 4*a*c
x1 = (-b + sqrt(D))/(2*a)
x2 = (-b - sqrt(D))/(2*a)
</script>

In a PDF: Calculation lines become aligned equations; plots and diagrams are drawn as vector figures (the final state only). Flowcharts, 3D surfaces and animations have no PDF form.

<code>

A code listing. ApasScript listings are also run and their output is shown.

Use it to teach the code itself. With no lang, or lang="as", the listing is run and its real output is printed under it, so the output is never typed by hand. Any other lang (Python, C, ...) is shown as a listing only.

Attributes

  • lang -- "as" (the default) runs the listing. Any other language name shows it without running.

ApasScript: listing and output

<code>
solve(x^2 + 6*x + 5 = 0, x)
</code>

Another language, with typed output

<code lang="python">
for i in range(3):
    print(i * i)
</code>
<output>
0
1
4
</output>

<output>

Typed output shown verbatim, to pair with a listing in another language.

Never run or checked: what is written is what shows. An ApasScript <code> block makes its own output and does not need this.

Example

<output>
Enter a number: 7
7 is prime
</output>

<pseudocode>

Textbook pseudocode, shown as a numbered listing and a flowchart.

Never run. Only each line's leading keyword and its indentation matter: if / else if / else, while, for, repeat ... until, do ... while, return, break, continue, and an optional algorithm or procedure header on the first line. The rest of the line is free text.

A block that does not fit together (an else with no if, an until with no repeat) is an error, not a wrong chart.

Attributes

  • title -- Shown above the listing. Optional.

Example

<pseudocode title="Largest of three numbers">
algorithm Largest(a, b, c)
  max ← a
  if b > max
    max ← b
  if c > max
    max ← c
  return max
</pseudocode>

In a PDF: The numbered listing only; the flowchart is a web feature.

Plots

A plot is a <script> block whose last line is a graph. Build the graph inside quiet { ... }, then name it on the last line so only the picture shows.

graph() fits its axes to the data; graph(xmin, xmax, ymin, ymax) fixes them. Colours are CSS names or hex. Line styles are "solid", "dashed" and "dotted". The full list of functions is on the Plotting page.

Function plot

One or more curves y = f(x) with a legend.

plot(g, "expression in x", xmin, xmax, colour, style, label). The expression is quoted. Add a line per curve.

Example

<script>
quiet {
g = graph()
plot(g, "sin(x)", 0, 2*pi, "tomato", "solid", "sin x")
plot(g, "cos(x)", 0, 2*pi, "steelblue", "dashed", "cos x")
title(g, "Sine and cosine")
xticks(g, 1)
}
g
</script>

Data: line, scatter and area

Measured values held in arrays.

plot(g, xs, ys, ...) joins the points; scatter(g, xs, ys, colour, label) shows them as dots; area(g, xs, ys) fills down to the axis. With one array, x counts 1, 2, 3, ...

Example

<script>
quiet {
t = [0, 1, 2, 3, 4, 5]
v = [0, 9.8, 19.6, 29.4, 39.2, 49]
g = graph()
plot(g, t, v, "steelblue", "solid", "v = 9.8 t")
scatter(g, t, v, "tomato", "readings")
title(g, "Speed of a falling body")
}
g
</script>

Bar chart, pie chart and histogram

Counts by category, parts of a whole, or raw data sorted into bins.

bar(g, xs, ys, colour, label) draws one bar per value. With names for xs, bar(g, ["Mon", "Tue"], ys), each bar is named on the axis; stacked(g) stands several bar series on one another.

pie(g, names, values) draws parts of a whole with each name, value and share beside it; a fourth argument such as 0.6 leaves a hole, which makes a ring.

histogram(g, data, bins) bins raw data itself. graph(w, h) sets the size of any of them in pixels.

Bar chart

<script>
quiet {
g = graph()
bar(g, [1, 2, 3, 4, 5], [4, 9, 2, 7, 5], "goldenrod", "Students")
title(g, "Marks out of 5")
}
g
</script>

Named bars, stacked

<script>
quiet {
g = graph(560, 300)
days = ["Mon", "Tue", "Wed", "Thu", "Fri"]
bar(g, days, [2, 4, 1, 3, 2], "#3987e5", "Lessons read")
bar(g, days, [1, 2, 3, 0, 4], "#d95926", "Questions answered")
stacked(g)
}
g
</script>

Pie chart

<script>
quiet {
p = graph(460, 260)
pie(p, ["Physics", "Chemistry", "Mathematics", "Biology"], [40, 25, 20, 15])
title(p, "Questions by subject")
}
p
</script>

Ring

<script>
quiet {
p = graph(460, 240)
pie(p, ["Correct at the first try", "Correct after another try", "Not correct yet"], [15, 3, 4], 0.6)
}
p
</script>

Histogram

<script>
quiet {
heights = [150, 152, 155, 155, 158, 160, 160, 161, 163, 165, 165, 166, 170, 172, 178]
g = graph()
histogram(g, heights, 6)
title(g, "Heights in a class (cm)")
}
g
</script>

Parametric and polar curves

Curves given by x(t), y(t) or by r(theta).

plotParametric(g, "x(t)", "y(t)", tmin, tmax, ...) and plotPolar(g, "r(theta)", thetaMin, thetaMax, ...). aspect(g, true) gives both axes the same scale, so a circle looks like a circle.

tracePlot(g, seconds) makes every curve of the graph draw itself in over that many seconds, a function from left to right, a parametric or polar curve in the order of its parameter. It works for plot, plotParametric and plotPolar alike; in a PDF the finished curves are shown. In one line: tracePlot(g, "sin(x)", 0, 2*pi, 4), tracePolar(g, "1 + cos(theta)", 0, 2*pi, 4) and traceParametric(g, "3*cos(t)", "2*sin(t)", 0, 2*pi, 4) plot the curve and trace it, with the colour, line type and label after the seconds.

Example

<script>
quiet {
g = graph()
plotParametric(g, "3*cos(t)", "2*sin(t)", 0, 2*pi, "steelblue", "solid", "ellipse")
plotPolar(g, "1 + cos(theta)", 0, 2*pi, "tomato", "solid", "cardioid")
aspect(g, true)
}
g
</script>

Logarithmic axis

Growth and decay over many powers of ten.

logScale(g, "x"), logScale(g, "y") or logScale(g, "both").

Example

<script>
quiet {
n = [1, 2, 3, 4, 5, 6, 7, 8]
cells = [2, 4, 8, 16, 32, 64, 128, 256]
g = graph()
plot(g, n, cells, "seagreen", "solid", "cells")
logScale(g, "y")
title(g, "Doubling every generation")
}
g
</script>

Diagrams

A diagram is a <script> block whose last line is a diagram. Create it with d = diagram(width, height), set xrangeOn and yrangeOn, draw into it with the functions ending in On, and put d on the last line.

Coordinates are ordinary x, y units inside the ranges you set. Angles are in degrees, counter-clockwise from the positive x-axis. Styles come after the geometry, in order: colour, label, then a fill colour (shapes) or arrows ("one", "two", "none"). Leave a label as "" and place it yourself with textOn when its position matters. textOn(d, point, "text") starts the text at the point and runs to the right; text is 14 px high whatever the ranges are, so leave room for it. To centre text on a point (a name inside a box, a tick value under an axis, a charge sign) write labelAtOn(d, point, "text", "center"): with textOn the same coordinates put the text to the right of the point and above it, on top of whatever is there. labelAtOn(d, point, "text", side) puts a label beside a point instead ("above", "below", "left", "right"), clear of what is drawn there. Every function is listed on the Diagrams page.

A diagram is drawn on a white surface, on the web and in a PDF. A shape, label or text with no colour of its own is drawn in dark ink; give it a colour to make it stand out.

Points, segments and circles

The basic shapes and how a diagram is set up.

drawOn(d, shape, colour, label, fill) draws a point, circle, ellipse or line. drawOn(d, pointA, pointB, ...) draws the segment between two points. Turn the axes and grid off for a geometry figure.

Example

<script>
quiet {
d = diagram(480, 360)
xrangeOn(d, -4, 4)
yrangeOn(d, -3, 3)
axesOn(d, false)
gridOn(d, false)
O = point(0, 0)
P = point(2, 0)
drawOn(d, circle(O, 2), "steelblue", "", withAlpha("steelblue", 0.15))
drawOn(d, O, P, "tomato", "r = 2")
drawOn(d, O, "black", "O")
drawOn(d, P, "black", "P")
}
d
</script>

Triangles and angle markers

A polygon with its sides and angles labelled.

polygonOn(d, A, B, C, colour, label, fill) takes any number of corners. angleMarkerOn(d, vertex, pointOnRay1, pointOnRay2, radius, label, colour) draws the arc and its label; rightAngleMarkerOn(d, vertex, pointOnRay1, pointOnRay2, size, label, colour) draws the square. Neither needs you to work out an angle.

Example

<script>
quiet {
d = diagram(480, 380)
xrangeOn(d, -1, 6)
yrangeOn(d, -1, 5)
axesOn(d, false)
gridOn(d, false)
A = point(0, 0)
B = point(5, 0)
C = point(0, 4)
polygonOn(d, A, B, C, "steelblue", "", withAlpha("steelblue", 0.15))
rightAngleMarkerOn(d, A, B, C, 0.4, "", "black")
angleMarkerOn(d, B, C, A, 0.9, "θ", "tomato")
textOn(d, point(2.5, -0.4), "5 cm", "black")
textOn(d, point(-0.7, 2), "4 cm", "black")
textOn(d, point(-0.3, -0.3), "A", "black")
textOn(d, point(5.3, -0.3), "B", "black")
textOn(d, point(-0.3, 4.3), "C", "black")
}
d
</script>

Vectors and force diagrams

Arrows from a point, by components or by length and angle.

drawOn(d, point, vec(dx, dy), colour, label, arrows) draws an arrow by its components. vectorFromOn(d, point, length, degrees, colour, label) draws one by length and direction, and segmentFromOn the same without an arrowhead.

drawOn(d, p1, p2, colour, label) with two points draws a plain line between them, with no arrowhead: a wire, a side, a plate, a ray path. For an arrow from p1 to p2, add "one" after the label; "two" puts a head at both ends (a dimension line).

Free-body diagram

<script>
quiet {
d = diagram(480, 380)
xrangeOn(d, -4, 4)
yrangeOn(d, -4, 4)
axesOn(d, false)
gridOn(d, false)
c = point(0, 0)
polygonOn(d, point(-1, -0.6), point(1, -0.6), point(1, 0.6), point(-1, 0.6),
  "gray", "", withAlpha("gray", 0.3))
vectorFromOn(d, c, 3, 90, "seagreen", "N")
vectorFromOn(d, c, 3, 270, "tomato", "mg")
vectorFromOn(d, c, 2.5, 0, "steelblue", "F")
vectorFromOn(d, c, 1.5, 180, "darkgoldenrod", "f")
}
d
</script>

Curves, arcs and text

Smooth paths between points, circular arcs and free labels.

curveBetweenOn(d, a, b, bend, colour, label, style, width) bends a line between two points; bend is signed, and 0 is straight. curveOn(d, p0, control, p1) is a Bezier curve. arcAtOn(d, centre, radius, startDegrees, sweepDegrees, colour) is part of a circle. textOn(d, point, "text", colour, fontSize) places a label.

Example

<script>
quiet {
d = diagram(480, 320)
xrangeOn(d, -1, 7)
yrangeOn(d, -1, 4)
axesOn(d, false)
gridOn(d, false)
A = point(0, 0)
B = point(6, 0)
drawOn(d, A, B, "gray", "")
curveBetweenOn(d, A, B, -0.45, "tomato", "", "dashed", 2)
arcAtOn(d, A, 1.2, 0, 60, "darkgoldenrod")
textOn(d, point(1.5, 0.7), "60°", "darkgoldenrod", 14)
textOn(d, point(3, 3.1), "path of the ball", "tomato", 14)
}
d
</script>

A graph of a function inside a diagram

For a coordinate-geometry figure: axes, a line or curve, and marked points.

Leave the axes and grid on. A line is drawn across the whole diagram; a parabola or other curve is a plot on a graph() (see Plots), which can sit next to a diagram in the same document.

Example

<script>
quiet {
d = diagram(480, 400)
xrangeOn(d, -5, 5)
yrangeOn(d, -5, 5)
A = point(-2, -3)
B = point(3, 2)
drawOn(d, line(A, B), "steelblue", "y = x - 1")
drawOn(d, A, "tomato", "A(-2, -3)")
drawOn(d, B, "tomato", "B(3, 2)")
drawOn(d, circle(point(0, 0), 3), "seagreen", "x² + y² = 9")
}
d
</script>

Solids

Textbook cubes, prisms, cylinders and spheres, with marked corners and edges.

cubeOn(d, origin, size, dx, dy, colour) draws a cube in a slanted view; dx, dy is the offset of the back face. It returns a record whose vertices are the exact corner points, so markers and labels attach to the figure. prismOn, cylinderOn and sphereOn work the same way.

cornerMarkerOn(d, point, label, colour) puts a labelled dot on a corner; edgeMarkerOn(d, a, b, fraction, label, colour) puts one part-way along an edge.

Example

<script>
quiet {
d = diagram(480, 380)
xrangeOn(d, -1, 5)
yrangeOn(d, -1, 4.5)
axesOn(d, false)
gridOn(d, false)
s = cubeOn(d, point(0, 0), 3, 1, 1, "steelblue")
cornerMarkerOn(d, s.vertices[0], "A", "tomato")
edgeMarkerOn(d, s.vertices[0], s.vertices[1], 1/2, "M", "darkgoldenrod")
}
d
</script>

Springs, coils and fields

Ready-made physics shapes and field pictures.

springOn(d, start, end, amplitude, turns, colour), coilOn and inductorOn draw between two attachment points. vectorFieldOn(d, "Fx", "Fy", xmin, xmax, ymin, ymax, nx, ny, mode, scale, colour) draws a grid of arrows; the mode is "direction", "scaled" or "capped". pointChargeLinesOn(d, centre, charge, lineCount, xmin, xmax, ymin, ymax) draws the field lines of one charge, and fieldLineOn traces one line of any field.

Spring and mass

<script>
quiet {
d = diagram(520, 260)
xrangeOn(d, -1, 8)
yrangeOn(d, -2, 2)
axesOn(d, false)
gridOn(d, false)
drawOn(d, point(0, -1.5), point(0, 1.5), "gray", "")
springOn(d, point(0, 0), point(5, 0), 0.4, 8, "steelblue")
polygonOn(d, point(5, -0.7), point(6.4, -0.7), point(6.4, 0.7), point(5, 0.7),
  "tomato", "", withAlpha("tomato", 0.3))
textOn(d, point(5.7, 0), "m", "black")
textOn(d, point(2.5, 0.9), "k", "black")
}
d
</script>

Field of a positive charge

<script>
quiet {
d = diagram(420, 420)
xrangeOn(d, -3, 3)
yrangeOn(d, -3, 3)
axesOn(d, false)
gridOn(d, false)
pointChargeLinesOn(d, point(0, 0), 1, 12, -3, 3, -3, 3)
}
d
</script>

Circuits

Resistors, cells, meters and the other circuit symbols, one call each.

Name the corners of the circuit as points, then put one component on each branch: resistorOn(d, a, b, label), cellOn, batteryOn(d, a, b, cells, label), capacitorOn, bulbOn, variableResistorOn, inductorOn(d, a, b, label), acSourceOn, diodeOn and zenerDiodeOn (the triangle points from a to b), meterOn(d, a, b, "A", label) and switchKeyOn(d, a, b, "open", label). Each draws its symbol in the middle of the branch and the lead wires out to both points, so do not draw the zigzag or the leads yourself. A cell's positive (long) plate faces its second point.

The label is an argument, not a separate textOn: the engine puts it beside the symbol and clear of it, outside the circuit by default. Add a side ("above", "below", "left", "right") after the label to choose. wireOn(d, p1, p2, p3) joins the remaining corners, junctionOn(d, p, "A") marks and names a node, currentArrowOn(d, a, b, "I") shows a current on a wire, terminalOn(d, p, "A") ends a wire in an open ring, groundOn(d, p) earths a point and labelAtOn(d, p, "+", "left") writes beside a point.

Three symbols take more than two points. transistorOn(d, base, collector, emitter, "npn", label) draws the transistor and its three leads: put the collector and emitter one above the other and the base to one side, level with their middle. gateOn(d, "nand", inputA, inputB, output, label) draws a logic gate ("and", "or", "not" with one input, "nand", "nor", "xor", "xnor") with its leads; give one gate's output point as the next gate's input to connect them. twoWaySwitchOn(d, pivot, contactA, contactB, "a", label) draws a switch thrown to contact "a" or "b", or "open".

Symbols have a fixed size on the page, so set xrangeOn and yrangeOn first, and choose ranges that give each branch about 80 px or more: at least 2 units when the diagram shows 40 px per unit.

Two resistors in parallel, with an ammeter

<script>
quiet {
d = diagram(420, 300)
xrangeOn(d, -1.5, 7.5)
yrangeOn(d, -1.2, 4.2)
axesOn(d, false)
gridOn(d, false)
A = point(0, 3)
B = point(3, 3)
C = point(6, 3)
D = point(6, 0)
E = point(3, 0)
F = point(0, 0)
cellOn(d, F, A, "12 V")
resistorOn(d, A, B, "4 Ω")
meterOn(d, B, C, "A")
resistorOn(d, C, D, "6 Ω")
resistorOn(d, B, E, "3 Ω", "right")
switchKeyOn(d, E, D, "closed", "K")
wireOn(d, E, F)
junctionOn(d, B, "P")
junctionOn(d, E, "Q")
}
d
</script>

Sliders and animation

A picture the reader can change, or one that moves.

slider(label, min, max, step, start) gives a value the reader can drag; the script runs again with the new value. animate(tmin, tmax, frames, f) calls your function f(t) once per frame; each call draws one frame with the functions that have no On (draw, text, vectorFrom, ...).

Slider

<script>
n = slider("sides", 3, 12, 1, 6)
regularPolygon(point(0, 0), 3, n, 90, "teal", "", withAlpha("teal", 0.3))
</script>

Animation

<script>
quiet {
xrange(-5, 5)
yrange(-5, 5)
}
fn frame(t) {
  draw(circle(point(0, 0), 4), "gray")
  draw(point(4*cos(t), 4*sin(t)), "tomato", "P")
}
animate(0, 2*pi, 60, frame)
</script>

In a PDF: A slider shows its starting value. An animation has no PDF form.

3D surfaces

A surface z = f(x, y) the reader can turn.

surf("expression in x and y", xmin, xmax, ymin, ymax) draws a shaded surface; mesh a wireframe; surfc adds contour lines on the floor.

Example

<script>
surf("sin(sqrt(x^2 + y^2))", -5, 5, -5, 5)
</script>

In a PDF: None. Export a PNG from the web page and include it with <image>.

Images, audio and video

Files that already exist. The path or address may be written between the tags or as a src attribute; use one form consistently.

<image>

A picture from a file or web address.

Use it for photographs, scans and drawings made elsewhere. A diagram you can describe is better written as a <script> diagram: it stays sharp, editable and exact.

Example

<image>diagrams/pulley.png</image>
<image src="https://example.com/figures/cell.png">

<audio>

An audio player.

Nothing is downloaded until the reader presses play.

Example

<audio src="https://example.com/lesson/pronunciation.mp3">

In a PDF: A link to the file.

<video>

A video player.

Nothing is downloaded until the reader presses play.

Example

<video src="https://example.com/lesson/demo.mp4">

In a PDF: A link to the file.

Questions

An assessment item the reader can answer on the page, and that prints as a question with its key.

<question>

A question with options, answer, hint and solution.

The prompt is ordinary blocks (paragraphs, maths, tables, a diagram script). Then, each on its own line: <option label="A">...</option> for every choice, <answer>...</answer>, <hint>...</hint>, and a <solution> holding blocks. Answer, hint and solution are optional; leave them out to hide them.

Without format, it is worked out: options with one answer give single_correct, several answer labels (A, C) give multiple_correct; with no options a whole number gives integer, a decimal or fraction numerical, anything else subjective. Two options that come out equal are reported as an error.

Attributes

  • id -- A stable name for the question. Optional but recommended.
  • format -- single_correct, multiple_correct, integer, numerical, true_false, assertion_reason, matrix_match, subjective. Optional; inferred when left out.
  • marks -- Marks for a correct answer. Optional.
  • shuffle -- A bare flag: the option texts are put in a random order and the answer follows them.
  • template -- A bare flag marking a question whose numbers are drawn at random (see <params>).

Single correct

<question id="satellite-energy" format="single_correct" marks="4">
<p>For a satellite in a circular orbit, the ratio $KE/abs(PE)$ is:</p>
<option label="A">$1/2$</option>
<option label="B">$1$</option>
<option label="C">$2$</option>
<option label="D">$4$</option>
<answer>A</answer>
<hint>For a circular orbit, $PE = -2*KE$.</hint>
<solution>
<p>Since the size of $PE$ is twice $KE$, the ratio is $1/2$.</p>
</solution>
</question>

Integer answer, format inferred

<question id="carbon-electrons" marks="2">
<p>How many electrons does a neutral carbon atom have?</p>
<answer>6</answer>
<solution>
<p>The atomic number of carbon is 6, so a neutral atom has 6 electrons.</p>
</solution>
</question>

<ordering>

Arrange items in the correct order.

Each <item> holds one piece to order.

Example

<ordering title="Steps">
<item>First</item>
<item>Second</item>
</ordering>

<params> and <steps>

A question template: numbers drawn at random, with working that follows them.

<params> is ApasScript that draws the values: randInt(lo, hi) for a whole number, randDecimal(lo, hi, step) for an exact decimal on a grid, choose([...]) for one item of a list. require(condition) lines make it draw again until they hold. It shows nothing and its names are visible on the whole page, like <calc>.

<steps> holds one name = expression per line, optionally followed by | unit. Each line prints the formula, then the formula with the values put in, then the result. <steps decimal> writes decimals instead of fractions.

A value in the working may be a quantity (u = quantity(10, "m/s") in <params> or <calc>): the line with the values put in then shows each with its unit, and the answer has the unit the engine arrives at. | unit after such a step gives the answer in that unit (| km/h). A step that is a solve(...) call is shown as the equation it solves. A // comment at the end of a step is not shown.

The page always uses the same numbers unless a seed is given, so a worksheet and its answer key agree.

Example

<question id="car-accel" format="numerical" template>
<params>
u = randInt(10, 30)
a = randInt(2, 5)
t = randInt(2, 6)
require(u + a*t < 60)
</params>
<p>A car moving at $u$ m/s accelerates at $a$ $\mathrm{m/s^2}$ for $t$ s. Find its final speed.</p>
<answer>$v$ m/s</answer>
<solution>
<steps>
v = u + a*t | m/s
</steps>
</solution>
</question>

Slides, notebooks and mindmaps

Three blocks that change what the whole document is.

<sequence>

A timed sequence of content frames.

Each <frame> contains ordinary content blocks and an optional duration in milliseconds.

Example

<sequence title="Steps">
<frame duration="1000">
<p>First</p>
</frame>
<frame duration="1000">
<p>Second</p>
</frame>
</sequence>

<slide>

One slide of a presentation.

A presentation is a list of <slide> blocks at the top level; everything else goes inside a slide. A heading inside a slide does not start a new one. Slides cannot be nested.

A callout with type="speaker notes" is shown to the presenter only.

Attributes

  • id -- A stable name for the slide. Optional.
  • title -- The slide title; takes inline maths and formatting. Optional. Write &quot; for a quotation mark inside it.

Example

<slide id="second-law" title="Newton's second law">
<p>For a constant mass:</p>
$$
F = m*a
$$
<callout type="speaker notes">
<p>Ask what happens when the mass doubles.</p>
</callout>
</slide>
<slide id="your-turn" title="Your turn">
<question>
<p>A 2 kg body accelerates at $3\,\mathrm{m/s^2}$. Find the force in newtons.</p>
<answer>6</answer>
</question>
</slide>

<cell>

One cell of a notebook.

A document containing any <cell> is a notebook. Text and question cells hold ordinary blocks; a code cell's body is ApasScript. All code in a notebook shares its names, top to bottom, so a later cell and later $x$ maths can use an earlier value. Cells cannot be nested.

The cell tags stand on their own lines. Each id must be unique in the document.

Attributes

  • id -- Required and unique.
  • type -- text (the default), code or question.

Example

<cell id="intro" type="text">
<h2>Kinetic energy</h2>
<p>We compute $E = m*v^2/2$ for a 3 kg body at 4 m/s.</p>
</cell>
<cell id="work" type="code">
m = 3
v = 4
E = m*v^2/2
</cell>
<cell id="result" type="text">
<p>The energy is $E$ J.</p>
</cell>

<mindmap>

A mindmap, concept map or pathway: nodes joined by branches and arrows.

<node id="...">text</node> makes a node; the text takes inline maths and formatting. Place a node with x and y, or relative to another with parent plus direction (left, right, up, down) or angle in degrees, and distance. A node with a parent gets a branch to it automatically.

<connect from="a" to="b" .../> adds a connection. path is straight or curved (with bend), arrow is none, start, middle, end, both or a percentage, label is text on the line, line-type is solid, dashed or dotted. Every opening tag stays on one line.

Node shapes: rounded (default), rectangle, circle, ellipse, diamond, text. color sets the outline and fill the background.

Attributes

  • id -- A name for the map. Optional.

Example

<mindmap id="states">
<node id="matter" x="0" y="0">States of matter</node>
<node id="solid" parent="matter" direction="left" distance="260" shape="rectangle">Solid</node>
<node id="liquid" parent="matter" direction="down" distance="170" shape="ellipse">Liquid</node>
<node id="gas" parent="matter" direction="right" distance="260" shape="circle">Gas</node>
<connect from="solid" to="liquid" path="curved" bend="-0.3" arrow="end" label="melting"/>
<connect from="liquid" to="gas" path="curved" bend="-0.3" arrow="end" label="boiling"/>
</mindmap>

Agent and application blocks

Compact result cards for AI agents and applications: status, numbers, sources, data, files and approvals. They are drawn on the web only and left out of a PDF. Quote an attribute value that contains spaces. Rows use | between cells, as in <table>.

<tool>

The status of a step an agent ran.

The body is plain text.

Attributes

  • name -- What the step was.
  • state -- running, complete, failed or approval_required.

Example

<tool name="Analyse dataset" state="complete">
Read 1,240 sales rows.
</tool>

<risk>

A notice the reader should see before relying on the result.

The body is plain text.

Attributes

  • level -- info, warning or danger.
  • title -- The notice's heading.

Example

<risk level="warning" title="Educational analysis only">
This is general information, not investment advice.
</risk>

<metrics>

A row of headline numbers.

Holds one <metric> line per number. Each takes label, value, and optionally change and tone (positive or negative).

Attributes

  • title -- Heading for the group. Optional.

Example

<metrics title="Campaign performance">
<metric label="Reach" value="40.5K" change="+12.4%" tone="positive">
<metric label="Conversions" value="1,284" change="-3.1%" tone="negative">
</metrics>

<sources>

A list of the sources behind a result.

Holds one <source> line per source, with optional title, url, publisher, published_at and retrieved_at.

Attributes

  • title -- Heading for the list. Optional.

Example

<sources title="Data sources">
<source title="Quarterly report" url="https://example.com/report" publisher="Example Company" retrieved_at="Retrieved today">
</sources>

<actions>

Buttons the host application handles.

Holds one <action> line per button, with id, label and the bare flags primary and disabled. The page passes the id to the application; the document itself does nothing.

Attributes

  • title -- Heading for the group. Optional.

Example

<actions title="Next steps">
<action id="download_png" label="Download PNG" primary>
<action id="copy_chart" label="Copy chart">
</actions>

<dataset>

A preview of an uploaded or loaded dataset.

<column name="..." type="..."> lines declare the columns, then one <row>...</row> per record with its cells in the same order.

Attributes

  • name -- The dataset's name.
  • sheet -- Sheet or table name. Optional.
  • rows -- Total number of rows in the full dataset.
  • preview_limit -- How many rows to show.

Example

<dataset name="Q3 Sales" sheet="Revenue" rows="1240" preview_limit="8">
<column name="month" type="date">
<column name="region" type="string">
<column name="revenue" type="number">
<row>2026-07-01 | North | 42000</row>
<row>2026-08-01 | West | 38900</row>
</dataset>

<data>

A paged data table with typed columns.

Like <dataset>: <column> lines (with name, an optional display label and type), then <row> lines. Use <table> for a table in a lesson; use this for records an application shows.

Attributes

  • title -- Heading for the table.
  • page_size -- Rows per page.

Example

<data title="Monthly revenue" page_size="3">
<column name="month" label="Month">
<column name="revenue" label="Revenue" type="number">
<row>Jan | 42000</row>
<row>Feb | 38900</row>
</data>

<file>

A card for a file the reader can download.

A single opening tag with no closing tag.

Attributes

  • title -- Card heading.
  • name -- File name.
  • type -- Kind of file, such as PDF.
  • size -- Size as text.
  • description -- One line about the file.
  • url -- Where to download it.
  • link_label -- Text of the link.

Example

<file title="Generated report" name="q3-summary.pdf" type="PDF" size="1.8 MB" description="Generated from Q3 campaign data" url="https://example.com/q3-summary.pdf" link_label="Download">

<social>

A draft social-media post.

The body is the post text, followed by any number of one-line <tag>...</tag> hashtags.

Attributes

  • platform -- Where it will be posted.
  • image_url -- Picture shown with the post. Optional.
  • image_alt -- Description of the picture. Optional.

Example

<social platform="LinkedIn">
Our Q3 campaign reached 40.5K people.
<tag>MarketingAnalytics</tag>
<tag>Growth</tag>
</social>

<approval>

A request for the reader's go-ahead before something is published.

The body is the summary, followed by <action> lines for the choices.

Attributes

  • title -- The question being asked.
  • platform -- Where it will be published.
  • destination -- The page or account.
  • scheduled_at -- When, as text.

Example

<approval title="Ready to publish?" platform="LinkedIn" destination="Company page" scheduled_at="Today, 6:00 PM">
The post and visual are ready for final review.
<action id="approve_publish" label="Approve and publish" primary>
<action id="edit_draft" label="Edit draft">
</approval>