Add files via upload

This commit is contained in:
Rekushi authored and GitHub committed 2026-10-29 11:59:16 +00:00
1 parent 9c7e2ae481
commit 404be3d6a5
95 files changed
+8163

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

+213
View File
@@ -0,0 +1,213 @@
(function (global) {
'use strict';
let data, bidi, loading;
const contains = value => /[\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb50-\ufdff\ufe70-\ufeff]/u.test(value);
function configure(value, factory) { data = value; bidi = factory(); }
function load() {
if (data && bidi) return Promise.resolve();
if (!loading) loading = Promise.all([
fetch('./assets/flipnote-editor/arabic-text.json?v=1.1').then(response => {
if (!response.ok) throw new Error('The Arabic text font could not load.');
return response.json();
}),
new Promise((resolve, reject) => {
if (global.bidi_js) { resolve(global.bidi_js); return; }
const script = document.createElement('script');
script.src = './assets/flipnote-editor/bidi.min.js?v=1.0.3';
script.onload = () => resolve(global.bidi_js);
script.onerror = reject;
document.head.append(script);
}),
]).then(([value, factory]) => configure(value, factory));
return loading;
}
// Joining forms follow arabic-reshaper's MIT-licensed algorithm.
// Keep the same forms, ligatures and mark handling as the PPM renderer.
function reshape(text) {
text = text.normalize('NFC');
const output = [], marks = new Map(), letters = data.letters;
for (const char of text) {
if (data.marks.includes(char)) {
const position = output.length - 1;
if (!marks.has(position)) marks.set(position, []);
marks.get(position).push(char);
} else if (!letters[char]) output.push([char, -1]);
else if (!output.length) output.push([char, 0]);
else {
const previous = output[output.length - 1];
const a = letters[char], b = letters[previous[0]];
if (previous[1] === -1 || !(a[3] || a[2]) || !(b[1] || b[2]) || (previous[1] === 3 && !b[2])) output.push([char, 0]);
else { previous[1] = previous[1] === 0 ? 1 : 2; output.push([char, 3]); }
}
if (output.length > 1 && output[output.length - 2][0] === '\u200d') output.splice(output.length - 2, 1);
}
if (output.length && output[output.length - 1][0] === '\u200d') output.pop();
const clean = Array.from(text).filter(char => !data.marks.includes(char)).join('');
for (const [word, forms] of data.ligatures) {
let start = clean.indexOf(word);
while (start !== -1) {
const end = start + word.length, first = output[start]?.[1], last = output[end - 1]?.[1];
const form = first === 0 || first === 1 ? (last === 0 || last === 3 ? 0 : 1) : (last === 0 || last === 3 ? 3 : 2);
if (forms[form] && output[start]) {
output[start] = [forms[form], -1];
for (let i = start + 1; i < end; i++) output[i] = ['', -1];
}
start = clean.indexOf(word, end);
}
}
return (marks.get(-1) || []).join('') + output.map(([char, form], index) => (char && form !== -1 && form !== 0 ? letters[char][form] : char) + (marks.get(index) || []).join('')).join('');
}
function visualClusters(text) {
const clusters = [];
for (const char of reshape(text)) {
if (data.marks.includes(char) && clusters.length) clusters[clusters.length - 1].marks += char;
else clusters.push({char, marks: ''});
}
const base = clusters.map(cluster => cluster.char).join('');
const levels = bidi.getEmbeddingLevels(base);
const order = bidi.getReorderedIndices(base, levels);
const display = bidi.getReorderedString(base, levels);
return order.map((index, position) => ({char: display[position], marks: clusters[index].marks}));
}
function visual(text) {
return text.split('\n').map(line => visualClusters(line).map(cluster => cluster.char + cluster.marks).join('')).join('\n');
}
function markGlyphs(marks) {
const glyphs = [];
for (let index = 0; index < marks.length;) {
const match = data.mark_ligatures.find(([text]) => marks.startsWith(text, index));
if (match) { glyphs.push(data.glyphs[match[1]]); index += match[0].length; }
else glyphs.push(data.glyphs[marks[index++]]);
}
return glyphs;
}
function placeMark(bitmap, base, previous) {
let position;
for (const lookup of data.positioning) {
const reference = lookup.marks[bitmap.name];
if (!reference) continue;
const targets = lookup.kind === 'mark' ? previous.slice().reverse() : [base];
for (const target of targets) {
let anchors = lookup.bases[target.bitmap.name];
if (!anchors) continue;
if (lookup.kind === 'ligature') anchors = anchors[0];
const anchor = anchors[reference[0]];
if (!anchor) continue;
position = {bitmap,
x: target.x + Math.round((anchor[0] - reference[1]) * data.pixel_scale),
y: target.y - Math.round((anchor[1] - reference[2]) * data.pixel_scale)};
break;
}
}
return position || {bitmap, x: base.x + Math.floor(base.bitmap.advance / 2), y: base.y};
}
function line(text) {
const glyphs = [];
let pen = 0;
for (const cluster of visualClusters(text)) {
if (cluster.char === '\u200c' || cluster.char === '\u200d') continue;
const bitmap = data.glyphs[cluster.char];
if (!bitmap) throw new Error(`The text fonts do not include “${cluster.char}”. Replace it to save this subtitle.`);
const base = {bitmap, x: pen, y: 0};
glyphs.push(base);
const marks = [];
for (const mark of markGlyphs(cluster.marks)) {
const placed = placeMark(mark, base, marks);
glyphs.push(placed); marks.push(placed);
}
pen += bitmap.advance;
}
let left = 0, right = pen, above = 11, below = 1;
for (const glyph of glyphs) {
for (const [_dy, dx, count] of glyph.bitmap.spans) {
left = Math.min(left, glyph.x + dx); right = Math.max(right, glyph.x + dx + count);
}
above = Math.max(above, -glyph.bitmap.top - glyph.y);
below = Math.max(below, glyph.bitmap.bottom + glyph.y);
}
if (left) glyphs.forEach(glyph => { glyph.x -= left; });
return {glyphs, advance: pen, width: right - left, above, height: Math.max(14, above + below + 2)};
}
function paint(mask, width, height, glyph, x, baseline) {
for (const [dy, dx, count] of glyph.spans) {
const y = baseline + dy;
if (y < 0 || y >= height) continue;
const start = Math.max(0, x + dx), end = Math.min(width, x + dx + count);
if (end > start) mask.fill(1, y * width + start, y * width + end);
}
}
function rasterize(glyphs, width, height) {
const mask = new Uint8Array(width * height);
for (const glyph of glyphs) paint(mask, width, height, glyph.bitmap, glyph.x, glyph.baseline);
return mask;
}
function wrap(text, width) {
const lines = [];
for (const paragraph of text.split('\n')) {
let current = '';
for (const word of paragraph.split(' ')) {
if (current && line((current + ' ' + word).trim()).width > width) { lines.push(current); current = ''; }
for (const char of graphemes((current ? ' ' : '') + word)) {
if (current && line(current + char).width > width) { lines.push(current.trimEnd()); current = ''; }
current += char;
}
}
lines.push(current.trim());
}
return lines;
}
function graphemes(text) {
const groups = [];
for (const char of text) {
if (groups.length && (/\p{M}/u.test(char) || char === '\u200c' || char === '\u200d')) groups[groups.length - 1] += char;
else groups.push(char);
}
return groups;
}
function layoutText(value, anchor, width, caretIndex = value.length) {
const glyphs = [];
const rtl = bidi.getEmbeddingLevels(value).paragraphs[0]?.level % 2 === 1;
const available = Math.max(1, rtl ? anchor.x : width - anchor.x);
function editorLines(text) {
const result = [];
for (const paragraph of text.replace(/\t/g, ' ').split('\n')) {
let current = '';
for (const char of graphemes(paragraph)) {
if (current && line(current + char).width > available) { result.push(current); current = ''; }
current += char;
}
result.push(current);
}
return result;
}
const lines = editorLines(value);
let top = anchor.y, caret = {x: anchor.x, y: top, height: 12};
const prefix = value.slice(0, caretIndex);
const before = editorLines(prefix);
lines.forEach((text, index) => {
const shaped = line(text);
let x = rtl ? anchor.x - shaped.width : anchor.x;
for (const placed of shaped.glyphs) glyphs.push({bitmap: placed.bitmap, x: x + placed.x, baseline: top + shaped.above + placed.y});
if (index === before.length - 1) {
const advance = line(before[index] || '').advance;
caret = {x: rtl ? anchor.x - advance : anchor.x + advance, y: top, height: shaped.height - 2};
}
top += shaped.height;
});
return {glyphs, caret, arabic: true};
}
function subtitleMask(value) {
const lines = wrap(value, 244);
if (lines.length > 2) throw new Error('Keep each subtitle to two lines. Split longer text into another subtitle.');
const shaped = lines.map(line), glyphs = [];
let top = 192 - 4 - shaped.reduce((sum, value) => sum + value.height, 0) + 1;
for (const value of shaped) {
let x = Math.floor((256 - value.width) / 2);
for (const placed of value.glyphs) glyphs.push({bitmap: placed.bitmap, x: x + placed.x, baseline: top + value.above + placed.y});
top += value.height;
}
return {mask: rasterize(glyphs, 256, 192), lines};
}
global.FlipnoteArabicText = {load, configure, contains, visual, line, layoutText, rasterize, subtitleMask};
})(typeof window !== 'undefined' ? window : globalThis);
+1
View File
@@ -0,0 +1 @@
{"version":2,"glyphs":{" ":{"name":"space","advance":5,"spans":[],"top":0,"bottom":0},"!":{"name":"exclam","advance":2,"spans":[[-9,0,1],[-8,0,1],[-7,0,1],[-6,0,1],[-5,0,1],[-4,0,1],[-2,0,1],[-1,0,1]],"top":-9,"bottom":0},"\"":{"name":"quotedbl","advance":6,"spans":[[-9,2,1],[-9,4,1],[-8,1,1],[-8,3,1],[-7,0,1],[-7,2,1]],"top":-9,"bottom":-6},"#":{"name":"numbersign","advance":8,"spans":[[-9,3,1],[-9,5,1],[-8,3,1],[-8,5,1],[-7,1,6],[-6,2,1],[-6,4,1],[-5,2,1],[-5,4,1],[-4,2,1],[-4,4,1],[-3,0,6],[-2,1,1],[-2,3,1],[-1,1,1],[-1,3,1]],"top":-9,"bottom":0},"%":{"name":"percent","advance":8,"spans":[[-9,1,1],[-9,5,1],[-8,0,1],[-8,2,1],[-8,5,1],[-7,0,1],[-7,2,1],[-7,4,1],[-6,1,1],[-6,4,1],[-5,3,1],[-4,2,1],[-4,5,1],[-3,2,1],[-3,4,1],[-3,6,1],[-2,1,1],[-2,4,1],[-2,6,1],[-1,1,1],[-1,5,1]],"top":-9,"bottom":0},"'":{"name":"quotesingle","advance":2,"spans":[[-9,0,1],[-8,0,1]],"top":-9,"bottom":-7},"(":{"name":"parenleft","advance":4,"spans":[[-9,2,1],[-8,1,1],[-7,1,1],[-6,0,1],[-5,0,1],[-4,0,1],[-3,1,1],[-2,1,1],[-1,2,1]],"top":-9,"bottom":0},")":{"name":"parenright","advance":4,"spans":[[-9,0,1],[-8,1,1],[-7,1,1],[-6,2,1],[-5,2,1],[-4,2,1],[-3,1,1],[-2,1,1],[-1,0,1]],"top":-9,"bottom":0},"*":{"name":"asterisk","advance":8,"spans":[[-8,3,1],[-7,0,1],[-7,3,1],[-7,6,1],[-6,1,1],[-6,3,1],[-6,5,1],[-5,2,3],[-4,1,1],[-4,3,1],[-4,5,1],[-3,0,1],[-3,3,1],[-3,6,1],[-2,3,1]],"top":-8,"bottom":-1},"+":{"name":"plus","advance":6,"spans":[[-7,2,1],[-6,2,1],[-5,0,5],[-4,2,1],[-3,2,1]],"top":-7,"bottom":-2},",":{"name":"comma","advance":3,"spans":[[-1,1,1],[0,0,1]],"top":-1,"bottom":1},"-":{"name":"hyphen","advance":6,"spans":[[-5,0,5]],"top":-5,"bottom":-4},".":{"name":"period","advance":2,"spans":[[-1,0,1]],"top":-1,"bottom":0},"/":{"name":"slash","advance":4,"spans":[[-9,2,1],[-8,2,1],[-7,2,1],[-6,1,1],[-5,1,1],[-4,1,1],[-3,0,1],[-2,0,1],[-1,0,1]],"top":-9,"bottom":0},"0":{"name":"zero","advance":6,"spans":[[-9,1,3],[-8,0,1],[-8,4,1],[-7,0,1],[-7,3,2],[-6,0,1],[-6,3,2],[-5,0,1],[-5,2,1],[-5,4,1],[-4,0,2],[-4,4,1],[-3,0,2],[-3,4,1],[-2,0,1],[-2,4,1],[-1,1,3]],"top":-9,"bottom":0},"1":{"name":"one","advance":3,"spans":[[-9,0,2],[-8,1,1],[-7,1,1],[-6,1,1],[-5,1,1],[-4,1,1],[-3,1,1],[-2,1,1],[-1,1,1]],"top":-9,"bottom":0},"2":{"name":"two","advance":6,"spans":[[-9,0,4],[-8,4,1],[-7,4,1],[-6,4,1],[-5,1,3],[-4,0,1],[-3,0,1],[-2,0,1],[-1,0,5]],"top":-9,"bottom":0},"3":{"name":"three","advance":6,"spans":[[-9,0,4],[-8,4,1],[-7,4,1],[-6,4,1],[-5,1,3],[-4,4,1],[-3,4,1],[-2,4,1],[-1,0,4]],"top":-9,"bottom":0},"4":{"name":"four","advance":7,"spans":[[-9,0,1],[-9,4,1],[-8,0,1],[-8,4,1],[-7,0,1],[-7,4,1],[-6,0,1],[-6,4,1],[-5,0,1],[-5,4,1],[-4,1,5],[-3,4,1],[-2,4,1],[-1,4,1]],"top":-9,"bottom":0},"5":{"name":"five","advance":6,"spans":[[-9,0,5],[-8,0,1],[-7,0,1],[-6,0,1],[-5,0,4],[-4,4,1],[-3,4,1],[-2,4,1],[-1,0,4]],"top":-9,"bottom":0},"6":{"name":"six","advance":6,"spans":[[-9,1,3],[-8,0,1],[-7,0,1],[-6,0,1],[-5,0,4],[-4,0,1],[-4,4,1],[-3,0,1],[-3,4,1],[-2,0,1],[-2,4,1],[-1,1,3]],"top":-9,"bottom":0},"7":{"name":"seven","advance":6,"spans":[[-9,0,5],[-8,4,1],[-7,4,1],[-6,3,1],[-5,2,1],[-4,2,1],[-3,2,1],[-2,2,1],[-1,2,1]],"top":-9,"bottom":0},"8":{"name":"eight","advance":6,"spans":[[-9,1,3],[-8,0,1],[-8,4,1],[-7,0,1],[-7,4,1],[-6,0,1],[-6,4,1],[-5,1,3],[-4,0,1],[-4,4,1],[-3,0,1],[-3,4,1],[-2,0,1],[-2,4,1],[-1,1,3]],"top":-9,"bottom":0},"9":{"name":"nine","advance":6,"spans":[[-9,1,3],[-8,0,1],[-8,4,1],[-7,0,1],[-7,4,1],[-6,0,1],[-6,4,1],[-5,1,4],[-4,4,1],[-3,4,1],[-2,4,1],[-1,1,3]],"top":-9,"bottom":0},":":{"name":"colon","advance":2,"spans":[[-7,0,1],[-3,0,1]],"top":-7,"bottom":-2},";":{"name":"semicolon","advance":3,"spans":[[-7,1,1],[-3,1,1],[-2,0,1]],"top":-7,"bottom":-1},"<":{"name":"less","advance":4,"spans":[[-7,2,1],[-6,1,1],[-5,0,1],[-4,1,1],[-3,2,1]],"top":-7,"bottom":-2},"=":{"name":"equal","advance":6,"spans":[[-6,0,5],[-4,0,5]],"top":-6,"bottom":-3},">":{"name":"greater","advance":4,"spans":[[-7,0,1],[-6,1,1],[-5,2,1],[-4,1,1],[-3,0,1]],"top":-7,"bottom":-2},"?":{"name":"question","advance":5,"spans":[[-9,0,3],[-8,3,1],[-7,3,1],[-6,2,1],[-5,1,1],[-4,1,1],[-2,1,1],[-1,1,1]],"top":-9,"bottom":0},"[":{"name":"bracketleft","advance":4,"spans":[[-9,0,3],[-8,0,1],[-7,0,1],[-6,0,1],[-5,0,1],[-4,0,1],[-3,0,1],[-2,0,1],[-1,0,3]],"top":-9,"bottom":0},"\\":{"name":"backslash","advance":4,"spans":[[-9,0,1],[-8,0,1],[-7,0,1],[-6,1,1],[-5,1,1],[-4,1,1],[-3,2,1],[-2,2,1],[-1,2,1]],"top":-9,"bottom":0},"]":{"name":"bracketright","advance":4,"spans":[[-9,0,3],[-8,2,1],[-7,2,1],[-6,2,1],[-5,2,1],[-4,2,1],[-3,2,1],[-2,2,1],[-1,0,3]],"top":-9,"bottom":0},"^":{"name":"asciicircum","advance":4,"spans":[[-9,1,1],[-8,0,1],[-8,2,1]],"top":-9,"bottom":-7},"_":{"name":"underscore","advance":6,"spans":[[-1,0,5]],"top":-1,"bottom":0},"{":{"name":"braceleft","advance":5,"spans":[[-9,2,2],[-8,1,1],[-7,1,1],[-6,1,1],[-5,0,1],[-4,1,1],[-3,1,1],[-2,1,1],[-1,2,2]],"top":-9,"bottom":0},"|":{"name":"bar","advance":2,"spans":[[-9,0,1],[-8,0,1],[-7,0,1],[-6,0,1],[-5,0Line truncated
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2021 Jason Johnston
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+1
View File
@@ -0,0 +1 @@
!function(r,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(r="undefined"!=typeof globalThis?globalThis:r||self).bidi_js=e()}(this,(function(){"use strict";return function(){return function(r){var e={R:"13k,1a,2,3,3,2+1j,ch+16,a+1,5+2,2+n,5,a,4,6+16,4+3,h+1b,4mo,179q,2+9,2+11,2i9+7y,2+68,4,3+4,5+13,4+3,2+4k,3+29,8+cf,1t+7z,w+17,3+3m,1t+3z,16o1+5r,8+30,8+mc,29+1r,29+4v,75+73",EN:"1c+9,3d+1,6,187+9,513,4+5,7+9,sf+j,175h+9,qw+q,161f+1d,4xt+a,25i+9",ES:"17,2,6dp+1,f+1,av,16vr,mx+1,4o,2",ET:"z+2,3h+3,b+1,ym,3e+1,2o,p4+1,8,6u,7c,g6,1wc,1n9+4,30+1b,2n,6d,qhx+1,h0m,a+1,49+2,63+1,4+1,6bb+3,12jj",AN:"16o+5,2j+9,2+1,35,ed,1ff2+9,87+u",CS:"18,2+1,b,2u,12k,55v,l,17v0,2,3,53,2+1,b",B:"a,3,f+2,2v,690",S:"9,2,k",WS:"c,k,4f4,1vk+a,u,1j,335",ON:"x+1,4+4,h+5,r+5,r+3,z,5+3,2+1,2+1,5,2+2,3+4,o,w,ci+1,8+d,3+d,6+8,2+g,39+1,9,6+1,2,33,b8,3+1,3c+1,7+1,5r,b,7h+3,sa+5,2,3i+6,jg+3,ur+9,2v,ij+1,9g+9,7+a,8m,4+1,49+x,14u,2+2,c+2,e+2,e+2,e+1,i+n,e+e,2+p,u+2,e+2,36+1,2+3,2+1,b,2+2,6+5,2,2,2,h+1,5+4,6+3,3+f,16+2,5+3l,3+81,1y+p,2+40,q+a,m+13,2r+ch,2+9e,75+hf,3+v,2+2w,6e+5,f+6,75+2a,1a+p,2+2g,d+5x,r+b,6+3,4+o,g,6+1,6+2,2k+1,4,2j,5h+z,1m+1,1e+f,t+2,1f+e,d+3,4o+3,2s+1,w,535+1r,h3l+1i,93+2,2s,b+1,3l+x,2v,4g+3,21+3,kz+1,g5v+1,5a,j+9,n+v,2,3,2+8,2+1,3+2,2,3,46+1,4+4,h+5,r+5,r+a,3h+2,4+6,b+4,78,1r+24,4+c,4,1hb,ey+6,103+j,16j+c,1ux+7,5+g,fsh,jdq+1t,4,57+2e,p1,1m,1m,1m,1m,4kt+1,7j+17,5+2r,d+e,3+e,2+e,2+10,m+4,w,1n+5,1q,4z+5,4b+rb,9+c,4+c,4+37,d+2g,8+b,l+b,5+1j,9+9,7+13,9+t,3+1,27+3c,2+29,2+3q,d+d,3+4,4+2,6+6,a+o,8+6,a+2,e+6,16+42,2+1i",BN:"0+8,6+d,2s+5,2+p,e,4m9,1kt+2,2b+5,5+5,17q9+v,7k,6p+8,6+1,119d+3,440+7,96s+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+1,1ekf+75,6p+2rz,1ben+1,1ekf+1,1ekf+1",NSM:"lc+33,7o+6,7c+18,2,2+1,2+1,2,21+a,1d+k,h,2u+6,3+5,3+1,2+3,10,v+q,2k+a,1n+8,a,p+3,2+8,2+2,2+4,18+2,3c+e,2+v,1k,2,5+7,5,4+6,b+1,u,1n,5+3,9,l+1,r,3+1,1m,5+1,5+1,3+2,4,v+1,4,c+1,1m,5+4,2+1,5,l+1,n+5,2,1n,3,2+3,9,8+1,c+1,v,1q,d,1f,4,1m+2,6+2,2+3,8+1,c+1,u,1n,g+1,l+1,t+1,1m+1,5+3,9,l+1,u,21,8+2,2,2j,3+6,d+7,2r,3+8,c+5,23+1,s,2,2,1k+d,2+4,2+1,6+a,2+z,a,2v+3,2+5,2+1,3+1,q+1,5+2,h+3,e,3+1,7,g,jk+2,qb+2,u+2,u+1,v+1,1t+1,2+6,9,3+a,a,1a+2,3c+1,z,3b+2,5+1,a,7+2,64+1,3,1n,2+6,2,2,3+7,7+9,3,1d+g,1s+3,1d,2+4,2,6,15+8,d+1,x+3,3+1,2+2,1l,2+1,4,2+2,1n+7,3+1,49+2,2+c,2+6,5,7,4+1,5j+1l,2+4,k1+w,2db+2,3y,2p+v,ff+3,30+1,n9x+3,2+9,x+1,29+1,7l,4,5,q+1,6,48+1,r+h,e,13+7,q+a,1b+2,1d,3+3,3+1,14,1w+5,3+1,3+1,d,9,1c,1g,2+2,3+1,6+1,2,17+1,9,6n,3,5,fn5,ki+f,h+f,r2,6b,46+4,1af+2,2+1,6+3,15+2,5,4m+1,fy+3,as+1,4a+a,4x,1j+e,1l+2,1e+3,3+1,1y+2,11+4,2+7,1r,d+1,1h+8,b+3,3,2o+2,3,2+1,7,4h,4+7,m+1,1m+1,4,12+6,4+4,5g+7,3+2,2,o,2d+5,2,5+1,2+1,6n+3,7+1,2+1,s+1,2e+7,3,2+1,2z,2,3+5,2,2u+2,3+3,2+4,78+8,2+1,75+1,2,5,41+3,3+1,5,x+5,3+1,15+5,3+3,9,a+5,3+2,1b+c,2+1,bb+6,2+5,2d+l,3+6,2+1,2+1,3f+5,4,2+1,2+6,2,21+1,4,2,9o+1,f0c+4,1o+6,t5,1s+3,2a,f5l+1,43t+2,i+7,3+6,v+3,45+2,1j0+1i,5+1d,9,f,n+4,2+e,11t+6,2+g,3+6,2+1,2+4,7a+6,c6+3,15t+6,32+6,gzhy+6n",AL:"16w,3,2,e+1b,z+2,2+2s,g+1,8+1,b+m,2+t,s+2i,c+e,4h+f,1d+1e,1bwe+dp,3+3z,x+c,2+1,35+3y,2rm+z,5+7,b+5,dt+l,c+u,17nl+27,1t+27,4x+6n,3+d",LRO:"6ct",RLO:"6cu",LRE:"6cq",RLE:"6cr",PDF:"6cs",LRI:"6ee",RLI:"6ef",FSI:"6eg",PDI:"6eh"},f={},a={};f.L=1,a[1]="L",Object.keys(e).forEach((function(r,e){f[r]=1<<e+1,a[f[r]]=r})),Object.freeze(f);var n=f.LRI|f.RLI|f.FSI,i=f.L|f.R|f.AL,v=f.B|f.S|f.WS|f.ON|f.FSI|f.LRI|f.RLI|f.PDI,o=f.BN|f.RLE|f.LRE|f.RLO|f.LRO|f.PDF,t=f.S|f.WS|f.B|n|f.PDI|o,u=null;function l(r){return function(){if(!u){u=new Map;var r=function(r){if(e.hasOwnProperty(r)){var a=0;e[r].split(",").forEach((function(e){var n=e.split("+"),i=n[0],v=n[1];i=parseInt(i,36),v=v?parseInt(v,36):0,u.set(a+=i,f[r]);for(var o=0;o<v;o++)u.set(++a,f[r])}))}};for(var a in e)r(a)}}(),u.get(r.codePointAt(0))||f.L}var c,d,b,s="14>1,1e>2,u>2,2wt>1,1>1,1ge>1,1wp>1,1j>1,f>1,hm>1,1>1,u>1,u6>1,1>1,+5,28>1,w>1,1>1,+3,b8>1,1>1,+3,1>3,-1>-1,3>1,1>1,+2,1s>1,1>1,x>1,th>1,1>1,+2,db>1,1>1,+3,3>1,1>1,+2,14qm>1,1>1,+1,4q>1,1e>2,u>2,2>1,+1",h="6f1>-6dx,6dy>-6dx,6ec>-6ed,6ee>-6ed,6ww>2jj,-2ji>2jj,14r4>-1e7l,1e7m>-1e7l,1e7m>-1e5c,1e5d>-1e5b,1e5c>-14qx,14qy>-14qx,14vn>-1ecg,1ech>-1ecg,1edu>-1ecg,1eci>-1ecg,1eda>-1ecg,1eci>-1ecg,1eci>-168q,168r>-168q,168s>-14ye,14yf>-14ye";function k(r,e){var f,a=0,n=new Map,i=e&&new Map;return r.split(",").forEach((function r(v){if(-1!==v.indexOf("+"))for(var o=+v;o--;)r(f);else{f=v;var t=v.split(">"),u=t[0],l=t[1];u=String.fromCodePoint(a+=parseInt(u,36)),l=String.fromCodePoint(a+=parseInt(l,36)),n.set(u,l),e&&i.set(l,u)}})),{map:n,reverseMap:i}}function m(){if(!c){var r=k(s,!0),e=r.map,f=r.reverseMap;c=e,d=f,b=k(h,!1).map}}function j(r){return m(),c.get(r)||null}function g(r){return m(),d.get(r)||null}function p(r){return m(),b.get(r)||null}var q=f.L,w=f.R,y=f.EN,x=f.ES,_=f.ET,M=f.AN,z=f.CS,I=f.B,L=f.S,S=f.ON,R=f.BN,O=f.NSM,E=f.AL,N=f.LRO,T=f.RLO,A=f.LRE,D=f.RLE,P=f.PDF,W=f.LRI,B=f.RLI,F=f.FSI,U=f.PDI;var C;function G(r){return function(){if(!C){var r=k("14>1,j>Line truncated
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+661
View File
@@ -0,0 +1,661 @@
(function (global) {
'use strict';
const WIDTH = 256;
const HEIGHT = 192;
const RNG_INITIAL_STATE = 0x1F01;
const PEN_NAMES = [
'1 px',
'Direction-aware 2 px',
'3×3 square',
'Squiggle',
'Directional =',
'Sparse airbrush',
'Jitter line',
'Dense spray',
'5×5 square',
'7×7 square',
'9×9 square',
'Dynamic-width pen',
'Hollow diamond',
'Large airbrush',
'Wide jitter line',
'Heavy spray',
];
const PAINT_NAMES = [
'Sparse 3×3 dots',
'Sparse 2×2 dots',
'Vertical stripes',
'Horizontal stripes',
'Checkerboard',
'Dense 2×2',
'Dense 3×3',
'Custom motif',
];
const FILL_NAMES = [
'Sparse 3×3 fill',
'Sparse 2×2 fill',
'Vertical stripe fill',
'Horizontal stripe fill',
'Checkerboard fill',
'Dense 2×2 fill',
'Dense 3×3 fill',
'Solid fill',
];
// The supplied ROM notes identify all eight eraser action slots and state
// that advanced erasing keeps the base geometry while filtering candidate
// pixels, but do not include the exact geometry/filter routines. These
// names/geometries preserve the restored editor's existing DSi palette
// until those remaining routines are ported from the ROM.
const ERASER_NAMES = [
'Pixel',
'3×3 square',
'Medium round',
'Large round',
'9×9 square',
'15×15 square',
'Vertical bar',
'Horizontal bar',
];
const AIRBRUSH_SMALL_MASK = [
'..#..',
'.###.',
'#####',
'.###.',
'..#..',
];
const AIRBRUSH_LARGE_MASK = [
'......###......',
'.....#####.....',
'...#########...',
'..###########..',
'.#############.',
'###############',
'###############',
'###############',
'###############',
'###############',
'.#############.',
'..###########..',
'...#########...',
'.....#####.....',
'......###......',
];
const RING_OUTER_MASK = [
'..###..',
'.#####.',
'#######',
'#######',
'#######',
'.#####.',
'..###..',
];
const RING_INNER_MASK = [
'..#..',
'.###.',
'#####',
'.###.',
'..#..',
];
let rngState = RNG_INITIAL_STATE;
function resetRandom(state) {
const next = Number(state);
rngState = Number.isFinite(next) ? (next & 0xffff) : RNG_INITIAL_STATE;
if (!rngState) rngState = RNG_INITIAL_STATE;
return rngState;
}
function getRandomState() {
return rngState & 0xffff;
}
function random16() {
const old = rngState & 0xffff;
const bit0 = old & 1;
const bit3 = (old >> 3) & 1;
rngState = old >> 1;
if (bit0 === bit3) rngState |= 0x8000;
rngState &= 0xffff;
return rngState;
}
function randomRange(range) {
const safeRange = Math.max(1, Math.floor(range));
return random16() % safeRange;
}
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function inBounds(x, y) {
return x >= 0 && y >= 0 && x < WIDTH && y < HEIGHT;
}
function indexOf(x, y) {
return y * WIDTH + x;
}
function writePixel(mask, x, y, value) {
if (!inBounds(x, y)) return;
mask[indexOf(x, y)] = value ? 1 : 0;
}
function setPixel(mask, x, y) {
writePixel(mask, x, y, 1);
}
function clearPixel(mask, x, y) {
writePixel(mask, x, y, 0);
}
function lineSteps(x0, y0, x1, y1, includeStart) {
let x = Math.round(x0);
let y = Math.round(y0);
const tx = Math.round(x1);
const ty = Math.round(y1);
const points = [];
const dx = Math.abs(tx - x);
const sx = x < tx ? 1 : -1;
const dyAbs = Math.abs(ty - y);
const sy = y < ty ? 1 : -1;
let err = dx - dyAbs;
let first = true;
let previousX = x;
let previousY = y;
while (true) {
if (includeStart || !first) {
points.push({
x,
y,
sx: first ? 0 : x - previousX,
sy: first ? 0 : y - previousY,
});
}
if (x === tx && y === ty) break;
first = false;
previousX = x;
previousY = y;
const e2 = 2 * err;
if (e2 > -dyAbs) {
err -= dyAbs;
x += sx;
}
if (e2 < dx) {
err += dx;
y += sy;
}
}
return points;
}
function stampSquare(mask, x, y, radius, value) {
for (let dy = -radius; dy <= radius; dy++) {
for (let dx = -radius; dx <= radius; dx++) {
writePixel(mask, x + dx, y + dy, value);
}
}
}
function stampMask(mask, rows, x, y, value) {
const height = rows.length;
const width = rows[0].length;
const ox = Math.floor(width / 2);
const oy = Math.floor(height / 2);
for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
if (rows[row][col] === '#') writePixel(mask, x + col - ox, y + row - oy, value);
}
}
}
function stampDirectionAware(mask, point, value) {
const x = point.x;
const y = point.y;
const sx = point.sx || 0;
const sy = point.sy || 0;
writePixel(mask, x, y, value);
writePixel(mask, x + sy, y, value);
writePixel(mask, x, y + sx, value);
}
function stampDirectionalEqual(mask, point) {
const x = point.x;
const y = point.y;
const sx = point.sx || 0;
const sy = point.sy || 0;
const horizontalMotion = sx !== 0 && sy === 0;
const verticalMotion = sy !== 0 && sx === 0;
if (horizontalMotion) {
setPixel(mask, x, y - 1);
setPixel(mask, x, y + 1);
} else if (verticalMotion) {
setPixel(mask, x - 1, y);
setPixel(mask, x + 1, y);
} else if ((sx === sy) || (sx === 0 && sy === 0)) {
setPixel(mask, x - 1, y);
setPixel(mask, x + 1, y);
} else {
setPixel(mask, x, y - 1);
setPixel(mask, x, y + 1);
}
clearPixel(mask, x, y);
if (Math.abs(sx) >= Math.abs(sy)) setPixel(mask, x, y + (sx || 1));
else setPixel(mask, x + (sy || 1), y);
}
function stampSquiggle(mask, point, state) {
const qx = point.x & ~1;
const qy = point.y & ~1;
const anchor = state.squiggleAnchor;
if (anchor && (anchor.x !== qx || anchor.y !== qy)) {
const mx = ((anchor.x + qx) >> 1) & ~1;
const my = ((anchor.y + qy) >> 1) & ~1;
stampSquare(mask, mx, my, 0, 1);
setPixel(mask, mx + 1, my);
setPixel(mask, mx, my + 1);
setPixel(mask, mx + 1, my + 1);
}
setPixel(mask, qx, qy);
setPixel(mask, qx + 1, qy);
setPixel(mask, qx, qy + 1);
setPixel(mask, qx + 1, qy + 1);
state.squiggleAnchor = {x: qx, y: qy};
}
function airbrushCandidatePass(segmentMagnitude, large) {
// The ROM consumes the LFSR while walking candidate pixels and folds
// segment delta into its skip logic. The supplied notes do not include
// the exact branch constants, so preserve both deterministic LFSR call
// order and motion sensitivity here pending the direct routine port.
const base = large ? 72 : 98;
const motionPenalty = Math.min(38, Math.max(0, segmentMagnitude - 1) * (large ? 2 : 4));
return (random16() & 0xff) < Math.max(24, base - motionPenalty);
}
function stampStochasticMask(mask, rows, point, segmentMagnitude) {
const height = rows.length;
const width = rows[0].length;
const ox = Math.floor(width / 2);
const oy = Math.floor(height / 2);
const large = width > 5;
for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
if (rows[row][col] !== '#') continue;
if (!airbrushCandidatePass(segmentMagnitude, large)) continue;
setPixel(mask, point.x + col - ox, point.y + row - oy);
}
}
}
function randomOffset(mode) {
return mode ? randomRange(10) - 5 : randomRange(5) - 2;
}
function drawOnePixelLine(mask, x0, y0, x1, y1) {
for (const point of lineSteps(x0, y0, x1, y1, true)) setPixel(mask, point.x, point.y);
}
function drawJitterSegment(mask, x0, y0, x1, y1, mode) {
const ax = clamp(Math.round(x0) + randomOffset(mode), 0, WIDTH - 1);
const ay = clamp(Math.round(y0) + randomOffset(mode), 0, HEIGHT - 1);
const bx = clamp(Math.round(x1) + randomOffset(mode), 0, WIDTH - 1);
const by = clamp(Math.round(y1) + randomOffset(mode), 0, HEIGHT - 1);
drawOnePixelLine(mask, ax, ay, bx, by);
}
function sprayClass(dx, dy, auxiliaryValue) {
const dynamic = Number(auxiliaryValue) || 0;
if (dynamic) return clamp(Math.ceil(Math.abs(dynamic) / 120), 1, 6);
const d2 = dx * dx + dy * dy;
if (d2 < 4) return 6;
if (d2 < 8) return 5;
if (d2 < 12) return 4;
if (d2 < 24) return 3;
if (d2 < 48) return 2;
return 1;
}
function drawComplexSpray(mask, x0, y0, x1, y1, mode, auxiliaryValue) {
const dx = x1 - x0;
const dy = y1 - y0;
const cls = sprayClass(dx, dy, auxiliaryValue);
const count = cls + (mode ? 3 : 2);
const spread = (7 - cls) + (mode ? 5 : 2);
for (let i = 0; i < count; i++) {
const scale = mode && (i & 1) ? 2 : 1;
const ax = clamp(Math.round(x0) + (randomRange(spread * 2 + 1) - spread) * scale, 0, WIDTH - 1);
const ay = clamp(Math.round(y0) + (randomRange(spread * 2 + 1) - spread) * scale, 0, HEIGHT - 1);
const bx = clamp(Math.round(x1) + (randomRange(spread * 2 + 1) - spread) * scale, 0, WIDTH - 1);
const by = clamp(Math.round(y1) + (randomRange(spread * 2 + 1) - spread) * scale, 0, HEIGHT - 1);
drawOnePixelLine(mask, ax, ay, bx, by);
}
}
function paintPattern(id, x, y, motif) {
switch (id & 7) {
case 0:
return x % 3 === 1 && y % 3 === 1;
case 1:
return (x & 1) === 0 && (y & 1) === 0;
case 2:
return (x & 1) === 1;
case 3:
return (y & 1) === 1;
case 4:
return ((x + y) & 1) === 1;
case 5:
return (x & 1) !== 0 || (y & 1) !== 0;
case 6:
return !(x % 3 === 1 && y % 3 === 1);
case 7:
if (motif && motif.data && motif.width && motif.height) {
const mx = ((x % motif.width) + motif.width) % motif.width;
const my = ((y % motif.height) + motif.height) % motif.height;
return !!motif.data[my * motif.width + mx];
}
// The supplied excerpt identifies slot 7 as a separate action
// but does not include its editor/capture routine. Keep the
// restored editor's neutral criss-cross motif until that ROM
// routine is supplied/ported.
return ((x - y) % 5 === 0) || ((x + y) % 5 === 0);
default:
return false;
}
}
function stampPaint(mask, logicalId, x, y, options) {
const baseId = logicalId & 7;
const expert = logicalId >= 8;
const paintState = Number(options && options.paintState);
const radius = paintState === 2 ? 3 : (expert ? 7 : 1);
for (let dy = -radius; dy <= radius; dy++) {
for (let dx = -radius; dx <= radius; dx++) {
const px = x + dx;
const py = y + dy;
if (!inBounds(px, py)) continue;
if (paintPattern(baseId, px, py, options && options.motif)) setPixel(mask, px, py);
}
}
}
// Bucket-style patterned fill. The connected transparent region is found
// from the pre-fill layer state so setting patterned pixels cannot split
// the traversal while it runs. Slots 0-6 reuse Paint 1-7's absolute-canvas
// predicates. Slot 7 is deliberately solid, per the editor UI spec.
function floodFillPattern(mask, slot, startX, startY, options) {
const sx = Math.round(startX);
const sy = Math.round(startY);
if (!inBounds(sx, sy)) return 0;
const startIndex = sy * WIDTH + sx;
// Normally the current layer is both the destination and the fill
// boundary. Shift-fill can supply the other layer as boundaryMask so
// its ink contains the flood while the current layer still receives
// the selected fill pattern/colour.
const suppliedBoundary = options && options.boundaryMask;
const boundaryMask = suppliedBoundary && suppliedBoundary.length === mask.length
? suppliedBoundary
: mask;
if (boundaryMask[startIndex]) return 0;
const original = new Uint8Array(boundaryMask);
const visited = new Uint8Array(WIDTH * HEIGHT);
const queueX = new Int16Array(WIDTH * HEIGHT);
const queueY = new Int16Array(WIDTH * HEIGHT);
let head = 0;
let tail = 0;
let changed = 0;
queueX[tail] = sx;
queueY[tail] = sy;
tail++;
visited[startIndex] = 1;
const patternId = clamp(Math.round(Number(slot) || 0), 0, 7);
const solid = patternId === 7;
const motif = options && options.motif;
while (head < tail) {
const x = queueX[head];
const y = queueY[head];
head++;
const index = y * WIDTH + x;
if (solid || paintPattern(patternId, x, y, motif)) {
if (!mask[index]) {
mask[index] = 1;
changed++;
}
}
if (x > 0) {
const n = index - 1;
if (!visited[n] && !original[n]) {
visited[n] = 1;
queueX[tail] = x - 1;
queueY[tail] = y;
tail++;
}
}
if (x + 1 < WIDTH) {
const n = index + 1;
if (!visited[n] && !original[n]) {
visited[n] = 1;
queueX[tail] = x + 1;
queueY[tail] = y;
tail++;
}
}
if (y > 0) {
const n = index - WIDTH;
if (!visited[n] && !original[n]) {
visited[n] = 1;
queueX[tail] = x;
queueY[tail] = y - 1;
tail++;
}
}
if (y + 1 < HEIGHT) {
const n = index + WIDTH;
if (!visited[n] && !original[n]) {
visited[n] = 1;
queueX[tail] = x;
queueY[tail] = y + 1;
tail++;
}
}
}
return changed;
}
function eraserContains(slot, dx, dy) {
switch (slot & 7) {
case 0: return dx === 0 && dy === 0;
case 1: return Math.abs(dx) <= 1 && Math.abs(dy) <= 1;
case 2: return dx * dx + dy * dy <= 12;
case 3: return dx * dx + dy * dy <= 42;
case 4: return Math.abs(dx) <= 4 && Math.abs(dy) <= 4;
case 5: return Math.abs(dx) <= 7 && Math.abs(dy) <= 7;
case 6: return Math.abs(dx) <= 1 && Math.abs(dy) <= 6;
case 7: return Math.abs(dy) <= 1 && Math.abs(dx) <= 6;
default: return false;
}
}
function eraserRadius(slot) {
return [0, 1, 3, 6, 4, 7, 6, 6][slot & 7];
}
function stampEraser(mask, logicalId, x, y, options) {
const slot = logicalId & 7;
const expert = logicalId >= 8;
const radius = eraserRadius(slot);
// Advanced Erase keeps the base Eraser geometry, but its candidate
// pixels use the most recently used Paint texture. Paint 5
// (checkerboard / pattern id 4) is the fallback until Paint has
// actually been used or selected in this editor session.
const requestedPattern = Number(options && options.eraserPatternId);
const eraserPatternId = Number.isFinite(requestedPattern)
? clamp(Math.round(requestedPattern), 0, 7)
: 4;
for (let dy = -radius; dy <= radius; dy++) {
for (let dx = -radius; dx <= radius; dx++) {
if (!eraserContains(slot, dx, dy)) continue;
const px = x + dx;
const py = y + dy;
if (expert && !paintPattern(eraserPatternId, px, py, options && options.motif)) continue;
clearPixel(mask, px, py);
}
}
}
function createStrokeState() {
return {
squiggleAnchor: null,
ringPrevious: null,
};
}
function stampPenPoint(mask, logicalId, point, state, segmentMagnitude, options) {
switch (logicalId) {
case 0:
setPixel(mask, point.x, point.y);
break;
case 1:
stampDirectionAware(mask, point, 1);
break;
case 2:
stampSquare(mask, point.x, point.y, 1, 1);
break;
case 3:
stampSquiggle(mask, point, state);
break;
case 4:
stampDirectionalEqual(mask, point);
break;
case 5:
stampStochasticMask(mask, AIRBRUSH_SMALL_MASK, point, segmentMagnitude);
break;
case 8:
stampSquare(mask, point.x, point.y, 2, 1);
break;
case 9:
stampSquare(mask, point.x, point.y, 3, 1);
break;
case 10:
stampSquare(mask, point.x, point.y, 4, 1);
break;
case 11: {
const aux = Number(options && options.auxiliaryValue) || 0;
const d2 = segmentMagnitude * segmentMagnitude;
let cls;
if (aux) cls = clamp(Math.ceil(Math.abs(aux) / 120), 1, 3);
else if (d2 < 4) cls = 3;
else if (d2 < 32) cls = 2;
else cls = 1;
if (cls === 1) setPixel(mask, point.x, point.y);
else if (cls === 2) stampDirectionAware(mask, point, 1);
else stampSquare(mask, point.x, point.y, 1, 1);
break;
}
case 12: {
const previous = state.ringPrevious || {x: point.x, y: point.y};
stampMask(mask, RING_OUTER_MASK, point.x, point.y, 1);
stampMask(mask, RING_INNER_MASK, previous.x, previous.y, 0);
state.ringPrevious = {x: point.x, y: point.y};
break;
}
case 13:
stampStochasticMask(mask, AIRBRUSH_LARGE_MASK, point, segmentMagnitude);
break;
default:
setPixel(mask, point.x, point.y);
break;
}
}
function applyStrokeStart(mask, kind, logicalId, x, y, state, options) {
const point = {x: Math.round(x), y: Math.round(y), sx: 0, sy: 0};
const strokeState = state || createStrokeState();
if (kind === 'paint') stampPaint(mask, logicalId, point.x, point.y, options);
else if (kind === 'eraser') stampEraser(mask, logicalId, point.x, point.y, options);
else if (logicalId === 6 || logicalId === 14) {
setPixel(mask, point.x, point.y);
} else if (logicalId === 7 || logicalId === 15) {
setPixel(mask, point.x, point.y);
} else {
stampPenPoint(mask, logicalId, point, strokeState, 0, options);
}
return strokeState;
}
function applyStrokeSegment(mask, kind, logicalId, x0, y0, x1, y1, state, options) {
const strokeState = state || createStrokeState();
const dx = Math.round(x1) - Math.round(x0);
const dy = Math.round(y1) - Math.round(y0);
const segmentMagnitude = Math.max(Math.abs(dx), Math.abs(dy));
if (kind === 'pen' && (logicalId === 6 || logicalId === 14)) {
drawJitterSegment(mask, x0, y0, x1, y1, logicalId === 14 ? 1 : 0);
return strokeState;
}
if (kind === 'pen' && (logicalId === 7 || logicalId === 15)) {
drawComplexSpray(mask, x0, y0, x1, y1, logicalId === 15 ? 1 : 0, options && options.auxiliaryValue);
return strokeState;
}
const points = lineSteps(x0, y0, x1, y1, false);
for (const point of points) {
if (kind === 'paint') stampPaint(mask, logicalId, point.x, point.y, options);
else if (kind === 'eraser') stampEraser(mask, logicalId, point.x, point.y, options);
else stampPenPoint(mask, logicalId, point, strokeState, segmentMagnitude, options);
}
return strokeState;
}
function logicalId(slot, expert) {
return clamp(Math.round(Number(slot) || 0), 0, 7) + (expert ? 8 : 0);
}
function toolName(kind, logical) {
const id = clamp(Math.round(Number(logical) || 0), 0, 15);
if (kind === 'paint') return PAINT_NAMES[id & 7];
if (kind === 'fill') return FILL_NAMES[id & 7];
if (kind === 'eraser') return ERASER_NAMES[id & 7];
return PEN_NAMES[id];
}
global.FlipnoteDrawingEngine = Object.freeze({
WIDTH,
HEIGHT,
RNG_INITIAL_STATE,
PEN_NAMES: PEN_NAMES.slice(),
PAINT_NAMES: PAINT_NAMES.slice(),
FILL_NAMES: FILL_NAMES.slice(),
ERASER_NAMES: ERASER_NAMES.slice(),
resetRandom,
getRandomState,
random16,
lineSteps,
paintPattern,
floodFillPattern,
logicalId,
toolName,
createStrokeState,
applyStrokeStart,
applyStrokeSegment,
});
})(typeof window !== 'undefined' ? window : globalThis);
Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 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.0 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: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B