summaryrefslogtreecommitdiff
path: root/tests/suite/text
diff options
context:
space:
mode:
Diffstat (limited to 'tests/suite/text')
-rw-r--r--tests/suite/text/case.typ11
-rw-r--r--tests/suite/text/coma.typ26
-rw-r--r--tests/suite/text/copy-paste.typ8
-rw-r--r--tests/suite/text/deco.typ85
-rw-r--r--tests/suite/text/edge.typ39
-rw-r--r--tests/suite/text/em.typ33
-rw-r--r--tests/suite/text/font.typ66
-rw-r--r--tests/suite/text/lang.typ74
-rw-r--r--tests/suite/text/lorem.typ32
-rw-r--r--tests/suite/text/raw.typ630
-rw-r--r--tests/suite/text/shift.typ19
-rw-r--r--tests/suite/text/smallcaps.typ3
-rw-r--r--tests/suite/text/smartquote.typ122
-rw-r--r--tests/suite/text/space.typ60
14 files changed, 1208 insertions, 0 deletions
diff --git a/tests/suite/text/case.typ b/tests/suite/text/case.typ
new file mode 100644
index 00000000..2bf68bc3
--- /dev/null
+++ b/tests/suite/text/case.typ
@@ -0,0 +1,11 @@
+// Test the `upper` and `lower` functions.
+
+--- lower-and-upper ---
+#let memes = "ArE mEmEs gReAt?";
+#test(lower(memes), "are memes great?")
+#test(upper(memes), "ARE MEMES GREAT?")
+#test(upper("Ελλάδα"), "ΕΛΛΆΔΑ")
+
+--- upper-bad-type ---
+// Error: 8-9 expected string or content, found integer
+#upper(1)
diff --git a/tests/suite/text/coma.typ b/tests/suite/text/coma.typ
new file mode 100644
index 00000000..df757633
--- /dev/null
+++ b/tests/suite/text/coma.typ
@@ -0,0 +1,26 @@
+--- coma ---
+// LARGE
+#set page(width: 450pt, margin: 1cm)
+
+*Technische Universität Berlin* #h(1fr) *WiSe 2019/2020* \
+*Fakultät II, Institut for Mathematik* #h(1fr) Woche 3 \
+Sekretariat MA \
+Dr. Max Mustermann \
+Ola Nordmann, John Doe
+
+#v(3mm)
+#align(center)[
+ #set par(leading: 3mm)
+ #text(1.2em)[*3. Übungsblatt Computerorientierte Mathematik II*] \
+ *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) \
+ *Alle Antworten sind zu beweisen.*
+]
+
+*1. Aufgabe* #h(1fr) (1 + 1 + 2 Punkte)
+
+Ein _Binärbaum_ ist ein Wurzelbaum, in dem jeder Knoten ≤ 2 Kinder hat.
+Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel
+zu _v_, und die Höhe von _v_ ist die Länge eines längsten (absteigenden) Weges
+von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel.
+
+#align(center, image("/assets/images/graph.png", width: 75%))
diff --git a/tests/suite/text/copy-paste.typ b/tests/suite/text/copy-paste.typ
new file mode 100644
index 00000000..ff6da893
--- /dev/null
+++ b/tests/suite/text/copy-paste.typ
@@ -0,0 +1,8 @@
+// Test copy-paste and search in PDF with ligatures
+// and Arabic test. Must be tested manually!
+
+--- text-copy-paste-ligatures ---
+The after fira 🏳️‍🌈!
+
+#set text(lang: "ar", font: "Noto Sans Arabic")
+مرحبًا
diff --git a/tests/suite/text/deco.typ b/tests/suite/text/deco.typ
new file mode 100644
index 00000000..07fdb6c1
--- /dev/null
+++ b/tests/suite/text/deco.typ
@@ -0,0 +1,85 @@
+// Test text decorations.
+
+--- underline-overline-strike ---
+#let red = rgb("fc0030")
+
+// Basic strikethrough.
+#strike[Statements dreamt up by the utterly deranged.]
+
+// Move underline down.
+#underline(offset: 5pt)[Further below.]
+
+// Different color.
+#underline(stroke: red, evade: false)[Critical information is conveyed here.]
+
+// Inherits font color.
+#text(fill: red, underline[Change with the wind.])
+
+// Both over- and underline.
+#overline(underline[Running amongst the wolves.])
+
+--- strike-with ---
+#let redact = strike.with(stroke: 10pt, extent: 0.05em)
+#let highlight-custom = strike.with(stroke: 10pt + rgb("abcdef88"), extent: 0.05em)
+
+// Abuse thickness and transparency for redacting and highlighting stuff.
+Sometimes, we work #redact[in secret].
+There might be #highlight-custom[redacted] things.
+
+--- underline-stroke-folding ---
+// Test stroke folding.
+#set underline(stroke: 2pt, offset: 2pt)
+#underline(text(red, [DANGER!]))
+
+--- underline-background ---
+// Test underline background
+#set underline(background: true, stroke: (thickness: 0.5em, paint: red, cap: "round"))
+#underline[This is in the background]
+
+--- overline-background ---
+// Test overline background
+#set overline(background: true, stroke: (thickness: 0.5em, paint: red, cap: "round"))
+#overline[This is in the background]
+
+--- strike-background ---
+// Test strike background
+#set strike(background: true, stroke: 5pt + red)
+#strike[This is in the background]
+
+--- highlight ---
+// Test highlight.
+This is the built-in #highlight[highlight with default color].
+We can also specify a customized value
+#highlight(fill: green.lighten(80%))[to highlight].
+
+--- highlight-bounds ---
+// Test default highlight bounds.
+#highlight[ace],
+#highlight[base],
+#highlight[super],
+#highlight[phone #sym.integral]
+
+--- highlight-edges ---
+// Test a tighter highlight.
+#set highlight(top-edge: "x-height", bottom-edge: "baseline")
+#highlight[ace],
+#highlight[base],
+#highlight[super],
+#highlight[phone #sym.integral]
+
+--- highlight-edges-bounds ---
+// Test a bounds highlight.
+#set highlight(top-edge: "bounds", bottom-edge: "bounds")
+#highlight[abc]
+#highlight[abc #sym.integral]
+
+--- highlight-radius ---
+// Test highlight radius
+#highlight(radius: 3pt)[abc],
+#highlight(radius: 1em)[#lorem(5)]
+
+--- highlight-stroke ---
+// Test highlight stroke
+#highlight(stroke: 2pt + blue)[abc]
+#highlight(stroke: (top: blue, left: red, bottom: green, right: orange))[abc]
+#highlight(stroke: 1pt, radius: 3pt)[#lorem(5)]
diff --git a/tests/suite/text/edge.typ b/tests/suite/text/edge.typ
new file mode 100644
index 00000000..57732156
--- /dev/null
+++ b/tests/suite/text/edge.typ
@@ -0,0 +1,39 @@
+// Test top and bottom text edge.
+
+--- text-edge ---
+#set page(width: 160pt)
+#set text(size: 8pt)
+
+#let try(top, bottom) = rect(inset: 0pt, fill: conifer)[
+ #set text(font: "IBM Plex Mono", top-edge: top, bottom-edge: bottom)
+ From #top to #bottom
+]
+
+#let try-bounds(top, bottom) = rect(inset: 0pt, fill: conifer)[
+ #set text(font: "IBM Plex Mono", top-edge: top, bottom-edge: bottom)
+ #top to #bottom: "yay, Typst"
+]
+
+#try("ascender", "descender")
+#try("ascender", "baseline")
+#try("cap-height", "baseline")
+#try("x-height", "baseline")
+#try-bounds("cap-height", "baseline")
+#try-bounds("bounds", "baseline")
+#try-bounds("bounds", "bounds")
+#try-bounds("x-height", "bounds")
+
+#try(4pt, -2pt)
+#try(1pt + 0.3em, -0.15em)
+
+--- text-edge-bad-type ---
+// Error: 21-23 expected "ascender", "cap-height", "x-height", "baseline", "bounds", or length, found array
+#set text(top-edge: ())
+
+--- text-edge-bad-value ---
+// Error: 24-26 expected "baseline", "descender", "bounds", or length
+#set text(bottom-edge: "")
+
+--- text-edge-wrong-edge ---
+// Error: 24-36 expected "baseline", "descender", "bounds", or length
+#set text(bottom-edge: "cap-height")
diff --git a/tests/suite/text/em.typ b/tests/suite/text/em.typ
new file mode 100644
index 00000000..be7e3428
--- /dev/null
+++ b/tests/suite/text/em.typ
@@ -0,0 +1,33 @@
+// Test font-relative sizing.
+
+--- text-size-em-nesting ---
+#set text(size: 5pt)
+A // 5pt
+#[
+ #set text(size: 2em)
+ B // 10pt
+ #[
+ #set text(size: 1.5em + 1pt)
+ C // 16pt
+ #text(size: 2em)[D] // 32pt
+ E // 16pt
+ ]
+ F // 10pt
+]
+G // 5pt
+
+--- text-size-em ---
+// Test using ems in arbitrary places.
+#set text(size: 5pt)
+#set text(size: 2em)
+#set square(fill: red)
+
+#let size = {
+ let size = 0.25em + 1pt
+ for _ in range(3) {
+ size *= 2
+ }
+ size - 3pt
+}
+
+#stack(dir: ltr, spacing: 1fr, square(size: size), square(size: 25pt))
diff --git a/tests/suite/text/font.typ b/tests/suite/text/font.typ
new file mode 100644
index 00000000..47ec6419
--- /dev/null
+++ b/tests/suite/text/font.typ
@@ -0,0 +1,66 @@
+// Test configuring font properties.
+
+--- text-font-properties ---
+// Set same font size in three different ways.
+#text(20pt)[A]
+#text(2em)[A]
+#text(size: 15pt + 0.5em)[A]
+
+// Do nothing.
+#text()[Normal]
+
+// Set style (is available).
+#text(style: "italic")[Italic]
+
+// Set weight (is available).
+#text(weight: "bold")[Bold]
+
+// Set stretch (not available, matching closest).
+#text(stretch: 50%)[Condensed]
+
+// Set font family.
+#text(font: "IBM Plex Serif")[Serif]
+
+// Emoji.
+Emoji: 🐪, 🌋, 🏞
+
+// Colors.
+#[
+ #set text(fill: eastern)
+ This is #text(rgb("FA644B"))[way more] colorful.
+]
+
+// Transparency.
+#block(fill: green)[
+ #set text(fill: rgb("FF000080"))
+ This text is transparent.
+]
+
+// Disable font fallback beyond the user-specified list.
+// Without disabling, New Computer Modern Math would come to the rescue.
+#set text(font: ("PT Sans", "Twitter Color Emoji"), fallback: false)
+2π = 𝛼 + 𝛽. ✅
+
+--- text-call-body ---
+// Test string body.
+#text("Text") \
+#text(red, "Text") \
+#text(font: "Ubuntu", blue, "Text") \
+#text([Text], teal, font: "IBM Plex Serif") \
+#text(forest, font: "New Computer Modern", [Text]) \
+
+--- text-bad-argument ---
+// Error: 11-16 unexpected argument
+#set text(false)
+
+--- text-style-bad ---
+// Error: 18-24 expected "normal", "italic", or "oblique"
+#set text(style: "bold", weight: "thin")
+
+--- text-bad-extra-argument ---
+// Error: 23-27 unexpected argument
+#set text(size: 10pt, 12pt)
+
+--- text-bad-named-argument ---
+// Error: 11-31 unexpected argument: something
+#set text(something: "invalid")
diff --git a/tests/suite/text/lang.typ b/tests/suite/text/lang.typ
new file mode 100644
index 00000000..74f70140
--- /dev/null
+++ b/tests/suite/text/lang.typ
@@ -0,0 +1,74 @@
+// Test setting the document language.
+
+--- text-lang ---
+// without any region
+#set text(font: "Noto Serif CJK TC", lang: "zh")
+#outline()
+
+--- text-lang-unknown-region ---
+// with unknown region configured
+#set text(font: "Noto Serif CJK TC", lang: "zh", region: "XX")
+#outline()
+
+--- text-lang-region ---
+// with region configured
+#set text(font: "Noto Serif CJK TC", lang: "zh", region: "TW")
+#outline()
+
+--- text-lang-hyphenate ---
+// Ensure that setting the language does have effects.
+#set text(hyphenate: true)
+#grid(
+ columns: 2 * (20pt,),
+ gutter: 1fr,
+ text(lang: "en")["Eingabeaufforderung"],
+ text(lang: "de")["Eingabeaufforderung"],
+)
+
+--- text-lang-shaping ---
+// Test that the language passed to the shaper has an effect.
+#set text(font: "Ubuntu")
+
+// Some lowercase letters are different in Serbian Cyrillic compared to other
+// Cyrillic languages. Since there is only one set of Unicode codepoints for
+// Cyrillic, these can only be seen when setting the language to Serbian and
+// selecting one of the few fonts that support these letterforms.
+Бб
+#text(lang: "uk")[Бб]
+#text(lang: "sr")[Бб]
+
+--- text-lang-script-shaping ---
+// Verify that writing script/language combination has an effect
+#{
+ set text(size:20pt)
+ set text(script: "latn", lang: "en")
+ [Ş ]
+ set text(script: "latn", lang: "ro")
+ [Ş ]
+ set text(script: "grek", lang: "ro")
+ [Ş ]
+}
+
+--- text-script-bad-type ---
+// Error: 19-23 expected string or auto, found none
+#set text(script: none)
+
+--- text-script-bad-value ---
+// Error: 19-23 expected three or four letter script code (ISO 15924 or 'math')
+#set text(script: "ab")
+
+--- text-lang-bad-type ---
+// Error: 17-21 expected string, found none
+#set text(lang: none)
+
+--- text-lang-bad-value ---
+// Error: 17-20 expected two or three letter language code (ISO 639-1/2/3)
+#set text(lang: "ӛ")
+
+--- text-lang-bad-value-emoji ---
+// Error: 17-20 expected two or three letter language code (ISO 639-1/2/3)
+#set text(lang: "😃")
+
+--- text-region-bad-value ---
+// Error: 19-24 expected two letter region code (ISO 3166-1 alpha-2)
+#set text(region: "hey")
diff --git a/tests/suite/text/lorem.typ b/tests/suite/text/lorem.typ
new file mode 100644
index 00000000..1524e2a3
--- /dev/null
+++ b/tests/suite/text/lorem.typ
@@ -0,0 +1,32 @@
+// Test blind text.
+
+--- lorem ---
+// Test basic call.
+#lorem(19)
+
+--- lorem-pars ---
+// Test custom paragraphs with user code.
+#set text(8pt)
+
+#{
+ let sentences = lorem(59)
+ .split(".")
+ .filter(s => s != "")
+ .map(s => s + ".")
+
+ let used = 0
+ for s in sentences {
+ if used < 2 {
+ used += 1
+ } else {
+ parbreak()
+ used = 0
+ }
+ s.trim()
+ [ ]
+ }
+}
+
+--- lorem-missing-words ---
+// Error: 2-9 missing argument: words
+#lorem()
diff --git a/tests/suite/text/raw.typ b/tests/suite/text/raw.typ
new file mode 100644
index 00000000..dce77fdb
--- /dev/null
+++ b/tests/suite/text/raw.typ
@@ -0,0 +1,630 @@
+// Test raw blocks.
+
+--- raw-empty ---
+// Empty raw block.
+Empty raw block:``.
+
+--- raw-consecutive-single-backticks ---
+// No extra space.
+`A``B`
+
+--- raw-typst-lang ---
+// Typst syntax inside.
+```typ #let x = 1``` \
+```typ #f(1)```
+
+--- raw-block-no-parbreaks ---
+// Multiline block splits paragraphs.
+
+Text
+```rust
+fn code() {}
+```
+Text
+
+--- raw-more-backticks ---
+// Lots of backticks inside.
+````
+```backticks```
+````
+
+--- raw-trimming ---
+// Trimming.
+
+// Space between "rust" and "let" is trimmed.
+The keyword ```rust let```.
+
+// Trimming depends on number backticks.
+(``) \
+(` untrimmed `) \
+(``` trimmed` ```) \
+(``` trimmed ```) \
+(``` trimmed```) \
+
+--- raw-single-backtick-lang ---
+// Single ticks should not have a language.
+`rust let`
+
+--- raw-dedent-first-line ---
+// First line is not dedented and leading space is still possible.
+ ``` A
+ B
+ C
+ ```
+
+--- raw-dedent-empty-line ---
+// Do not take empty lines into account when computing dedent.
+```
+ A
+
+ B
+```
+
+--- raw-dedent-last-line ---
+// Take last line into account when computing dedent.
+```
+ A
+
+ B
+ ```
+
+--- raw-tab-size ---
+#set raw(tab-size: 8)
+
+```tsv
+Year Month Day
+2000 2 3
+2001 2 1
+2002 3 10
+```
+
+--- raw-syntaxes ---
+#set page(width: 180pt)
+#set text(6pt)
+#set raw(syntaxes: "/assets/syntaxes/SExpressions.sublime-syntax")
+
+```sexp
+(defun factorial (x)
+ (if (zerop x)
+ ; with a comment
+ 1
+ (* x (factorial (- x 1)))))
+```
+
+
+--- raw-theme ---
+// Test code highlighting with custom theme.
+#set page(width: 180pt)
+#set text(6pt)
+#set raw(theme: "/assets/themes/halcyon.tmTheme")
+#show raw: it => {
+ set text(fill: rgb("a2aabc"))
+ rect(
+ width: 100%,
+ inset: (x: 4pt, y: 5pt),
+ radius: 4pt,
+ fill: rgb("1d2433"),
+ place(right, text(luma(240), it.lang)) + it,
+ )
+}
+
+```typ
+= Chapter 1
+#lorem(100)
+
+#let hi = "Hello World"
+#show heading: emph
+```
+
+--- raw-show-set ---
+// Text show rule
+#show raw: set text(font: "Roboto")
+`Roboto`
+
+--- raw-align-default ---
+// Text inside raw block should be unaffected by outer alignment by default.
+#set align(center)
+#set page(width: 180pt)
+#set text(6pt)
+
+#lorem(20)
+
+```py
+def something(x):
+ return x
+
+a = 342395823859823958329
+b = 324923
+```
+
+#lorem(20)
+
+--- raw-align-specified ---
+// Text inside raw block should follow the specified alignment.
+#set page(width: 180pt)
+#set text(6pt)
+
+#lorem(20)
+#align(center, raw(
+ lang: "typ",
+ block: true,
+ align: right,
+ "#let f(x) = x\n#align(center, line(length: 1em))",
+))
+#lorem(20)
+
+--- raw-align-invalid ---
+// Error: 17-20 expected `start`, `left`, `center`, `right`, or `end`, found top
+#set raw(align: top)
+
+--- raw-highlight-typ ---
+// LARGE
+#set page(width: auto)
+
+```typ
+#set hello()
+#set hello()
+#set hello.world()
+#set hello.my.world()
+#let foo(x) = x * 2
+#show heading: func
+#show module.func: func
+#show module.func: it => {}
+#foo(ident: ident)
+#hello
+#hello()
+#box[]
+#hello.world
+#hello.world()
+#hello().world()
+#hello.my.world
+#hello.my.world()
+#hello.my().world
+#hello.my().world()
+#{ hello }
+#{ hello() }
+#{ hello.world() }
+$ hello $
+$ hello() $
+$ box[] $
+$ hello.world $
+$ hello.world() $
+$ hello.my.world() $
+$ f_zeta(x), f_zeta(x)/1 $
+$ emph(hello.my.world()) $
+$ emph(hello.my().world) $
+$ emph(hello.my().world()) $
+$ #hello $
+$ #hello() $
+$ #hello.world $
+$ #hello.world() $
+$ #box[] $
+#if foo []
+```
+
+--- raw-highlight ---
+#set page(width: 180pt)
+#set text(6pt)
+#show raw: it => rect(
+ width: 100%,
+ inset: (x: 4pt, y: 5pt),
+ radius: 4pt,
+ fill: rgb(239, 241, 243),
+ place(right, text(luma(110), it.lang)) + it,
+)
+
+```typ
+= Chapter 1
+#lorem(100)
+
+#let hi = "Hello World"
+#show heading: emph
+```
+
+```rust
+/// A carefully designed state machine.
+#[derive(Debug)]
+enum State<'a> { A(u8), B(&'a str) }
+
+fn advance(state: State<'_>) -> State<'_> {
+ unimplemented!("state machine")
+}
+```
+
+```py
+import this
+
+def hi():
+ print("Hi!")
+```
+
+```cpp
+#include <iostream>
+
+int main() {
+ std::cout << "Hello, world!";
+}
+```
+
+```julia
+# Add two numbers
+function add(x, y)
+ return x * y
+end
+```
+
+ // Try with some indent.
+ ```html
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <h1>Topic</h1>
+ <p>The Hypertext Markup Language.</p>
+ <script>
+ function foo(a, b) {
+ return a + b + "string";
+ }
+ </script>
+ </body>
+ </html>
+ ```
+
+--- raw-inline-multiline ---
+#set page(width: 180pt)
+#set text(6pt)
+#set raw(lang:"python")
+
+Inline raws, multiline e.g. `for i in range(10):
+ # Only this line is a comment.
+ print(i)` or otherwise e.g. `print(j)`, are colored properly.
+
+Inline raws, multiline e.g. `
+# Appears blocky due to linebreaks at the boundary.
+for i in range(10):
+ print(i)
+` or otherwise e.g. `print(j)`, are colored properly.
+
+--- raw-blocky ---
+// Test various raw parsing edge cases.
+#let empty = (
+ name: "empty",
+ input: ``,
+ text: "",
+)
+
+#let backtick = (
+ name: "backtick",
+ input: ``` ` ```,
+ text: "`",
+ block: false,
+)
+
+#let lang-backtick = (
+ name: "lang-backtick",
+ input: ```js ` ```,
+ lang: "js",
+ text: "`",
+ block: false,
+)
+
+// The language tag stops on space
+#let lang-space = (
+ name: "lang-space",
+ input: ```js test ```,
+ lang: "js",
+ text: "test ",
+ block: false,
+)
+
+// The language tag stops on newline
+#let lang-newline = (
+ name: "lang-newline",
+ input: ```js
+test
+```,
+ lang: "js",
+ text: "test",
+ block: true,
+)
+
+// The first line and the last line are ignored
+#let blocky = (
+ name: "blocky",
+ input: {
+```
+test
+```
+},
+ text: "test",
+ block: true,
+)
+
+// A blocky raw should handle dedents
+#let blocky-dedent = (
+ name: "blocky-dedent",
+ input: {
+```
+ test
+ ```
+ },
+ text: "test",
+ block: true,
+)
+
+// When there is content in the first line, it should exactly eat a whitespace char.
+#let blocky-dedent-firstline = (
+ name: "blocky-dedent-firstline",
+ input: ``` test
+ ```,
+ text: "test",
+ block: true,
+)
+
+// When there is content in the first line, it should exactly eat a whitespace char.
+#let blocky-dedent-firstline2 = (
+ name: "blocky-dedent-firstline2",
+ input: ``` test
+```,
+ text: "test",
+ block: true,
+)
+
+// The first line is not affected by dedent, and the middle lines don't consider the whitespace prefix of the first line.
+#let blocky-dedent-firstline3 = (
+ name: "blocky-dedent-firstline3",
+ input: ``` test
+ test2
+ ```,
+ text: "test\n test2",
+ block: true,
+)
+
+// The first line is not affected by dedent, and the middle lines don't consider the whitespace prefix of the first line.
+#let blocky-dedent-firstline4 = (
+ name: "blocky-dedent-firstline4",
+ input: ``` test
+ test2
+ ```,
+ text: " test\ntest2",
+ block: true,
+)
+
+#let blocky-dedent-lastline = (
+ name: "blocky-dedent-lastline",
+ input: ```
+ test
+ ```,
+ text: " test",
+ block: true,
+)
+
+#let blocky-dedent-lastline2 = (
+ name: "blocky-dedent-lastline2",
+ input: ```
+ test
+ ```,
+ text: "test",
+ block: true,
+)
+
+#let blocky-tab = (
+ name: "blocky-tab",
+ input: {
+```
+ test
+```
+},
+ text: "\ttest",
+ block: true,
+)
+
+// This one is a bit problematic because there is a trailing tab below "test"
+// which the editor constantly wants to remove.
+#let blocky-tab-dedent = (
+ name: "blocky-tab-dedent",
+ input: eval("```\n\ttest\n \n ```"),
+ text: "test\n ",
+ block: true,
+)
+
+#let cases = (
+ empty,
+ backtick,
+ lang-backtick,
+ lang-space,
+ lang-newline,
+ blocky,
+ blocky-dedent,
+ blocky-dedent-firstline,
+ blocky-dedent-firstline2,
+ blocky-dedent-firstline3,
+ blocky-dedent-lastline,
+ blocky-dedent-lastline2,
+ blocky-tab,
+ blocky-tab-dedent,
+)
+
+#for c in cases {
+ assert.eq(c.text, c.input.text, message: "in point " + c.name + ", expect " + repr(c.text) + ", got " + repr(c.input.text) + "")
+ let block = c.at("block", default: false)
+ assert.eq(block, c.input.block, message: "in point " + c.name + ", expect " + repr(block) + ", got " + repr(c.input.block) + "")
+}
+
+--- raw-line ---
+#set page(width: 200pt)
+
+```rs
+fn main() {
+ println!("Hello, world!");
+}
+```
+
+#show raw.line: it => {
+ box(stack(
+ dir: ltr,
+ box(width: 15pt)[#it.number],
+ it.body,
+ ))
+ linebreak()
+}
+
+```rs
+fn main() {
+ println!("Hello, world!");
+}
+```
+
+--- raw-line-alternating-fill ---
+#set page(width: 200pt)
+#show raw: it => stack(dir: ttb, ..it.lines)
+#show raw.line: it => {
+ box(
+ width: 100%,
+ height: 1.75em,
+ inset: 0.25em,
+ fill: if calc.rem(it.number, 2) == 0 {
+ luma(90%)
+ } else {
+ white
+ },
+ align(horizon, stack(
+ dir: ltr,
+ box(width: 15pt)[#it.number],
+ it.body,
+ ))
+ )
+}
+
+```typ
+#show raw.line: block.with(
+ fill: luma(60%)
+);
+
+Hello, world!
+
+= A heading for good measure
+```
+
+--- raw-line-text-fill ---
+#set page(width: 200pt)
+#show raw.line: set text(fill: red)
+
+```py
+import numpy as np
+
+def f(x):
+ return x**2
+
+x = np.linspace(0, 10, 100)
+y = f(x)
+
+print(x)
+print(y)
+```
+
+--- raw-line-scripting ---
+
+// Test line extraction works.
+
+#show raw: code => {
+ for i in code.lines {
+ test(i.count, 10)
+ }
+
+ test(code.lines.at(0).text, "import numpy as np")
+ test(code.lines.at(1).text, "")
+ test(code.lines.at(2).text, "def f(x):")
+ test(code.lines.at(3).text, " return x**2")
+ test(code.lines.at(4).text, "")
+ test(code.lines.at(5).text, "x = np.linspace(0, 10, 100)")
+ test(code.lines.at(6).text, "y = f(x)")
+ test(code.lines.at(7).text, "")
+ test(code.lines.at(8).text, "print(x)")
+ test(code.lines.at(9).text, "print(y)")
+ test(code.lines.at(10, default: none), none)
+}
+
+```py
+import numpy as np
+
+def f(x):
+ return x**2
+
+x = np.linspace(0, 10, 100)
+y = f(x)
+
+print(x)
+print(y)
+```
+
+--- issue-3601-empty-raw ---
+// Test that empty raw block with `typ` language doesn't cause a crash.
+```typ
+```
+
+--- issue-3841-tabs-in-raw-type-code ---
+// Tab chars were not rendered in raw blocks with lang: "typ(c)"
+#raw("#if true {\n\tf()\t// typ\n}", lang: "typ")
+
+#raw("if true {\n\tf()\t// typc\n}", lang: "typc")
+
+```typ
+#if true {
+ // tabs around f()
+ f() // typ
+}
+```
+
+```typc
+if true {
+ // tabs around f()
+ f() // typc
+}
+```
+
+--- issue-2259-raw-color-overwrite ---
+// Test that the color of a raw block is not overwritten
+#show raw: set text(fill: blue)
+
+`Hello, World!`
+
+```rs
+fn main() {
+ println!("Hello, World!");
+}
+```
+
+--- issue-3191-raw-indent-shrink ---
+// Spaces in raw blocks should not be shrunk as it would mess up the indentation
+// of code.
+#set par(justify: true)
+
+#show raw.where(block: true): block.with(
+ fill: luma(240),
+ inset: 10pt,
+)
+
+#block(
+ width: 60%,
+ ```py
+ for x in xs:
+ print("x=",x)
+ ```
+)
+
+--- issue-3191-raw-normal-paragraphs-still-shrink ---
+// In normal paragraphs, spaces should still be shrunk.
+// The first line here serves as a reference, while the second
+// uses non-breaking spaces to create an overflowing line
+// (which should shrink).
+~~~~No shrinking here
+
+~~~~The~spaces~on~this~line~shrink
+
+--- raw-unclosed ---
+// Unterminated.
+// Error: 1-2:1 unclosed raw text
+`endless
diff --git a/tests/suite/text/shift.typ b/tests/suite/text/shift.typ
new file mode 100644
index 00000000..090f6ee8
--- /dev/null
+++ b/tests/suite/text/shift.typ
@@ -0,0 +1,19 @@
+// Test sub- and superscipt shifts.
+
+--- sub-super ---
+#table(
+ columns: 3,
+ [Typo.], [Fallb.], [Synth],
+ [x#super[1]], [x#super[5n]], [x#super[2 #box(square(size: 6pt))]],
+ [x#sub[1]], [x#sub[5n]], [x#sub[2 #box(square(size: 6pt))]],
+)
+
+--- sub-super-non-typographic ---
+#set super(typographic: false, baseline: -0.25em, size: 0.7em)
+n#super[1], n#sub[2], ... n#super[N]
+
+--- super-underline ---
+#set underline(stroke: 0.5pt, offset: 0.15em)
+#underline[The claim#super[\[4\]]] has been disputed. \
+The claim#super[#underline[\[4\]]] has been disputed. \
+It really has been#super(box(text(baseline: 0pt, underline[\[4\]]))) \
diff --git a/tests/suite/text/smallcaps.typ b/tests/suite/text/smallcaps.typ
new file mode 100644
index 00000000..6f977244
--- /dev/null
+++ b/tests/suite/text/smallcaps.typ
@@ -0,0 +1,3 @@
+--- smallcaps ---
+// Test smallcaps.
+#smallcaps[Smallcaps]
diff --git a/tests/suite/text/smartquote.typ b/tests/suite/text/smartquote.typ
new file mode 100644
index 00000000..28fcba5b
--- /dev/null
+++ b/tests/suite/text/smartquote.typ
@@ -0,0 +1,122 @@
+--- smartquote ---
+// LARGE
+#set page(width: 250pt)
+
+// Test simple quotations in various languages.
+#set text(lang: "en")
+"The horse eats no cucumber salad" was the first sentence ever uttered on the 'telephone.'
+
+#set text(lang: "de")
+"Das Pferd frisst keinen Gurkensalat" war der erste jemals am 'Fernsprecher' gesagte Satz.
+
+#set text(lang: "de", region: "CH")
+"Das Pferd frisst keinen Gurkensalat" war der erste jemals am 'Fernsprecher' gesagte Satz.
+
+#set text(lang: "es", region: none)
+"El caballo no come ensalada de pepino" fue la primera frase pronunciada por 'teléfono'.
+
+#set text(lang: "es", region: "MX")
+"El caballo no come ensalada de pepino" fue la primera frase pronunciada por 'teléfono'.
+
+#set text(lang: "fr", region: none)
+"Le cheval ne mange pas de salade de concombres" est la première phrase jamais prononcée au 'téléphone'.
+
+#set text(lang: "fi")
+"Hevonen ei syö kurkkusalaattia" oli ensimmäinen koskaan 'puhelimessa' lausuttu lause.
+
+#set text(lang: "gr")
+"Το άλογο δεν τρώει αγγουροσαλάτα" ήταν η πρώτη πρόταση που ειπώθηκε στο 'τηλέφωνο'.
+
+#set text(lang: "he")
+"הסוס לא אוכל סלט מלפפונים" היה המשפט ההראשון שנאמר ב 'טלפון'.
+
+#set text(lang: "ro")
+"Calul nu mănâncă salată de castraveți" a fost prima propoziție rostită vreodată la 'telefon'.
+
+#set text(lang: "ru")
+"Лошадь не ест салат из огурцов" - это была первая фраза, сказанная по 'телефону'.
+
+--- smartquote-empty ---
+// Test single pair of quotes.
+""
+
+--- smartquote-apostrophe ---
+// Test sentences with numbers and apostrophes.
+The 5'11" 'quick' brown fox jumps over the "lazy" dog's ear.
+
+He said "I'm a big fella."
+
+--- smartquote-escape ---
+// Test escape sequences.
+The 5\'11\" 'quick\' brown fox jumps over the \"lazy" dog\'s ear.
+
+--- smartquote-disable ---
+// Test turning smart quotes off.
+He's told some books contain questionable "example text".
+
+#set smartquote(enabled: false)
+He's told some books contain questionable "example text".
+
+--- smartquote-disabled-temporarily ---
+// Test changing properties within text.
+"She suddenly started speaking french: #text(lang: "fr")['Je suis une banane.']" Roman told me.
+
+Some people's thought on this would be #[#set smartquote(enabled: false); "strange."]
+
+--- smartquote-nesting ---
+// Test nested double and single quotes.
+"'test statement'" \
+"'test' statement" \
+"statement 'test'"
+
+--- smartquote-custom ---
+// Use language quotes for missing keys, allow partial reset
+#set smartquote(quotes: "«»")
+"Double and 'Single' Quotes"
+
+#set smartquote(quotes: (double: auto, single: "«»"))
+"Double and 'Single' Quotes"
+
+--- smartquote-custom-complex ---
+// Allow 2 graphemes
+#set smartquote(quotes: "a\u{0301}a\u{0301}")
+"Double and 'Single' Quotes"
+
+#set smartquote(quotes: (single: "a\u{0301}a\u{0301}"))
+"Double and 'Single' Quotes"
+
+--- smartquote-custom-bad-string ---
+// Error: 25-28 expected 2 characters, found 1 character
+#set smartquote(quotes: "'")
+
+--- smartquote-custom-bad-array ---
+// Error: 25-35 expected 2 quotes, found 4 quotes
+#set smartquote(quotes: ("'",) * 4)
+
+--- smartquote-custom-bad-dict ---
+// Error: 25-45 expected 2 quotes, found 4 quotes
+#set smartquote(quotes: (single: ("'",) * 4))
+
+--- issue-3662-pdf-smartquotes ---
+// Smart quotes were not appearing in the PDF outline, because they didn't
+// implement `PlainText`.
+= It's "Unnormal Heading"
+= It’s “Normal Heading”
+
+#set smartquote(enabled: false)
+= It's "Unnormal Heading"
+= It's 'single quotes'
+= It’s “Normal Heading”
+
+--- issue-1041-smartquotes-in-outline ---
+#set page(width: 15em)
+#outline()
+
+= "This" "is" "a" "test"
+
+--- issue-1540-smartquotes-across-newlines ---
+// Test that smart quotes are inferred correctly across newlines.
+"test"#linebreak()"test"
+
+"test"\
+"test"
diff --git a/tests/suite/text/space.typ b/tests/suite/text/space.typ
new file mode 100644
index 00000000..97541e38
--- /dev/null
+++ b/tests/suite/text/space.typ
@@ -0,0 +1,60 @@
+// Test whitespace handling.
+
+--- space-collapsing ---
+// Spacing around code constructs.
+A#let x = 1;B #test(x, 1) \
+C #let x = 2;D #test(x, 2) \
+E#if true [F]G \
+H #if true{"I"} J \
+K #if true [L] else []M \
+#let c = true; N#while c [#(c = false)O] P \
+#let c = true; Q #while c { c = false; "R" } S \
+T#for _ in (none,) {"U"}V
+#let foo = "A" ; \
+#foo;B \
+#foo; B \
+#foo ;B
+
+--- space-collapsing-comments ---
+// Test spacing with comments.
+A/**/B/**/C \
+A /**/ B/**/C \
+A /**/B/**/ C
+
+--- space-collapsing-with-h ---
+// Test spacing collapsing before spacing.
+#set align(right)
+A #h(0pt) B #h(0pt) \
+A B \
+A #h(-1fr) B
+
+--- text-font-just-a-space ---
+// Test that a run consisting only of whitespace isn't trimmed.
+A#text(font: "IBM Plex Serif")[ ]B
+
+--- text-font-change-after-space ---
+// Test font change after space.
+Left #text(font: "IBM Plex Serif")[Right].
+
+--- space-collapsing-linebreaks ---
+// Test that linebreak consumed surrounding spaces.
+#align(center)[A \ B \ C]
+
+--- space-collapsing-stringy-linebreak ---
+// Test that space at start of non-backslash-linebreak line isn't trimmed.
+A#"\n" B
+
+--- space-trailing-linebreak ---
+// Test that trailing space does not force a line break.
+LLLLLLLLLLLLLLLLLL R _L_
+
+--- space-ideographic-kept ---
+// Test that ideographic spaces are preserved.
+#set text(lang: "ja", font: "Noto Serif CJK JP")
+
+だろうか? 何のために! 私は、
+
+--- space-thin-kept ---
+// Test that thin spaces are preserved.
+| | U+0020 regular space \
+| | U+2009 thin space