mirror of
https://github.com/remko/waforth
synced 2024-12-27 09:59:29 +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:
|
// Query parameters:
|
||||||
// `p`: Base64-encoded program
|
// `p`: Base64-encoded program
|
||||||
|
// `u`: Base64-encoded ULZ-compressed program
|
||||||
// `pn`: Program name. If `p` is not provided, looks up builtin example
|
// `pn`: Program name. If `p` is not provided, looks up builtin example
|
||||||
// `ar`: Auto-run program
|
// `ar`: Auto-run program
|
||||||
// `sn`: Show navbar (default: 1)
|
// `sn`: Show navbar (default: 1)
|
||||||
|
@ -18,9 +19,12 @@ import {
|
||||||
import Editor from "./Editor";
|
import Editor from "./Editor";
|
||||||
import { saveAs } from "file-saver";
|
import { saveAs } from "file-saver";
|
||||||
import draw from "./draw";
|
import draw from "./draw";
|
||||||
|
import { ulzEncode, ulzDecode } from "./ulz";
|
||||||
|
|
||||||
declare let bootstrap: any;
|
declare let bootstrap: any;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
async function b64encode(bs: Uint8Array, forURL?: boolean) {
|
async function b64encode(bs: Uint8Array, forURL?: boolean) {
|
||||||
const url = await new Promise<string>((resolve) => {
|
const url = await new Promise<string>((resolve) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
@ -46,6 +50,8 @@ function b64decode(s: string) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
function parseQS(sqs = window.location.search) {
|
function parseQS(sqs = window.location.search) {
|
||||||
const qs: Record<string, string> = {};
|
const qs: Record<string, string> = {};
|
||||||
const sqss = sqs
|
const sqss = sqs
|
||||||
|
@ -65,6 +71,8 @@ function parseQS(sqs = window.location.search) {
|
||||||
|
|
||||||
const qs = parseQS();
|
const qs = parseQS();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
function About() {
|
function About() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -604,13 +612,17 @@ async function downloadPNG(ev: MouseEvent) {
|
||||||
async function share(ev: MouseEvent) {
|
async function share(ev: MouseEvent) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
shareModalTitleEl.innerText = programsEl.value;
|
shareModalTitleEl.innerText = programsEl.value;
|
||||||
shareModalURLEl.value = `${window.location.protocol}//${
|
let url = `${window.location.protocol}//${window.location.host}${
|
||||||
window.location.host
|
window.location.pathname
|
||||||
}${window.location.pathname}?pn=${encodeURIComponent(
|
}?pn=${encodeURIComponent(programsEl.value)}&ar=1&`;
|
||||||
programsEl.value
|
const program = new TextEncoder().encode(editor.getValue());
|
||||||
)}&p=${encodeURIComponent(
|
const compressed = ulzEncode(program);
|
||||||
await b64encode(new TextEncoder().encode(editor.getValue()), true)
|
if (compressed.length < program.length) {
|
||||||
)}&ar=1`;
|
url += `u=${encodeURIComponent(await b64encode(compressed, true))}`;
|
||||||
|
} else {
|
||||||
|
url += `p=${encodeURIComponent(await b64encode(program, true))}`;
|
||||||
|
}
|
||||||
|
shareModalURLEl.value = url;
|
||||||
shareModal.show();
|
shareModal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -676,8 +688,9 @@ async function reset() {
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
if (qs.p) {
|
if (qs.p || qs.u) {
|
||||||
saveProgram(qs.pn ?? "", new TextDecoder().decode(b64decode(qs.p)), true);
|
const program = qs.p ? b64decode(qs.p) : ulzDecode(b64decode(qs.u));
|
||||||
|
saveProgram(qs.pn ?? "", new TextDecoder().decode(program), true);
|
||||||
loadPrograms();
|
loadPrograms();
|
||||||
loadProgram(qs.pn);
|
loadProgram(qs.pn);
|
||||||
} else {
|
} 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