mirror of
https://github.com/remko/waforth
synced 2024-12-25 09:59:07 +01:00
thurtle: Add URL compression
This commit is contained in:
parent
41f3cf653c
commit
7135ea2865
2 changed files with 130 additions and 9 deletions
|
@ -1,6 +1,7 @@
|
|||
/////////////////////////////////////////////////////////////////////////
|
||||
// Query parameters:
|
||||
// `p`: Base64-encoded program
|
||||
// `u`: Base64-encoded ULZ-compressed program
|
||||
// `pn`: Program name. If `p` is not provided, looks up builtin example
|
||||
// `ar`: Auto-run program
|
||||
// `sn`: Show navbar (default: 1)
|
||||
|
@ -18,9 +19,12 @@ import {
|
|||
import Editor from "./Editor";
|
||||
import { saveAs } from "file-saver";
|
||||
import draw from "./draw";
|
||||
import { ulzEncode, ulzDecode } from "./ulz";
|
||||
|
||||
declare let bootstrap: any;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
async function b64encode(bs: Uint8Array, forURL?: boolean) {
|
||||
const url = await new Promise<string>((resolve) => {
|
||||
const reader = new FileReader();
|
||||
|
@ -46,6 +50,8 @@ function b64decode(s: string) {
|
|||
);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function parseQS(sqs = window.location.search) {
|
||||
const qs: Record<string, string> = {};
|
||||
const sqss = sqs
|
||||
|
@ -65,6 +71,8 @@ function parseQS(sqs = window.location.search) {
|
|||
|
||||
const qs = parseQS();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function About() {
|
||||
return (
|
||||
<>
|
||||
|
@ -604,13 +612,17 @@ async function downloadPNG(ev: MouseEvent) {
|
|||
async function share(ev: MouseEvent) {
|
||||
ev.preventDefault();
|
||||
shareModalTitleEl.innerText = programsEl.value;
|
||||
shareModalURLEl.value = `${window.location.protocol}//${
|
||||
window.location.host
|
||||
}${window.location.pathname}?pn=${encodeURIComponent(
|
||||
programsEl.value
|
||||
)}&p=${encodeURIComponent(
|
||||
await b64encode(new TextEncoder().encode(editor.getValue()), true)
|
||||
)}&ar=1`;
|
||||
let url = `${window.location.protocol}//${window.location.host}${
|
||||
window.location.pathname
|
||||
}?pn=${encodeURIComponent(programsEl.value)}&ar=1&`;
|
||||
const program = new TextEncoder().encode(editor.getValue());
|
||||
const compressed = ulzEncode(program);
|
||||
if (compressed.length < program.length) {
|
||||
url += `u=${encodeURIComponent(await b64encode(compressed, true))}`;
|
||||
} else {
|
||||
url += `p=${encodeURIComponent(await b64encode(program, true))}`;
|
||||
}
|
||||
shareModalURLEl.value = url;
|
||||
shareModal.show();
|
||||
}
|
||||
|
||||
|
@ -676,8 +688,9 @@ async function reset() {
|
|||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
if (qs.p) {
|
||||
saveProgram(qs.pn ?? "", new TextDecoder().decode(b64decode(qs.p)), true);
|
||||
if (qs.p || qs.u) {
|
||||
const program = qs.p ? b64decode(qs.p) : ulzDecode(b64decode(qs.u));
|
||||
saveProgram(qs.pn ?? "", new TextDecoder().decode(program), true);
|
||||
loadPrograms();
|
||||
loadProgram(qs.pn);
|
||||
} else {
|
||||
|
|
108
src/web/thurtle/ulz.ts
Normal file
108
src/web/thurtle/ulz.ts
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* ULZ ( http://wiki.xxiivv.com/site/ulz_format ) encoder & decoder
|
||||
*/
|
||||
|
||||
export function ulzDecode(src: Uint8Array) {
|
||||
const dst: Array<number> = [];
|
||||
let sp = 0;
|
||||
while (sp < src.length) {
|
||||
const c = src[sp++];
|
||||
if (c & 0x80) {
|
||||
// CPY
|
||||
let length;
|
||||
if (c & 0x40) {
|
||||
if (sp >= src.length) {
|
||||
throw new Error(`incomplete CPY2`);
|
||||
}
|
||||
length = ((c & 0x3f) << 8) | src[sp++];
|
||||
} else {
|
||||
length = c & 0x3f;
|
||||
}
|
||||
if (sp >= src.length) {
|
||||
throw new Error(`incomplete CPY`);
|
||||
}
|
||||
let cp = dst.length - (src[sp++] + 1);
|
||||
if (cp < 0) {
|
||||
throw new Error(`CPY underflow`);
|
||||
}
|
||||
for (let i = 0; i < length + 4; i++) {
|
||||
dst.push(dst[cp++]);
|
||||
}
|
||||
} else {
|
||||
// LIT
|
||||
if (sp + c >= src.length) {
|
||||
throw new Error(`LIT out of bounds: ${sp} + ${c} >= ${src.length}`);
|
||||
}
|
||||
for (let i = 0; i < c + 1; i++) {
|
||||
dst.push(src[sp++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Uint8Array(dst);
|
||||
}
|
||||
|
||||
const MIN_MAX_LENGTH = 4;
|
||||
|
||||
function findBestMatch(
|
||||
src: Uint8Array,
|
||||
sp: number,
|
||||
dlen: number,
|
||||
slen: number
|
||||
) {
|
||||
let bmlen = 0;
|
||||
let bmp = 0;
|
||||
let dp = sp - dlen;
|
||||
for (; dlen; dp++, dlen--) {
|
||||
let i = 0;
|
||||
for (; ; i++) {
|
||||
if (i == slen) {
|
||||
return [dp, i];
|
||||
}
|
||||
if (src[sp + i] != src[dp + (i % dlen)]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i > bmlen) {
|
||||
bmlen = i;
|
||||
bmp = dp;
|
||||
}
|
||||
}
|
||||
return [bmp, bmlen];
|
||||
}
|
||||
|
||||
export function ulzEncode(src: Uint8Array) {
|
||||
const dst: Array<number> = [];
|
||||
let sp = 0;
|
||||
let litp = -1;
|
||||
while (sp < src.length) {
|
||||
const dlen = Math.min(sp, 256);
|
||||
const slen = Math.min(src.length - sp, 0x3fff + MIN_MAX_LENGTH);
|
||||
const [bmp, bmlen] = findBestMatch(src, sp, dlen, slen);
|
||||
if (bmlen >= MIN_MAX_LENGTH) {
|
||||
// CPY
|
||||
const bmctl = bmlen - MIN_MAX_LENGTH;
|
||||
if (bmctl > 0x3f) {
|
||||
// CPY2
|
||||
dst.push((bmctl >> 8) | 0xc0);
|
||||
dst.push(bmctl & 0xff);
|
||||
} else {
|
||||
dst.push(bmctl | 0x80);
|
||||
}
|
||||
dst.push(sp - bmp - 1);
|
||||
sp += bmlen;
|
||||
litp = -1;
|
||||
} else {
|
||||
// LIT
|
||||
if (litp >= 0) {
|
||||
if ((dst[litp] += 1) == 127) {
|
||||
litp = -1;
|
||||
}
|
||||
} else {
|
||||
dst.push(0);
|
||||
litp = dst.length - 1;
|
||||
}
|
||||
dst.push(src[sp++]);
|
||||
}
|
||||
}
|
||||
return new Uint8Array(dst);
|
||||
}
|
Loading…
Reference in a new issue