TurboWarp拡張
SVG Text
名前付きstyleによるhost-neutralなplain/ruby文字とSVGテキストアクター。
責務
SVG Textは文字providerです。文字styleの定義、host-neutralなplain/ruby layout計算、renderer使用時のSVG文字skinの生成・測定・更新・解放を担当します。 吹き出しの外枠、tail、配置、ポートレイト、アニメーションはhost側の責務です。
紙芝居DSL 4.0集約ランタイム
集約ランタイムのパレットでSVG Text見出し直下の ドキュメントボタンを押すと、このページを開けます。集約される2つのブロックは、 下記の文字style定義とsprite文字設定であり、拡張機能別の名前空間とSVG Textの アイコンで由来を識別できます。host専用の測定・ライフサイクル操作は TurboWarpパレットではなくComposition APIにあります。
インストール
Composition APIはversionを固定してインストールします。
pnpm add --save-exact @kubohiroya/turbowarp-svg-text@0.8.1
単体のTurboWarp拡張はjsDelivrからも読み込めます。
https://cdn.jsdelivr.net/npm/@kubohiroya/turbowarp-svg-text@0.8.1/dist/svg-text.js
Host-neutral ruby layout
layoutRichText()は型付きのtext/ruby
runを受け取ります。rubyのbaseとreadingは個別のgeometryを持ち、折返しとrevealでは1つの原子的な単位として扱います。layoutは確定的なplainText/readingText射影も返します。
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はpaddingを含みます。textはgrapheme境界で折り返しますが、ruby
groupは分割しません。原子的なrubyが幅を超える場合はlayoutを拡張してoverflow: trueを返します。表示/backlogにはplainTextを使い、accessibilityや読上げでplainText/readingTextのどちらを使うかはhostが明示的に選択します。renderer-backedのsetRichText()/measureRichText()も同じlayoutを使います。contentとしてHTML、SVG
markup、DOM node、URL、event handlerを受理しません。
ブロック
define text style [STYLE] background [BACKGROUND] text [TEXT_COLOR]
font [FONT] size [SIZE] align [ALIGN]
名前付き文字styleを定義または置換します。alignmentはleft、center、rightから選びます。
set this sprite text [TEXT] with style [STYLE]
現在のsprite drawableへSVG文字skinを適用します。
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はstage相対pixelで最大行幅を返します。compositionは渡されたskinを所有します。
Host-neutral文字layout
createSvgTextLayoutComposition()はruntime、renderer、skin、drawableを必要としません。
同期APIのlayoutText()へ名前付きstyle、文字列、明示的なrenderer
native stage sizeを渡すと、deep
freezeされたJSON互換dataを返します。dataには全体の幅・高さ、
必須の空白保持policy、正規化済みの許可style値、各行の文字列・幅・水平位置・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: "1行目\n2行目",
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);
}
preserveWhitespaceを適用し、文字はtextContentへ代入してください。このAPIはSVG
markup、DOM node、event
handler、URL、foreignObjectを入力・返却しません。layout-only経路とrenderer経路は、
空白、font、size、alignment、line
height、色、配置、幅、高さの計算を共有します。
Standalone named-style handoff
0.8.1からstock拡張はfrozenなgetLayoutCapability()を公開します。layoutText()はdefine text styleブロックが更新する同じnamed-style
registryを解決し、skin/drawableを生成せずhost-neutralなdataを返します。mutableなregistryは公開せず、style再定義は次回以降のlayout結果へ反映されます。
const svgTextExtension = Scratch.vm.runtime.ext_kubohiroyasvgtext;
const textLayouts = svgTextExtension.getLayoutCapability();
const layout = textLayouts.layoutText({
styleName: "dialogue-text",
text: "既存named style",
nativeSize: [480, 360],
});
TurboWarp Bubbleとの併用
TurboWarp Bubbleは Text Capability経由でこのpackageを利用できます。Bubble coreはSVG Textへ依存せず、別の文字providerも注入できます。
Plain host-neutral layoutは0.6.0、型付きruby
layoutは0.8.0、stock named-style
handoffは0.8.1で初めて提供します。
Bubble #59で既定overlayからstandalone拡張のstyleを使う場合は0.8.1へ固定します。
TMPose Kamishibai #636などのruby consumerは>=0.8.0 <0.9.0を使えます。どちらの下流packageもこのpackageからimportしません。