TurboWarp extension
SVG Text
Responsive named styles and host-neutral plain/ruby text for TurboWarp.
Responsibility
SVG Text is a text provider. It defines text styles, computes host-neutral plain and ruby layout, creates and measures SVG text skins when a renderer is used, and releases the skins it owns. Bubble shape, tail, placement, portraits, and animation belong to the host package.
Kamishibai DSL 4.0 bundle
In the combined runtime palette, click the documentation button below the SVG Text member heading to return to this page. The two bundled blocks are the same style-definition and sprite-text blocks listed below; their member namespace and SVG Text icon identify their origin. Host-only measurement and lifecycle operations remain in the Composition API rather than the TurboWarp palette.
Installation
Install the Composition API with an exact version:
pnpm add --save-exact @kubohiroya/turbowarp-svg-text@0.8.1
The standalone extension is also available from jsDelivr:
https://cdn.jsdelivr.net/npm/@kubohiroya/turbowarp-svg-text@0.8.1/dist/svg-text.js
Host-neutral ruby layout
layoutRichText() accepts typed text and
ruby runs. Ruby base and reading rows have explicit
geometry and stay together as one wrapping and reveal unit. The layout
also returns deterministic plainText and
readingText projections.
const textLayout = createSvgTextLayoutComposition();
textLayout.defineStyle({
name: "dialogue-ruby",
backgroundColor: "transparent",
font: "Noto Sans JP",
rubyFontPercent: 50,
rubyGap: 1,
});
const layout = textLayout.layoutRichText({
styleName: "dialogue-ruby",
runs: [
{ type: "text", text: "海へ" },
{ type: "ruby", base: "出発", reading: "しゅっぱつ" },
],
nativeSize: [480, 360],
maxWidth: 240,
});
const svgNamespace = "http://www.w3.org/2000/svg";
const xmlNamespace = "http://www.w3.org/XML/1998/namespace";
const textElement = document.createElementNS(svgNamespace, "text");
textElement.setAttributeNS(xmlNamespace, "xml:space", "preserve");
textElement.setAttribute("fill", layout.style.textColor);
textElement.setAttribute("font-family", layout.style.font);
const appendGlyph = (glyph) => {
const tspan = document.createElementNS(svgNamespace, "tspan");
tspan.setAttribute("x", String(glyph.x));
tspan.setAttribute("y", String(glyph.baseline));
tspan.setAttribute("font-size", String(glyph.fontSize));
tspan.textContent = glyph.text;
textElement.append(tspan);
};
for (const line of layout.lines) {
for (const fragment of line.fragments) {
if (fragment.type === "text") {
appendGlyph({ ...fragment, fontSize: layout.style.fontSize });
} else {
appendGlyph(fragment.reading);
appendGlyph(fragment.base);
}
}
}
maxWidth includes padding. Text wraps at grapheme
boundaries, but a ruby group is never split. An over-wide atomic ruby
group grows the layout and sets overflow: true. Use
plainText for visible/backlog text and choose explicitly
whether accessibility or speech uses plainText or
readingText. Renderer-backed
setRichText() and measureRichText() use the
same layout. No HTML, SVG markup, DOM node, URL, or event handler is
accepted as content.
Blocks
define text style [STYLE] background [BACKGROUND] text [TEXT_COLOR]
font [FONT] size [SIZE] align [ALIGN]
Defines or replaces a named text style. Alignment is left, center, or right.
set this sprite text [TEXT] with style [STYLE]
Creates an SVG skin and applies it to the current sprite drawable.
Composition API
import { createSvgTextComposition } from "@kubohiroya/turbowarp-svg-text/composition";
const svgText = createSvgTextComposition({ runtime });
svgText.defineStyle({
name: "dialogue-text",
alignment: "left",
backgroundColor: "transparent",
font: "Noto Sans JP",
fontPercent: 100,
textColor: "#332200",
});
svgText.setText({ styleName: "dialogue-text", target, text: "The End" });
const width = svgText.measureText({ styleName: "dialogue-text", text: "The End" });
svgText.releaseTarget(target);
svgText.releaseAll();
measureText returns the maximum measured line width in
stage-relative pixels. The composition owns every skin passed to it.
Host-neutral text layout
createSvgTextLayoutComposition() requires no runtime,
renderer, skin, or drawable. Its synchronous
layoutText() receives a named style, text, and an
explicit renderer-native stage size. It returns deeply frozen
JSON-compatible data: overall width and height, the required
whitespace policy, normalized allowed style values, and each line's
text, width, horizontal position, and baseline.
import { createSvgTextLayoutComposition } from "@kubohiroya/turbowarp-svg-text/composition";
const textLayout = createSvgTextLayoutComposition();
textLayout.defineStyle({
name: "dialogue-text",
alignment: "center",
backgroundColor: "transparent",
font: "Noto Sans JP",
fontPercent: 100,
textColor: "#332200",
});
const layout = textLayout.layoutText({
styleName: "dialogue-text",
text: "First line\nSecond line",
nativeSize: [480, 360],
});
const svgNamespace = "http://www.w3.org/2000/svg";
const xmlNamespace = "http://www.w3.org/XML/1998/namespace";
const textElement = document.createElementNS(svgNamespace, "text");
if (layout.preserveWhitespace) {
textElement.setAttributeNS(xmlNamespace, "xml:space", "preserve");
}
textElement.setAttribute("fill", layout.style.textColor);
textElement.setAttribute("font-family", layout.style.font);
textElement.setAttribute("font-size", String(layout.style.fontSize));
for (const line of layout.lines) {
const tspan = document.createElementNS(svgNamespace, "tspan");
tspan.setAttribute("x", String(line.x));
tspan.setAttribute("y", String(line.baseline));
tspan.textContent = line.text;
textElement.append(tspan);
}
Apply preserveWhitespace and assign text with
textContent. The API accepts and returns no SVG markup,
DOM nodes, event handlers, URLs, or foreignObject. The
layout-only and renderer-backed paths share the same calculation for
whitespace, font, size, alignment, line height, colors, placement,
width, and height.
Standalone named-style handoff
From 0.8.1, the stock extension exposes a frozen
getLayoutCapability(). Its layoutText()
resolves the exact named-style registry populated by the
define text style block and returns host-neutral data
without creating a skin or drawable. Redefining a style changes
subsequent layout results without exposing the mutable registry.
const svgTextExtension = Scratch.vm.runtime.ext_kubohiroyasvgtext;
const textLayouts = svgTextExtension.getLayoutCapability();
const layout = textLayouts.layoutText({
styleName: "dialogue-text",
text: "Existing named style",
nativeSize: [480, 360],
});
Use with TurboWarp Bubble
TurboWarp Bubble can consume this package through its Text Capability. Bubble core does not depend on SVG Text, and a different text provider can be injected.
Plain host-neutral layout first ships in 0.6.0, typed
ruby layout in 0.8.0, and the stock named-style handoff
in 0.8.1.
Bubble #59
should pin 0.8.1 when its default overlay consumes styles
defined through the standalone extension.
TMPose Kamishibai #636
and other ruby consumers can use
>=0.8.0 <0.9.0. Neither downstream package is
imported here.