Add files via upload

This commit is contained in:
Rekushi authored and GitHub committed 2026-10-29 12:01:51 +00:00
1 parent 404be3d6a5
commit 249ce5cb8e
90 files changed
+273

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+273
View File
@@ -0,0 +1,273 @@
(function (global) {
'use strict';
// ndsbios.ttf uses 1024 units/em and a 64-unit pixel grid: 16px is
// its native size. The alphabetic baseline is 11px; allow 14px per line.
const FONT_SIZE = 16;
const BASELINE = 11;
const LINE_HEIGHT = 14;
const FONT = '16px "Flipnote Text NDS BIOS"';
let fontPromise;
function loadFont() {
if (!fontPromise) {
const face = new FontFace('Flipnote Text NDS BIOS', 'url("./assets/fonts/ndsbios.ttf")');
const arabicFace = new FontFace('Flipnote Text Paxalah', 'url("./assets/fonts/paxalah.otf")');
const arabic = global.FlipnoteArabicText ? Promise.resolve() : new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = './assets/flipnote-editor/arabic-text.js?v=1.1';
script.onload = resolve; script.onerror = reject; document.head.append(script);
});
fontPromise = Promise.all([face, arabicFace].map(font => font.load().then(loaded => document.fonts.add(loaded))))
.then(() => arabic).then(() => global.FlipnoteArabicText.load());
}
return fontPromise;
}
function layoutText(value, anchor, width, measure, caretIndex = value.length) {
const glyphs = [];
let x = anchor.x, y = anchor.y, index = 0;
let caret = {x, y};
for (const character of value) {
if (character === '\n') {
x = anchor.x;
y += LINE_HEIGHT;
} else {
const text = character === '\t' ? ' ' : character;
const advance = Math.max(0, Math.round(measure(text)));
if (x > anchor.x && x + advance > width) {
x = anchor.x;
y += LINE_HEIGHT;
if (index === caretIndex) caret = {x, y};
}
glyphs.push({text, x, y});
x += advance;
}
index += character.length;
if (index <= caretIndex) caret = {x, y};
}
return {glyphs, caret};
}
function rasterize(context, glyphs, width = 256, height = 192) {
if (glyphs.some(glyph => glyph.bitmap)) return global.FlipnoteArabicText.rasterize(glyphs, width, height);
context.clearRect(0, 0, width, height);
context.font = FONT;
context.fontKerning = 'none';
context.textBaseline = 'alphabetic';
context.fillStyle = '#000';
for (const glyph of glyphs) {
if (glyph.y >= height) break;
context.fillText(glyph.text, glyph.x, glyph.y + BASELINE);
}
const pixels = context.getImageData(0, 0, width, height).data;
return Uint8Array.from({length: width * height}, (_, index) => pixels[index * 4 + 3] >= 128 ? 1 : 0);
}
function subtitleMask(value) {
if (global.FlipnoteArabicText.contains(value)) {
const result = global.FlipnoteArabicText.subtitleMask(value);
return {...result, ring: outline(result.mask)};
}
const surface = document.createElement('canvas');
surface.width = 256; surface.height = 192;
const context = surface.getContext('2d', {willReadFrequently: true});
context.font = FONT; context.fontKerning = 'none';
const measure = text => Array.from(text).reduce((sum, char) => sum + Math.round(context.measureText(char).width), 0);
const lines = [];
for (const paragraph of value.split('\n')) {
let line = '';
for (const word of paragraph.split(' ')) {
if (line && measure((line + ' ' + word).trim()) > 244) { lines.push(line); line = ''; }
for (const char of (line ? ' ' : '') + word) {
if (line && measure(line + char) > 244) { lines.push(line.trimEnd()); line = ''; }
line += char;
}
}
lines.push(line.trim());
}
if (lines.length > 2) throw new Error('Keep each subtitle to two lines. Split longer text into another subtitle.');
const top = 192 - 4 - 12 - (lines.length - 1) * LINE_HEIGHT;
const glyphs = [];
lines.forEach((line, index) => {
let x = Math.floor((256 - measure(line)) / 2);
for (const char of line) {
glyphs.push({text: char, x, y: top + index * LINE_HEIGHT});
x += measure(char);
}
});
const mask = rasterize(context, glyphs);
return {mask, ring: outline(mask), lines};
}
function outline(mask) {
const ring = new Uint8Array(mask.length);
for (let y = 0; y < 192; y++) for (let x = 0; x < 256; x++) {
if (!mask[y * 256 + x]) continue;
for (let yy = Math.max(0, y - 1); yy <= Math.min(191, y + 1); yy++)
for (let xx = Math.max(0, x - 1); xx <= Math.min(255, x + 1); xx++)
if (!mask[yy * 256 + xx]) ring[yy * 256 + xx] = 1;
}
return ring;
}
class FlipnoteTextTool {
constructor(editor, timeline, hooks) {
this.editor = editor;
this.timeline = timeline;
this.hooks = hooks;
this.enabled = false;
this.session = null;
this.request = 0;
this.fontLoaded = false;
this.fontError = false;
this.ready = loadFont().then(() => { this.fontLoaded = true; }).catch(() => { this.fontError = true; });
this.raster = document.createElement('canvas');
this.raster.width = editor.width;
this.raster.height = editor.height;
this.context = this.raster.getContext('2d', {willReadFrequently: true});
this.input = document.createElement('textarea');
this.input.className = 'flipnote-editor-text-input';
this.input.setAttribute('aria-label', 'Flipnote text. Press Escape to finish.');
this.input.setAttribute('dir', 'auto');
this.input.setAttribute('autocapitalize', 'off');
this.input.setAttribute('autocomplete', 'off');
this.input.setAttribute('autocorrect', 'off');
this.input.spellcheck = false;
this.input.maxLength = 4096;
this.input.tabIndex = -1;
document.body.append(this.input);
this.input.addEventListener('input', () => this.update());
this.input.addEventListener('keydown', (event) => {
event.stopPropagation();
if (event.key !== 'Escape' || event.isComposing) return;
event.preventDefault();
this.finish();
editor.canvasElement.focus({preventScroll: true});
});
this.input.addEventListener('keyup', () => this.resetCaret());
this.input.addEventListener('blur', () => this.finish());
document.addEventListener('selectionchange', () => {
if (document.activeElement === this.input) this.resetCaret();
});
document.addEventListener('pointerdown', (event) => {
if (event.target !== editor.canvasElement && event.target !== this.input) this.finish();
}, true);
}
toggle() {
if (this.enabled) {
this.disable();
} else {
this.editor.finishStroke();
this.editor.clearSelection();
this.enabled = true;
this.editor.updateCursorMode();
this.editor.renderCurrentFrame();
}
}
disable() {
this.finish();
this.enabled = false;
this.request++;
this.editor.updateCursorMode();
this.editor.renderCurrentFrame();
}
async begin(point) {
const request = ++this.request;
const frame = this.timeline.getFrame(this.timeline.currentFrame);
const layer = this.editor.activeLayer;
await this.ready;
if (!this.enabled || request !== this.request || !this.editor.allowEditing || this.timeline.playing
|| frame !== this.timeline.getFrame(this.timeline.currentFrame) || layer !== this.editor.activeLayer) return;
if (this.fontError) {
global.alert('The text fonts could not load. Reload the editor before using the Text tool.');
return;
}
this.finish();
this.session = {
frame, layer, base: frame.layers[layer].slice(), value: '',
anchor: {x: Math.max(0, Math.min(this.editor.width - 1, Math.floor(point.x))),
y: Math.max(0, Math.min(this.editor.height - 1, Math.floor(point.y)))},
};
this.input.value = '';
this.input.focus({preventScroll: true});
this.resetCaret();
}
layout() {
if (global.FlipnoteArabicText.contains(this.input.value)) {
return global.FlipnoteArabicText.layoutText(this.input.value, this.session.anchor, this.editor.width, this.input.selectionEnd);
}
this.context.font = FONT;
this.context.fontKerning = 'none';
return layoutText(this.input.value, this.session.anchor, this.editor.width,
(text) => this.context.measureText(text).width, this.input.selectionEnd);
}
update() {
const session = this.session;
if (!session) return;
const layer = session.frame.layers[session.layer];
const previous = layer.slice();
const ctx = this.context;
const layout = this.layout();
const mask = rasterize(ctx, layout.glyphs, this.editor.width, this.editor.height);
for (let i = 0; i < layer.length; i++) layer[i] = session.base[i] || mask[i];
if (!this.hooks.canStoreFrame(session.frame)) {
layer.set(previous);
this.input.value = session.value;
this.hooks.notifyFull();
} else {
session.value = this.input.value;
this.hooks.markDirty();
}
this.timeline.createThumbnails();
this.resetCaret();
}
resetCaret() {
this.caretStartedAt = Date.now();
this.editor.renderCurrentFrame();
}
renderCaret() {
if (!this.session || this.timeline.playing || document.activeElement !== this.input) return;
const caret = this.layout().caret;
const rect = this.editor.canvasElement.getBoundingClientRect();
this.input.style.left = `${rect.left + Math.min(caret.x, this.editor.width - 1) * rect.width / this.editor.width}px`;
this.input.style.top = `${rect.top + Math.min(caret.y, this.editor.height - 1) * rect.height / this.editor.height}px`;
if ((Date.now() - this.caretStartedAt) % 1000 >= 500 || caret.y >= this.editor.height) return;
const ctx = this.editor.ctx;
ctx.save();
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'difference';
ctx.fillStyle = '#fff';
ctx.fillRect(Math.max(0, Math.min(caret.x, this.editor.width - 1)), caret.y, 1, caret.height || BASELINE + 1);
ctx.restore();
}
finish() {
this.request++;
const session = this.session;
if (!session) return;
this.session = null;
const layer = session.frame.layers[session.layer];
const changed = layer.some((pixel, i) => pixel !== session.base[i]);
if (changed) {
const index = this.timeline.frames.indexOf(session.frame);
if (index >= 0) this.timeline.updateFrame(index);
}
if (document.activeElement === this.input) this.input.blur();
this.editor.renderCurrentFrame();
}
}
FlipnoteTextTool.layoutText = layoutText;
FlipnoteTextTool.loadFont = loadFont;
FlipnoteTextTool.subtitleMask = subtitleMask;
FlipnoteTextTool.metrics = {fontSize: FONT_SIZE, baseline: BASELINE, lineHeight: LINE_HEIGHT};
global.FlipnoteTextTool = FlipnoteTextTool;
})(typeof window !== 'undefined' ? window : globalThis);
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB