Decimal Digit OCR 🤖

Write a number from 0 to 9 using primaryPath and/or secondaryPath and see if this turtle can handle your handwriting.

If not recognized correctly, please add the paths to the comments and I will add it to the 'learning' set, although it is in no way (machine) learning.

This is a naive implementation that compares characters by the pixel after normalising and sampling paths. No data on how a path is drawn or direction of a path is used in the comparison.

(I added tertiaryPath in case you - for whatever reason - need a third path for a digit)

Log in to post a comment.

// Naive Decimal Digit OCR. Created by Jurgen Westerhof 2024
// The MIT License
//
// https://turtletoy.net/turtle/81b04ff7e6
//
const display = 0; //min=0 max=2 step=1 (raw, normalized, grid)
const primaryPath = 'M-1,-44 C-1,-50 25,-57 28,-54 C31,-51 37,-49 41,-44 C44,-40 44,-34 45,-29 C47,-15 45,6 39,15 C36,19 24,17 20,17 C20,17 11,17 12,18 C14,20 23,17 26,18 C38,20 46,27 52,38 C54,42 52,49 51,53 C50,57 50,64 48,66 C37,77 -5,76 -5,59'; // type=path, bbox=-70,-100,170,200 Click here to redraw the path
const secondaryPath = 'M0,0';// type=path, bbox=-70,-100,170,200 Click here to redraw the path
const tertiaryPath = 'M0,0';// type=path, bbox=-70,-100,170,200 Click here to redraw the path

const turtle = new Turtle();
const text = new Text();

const grid = pixelize(primaryPath, secondaryPath, tertiaryPath, display);
const defs = getCharacterDefinitions().flatMap((v, i) => v.map((p, ii) => [[i, ii], p]));

const results = [];

function walk(i) {
    if(i == defs.length) {
        const winner = results.sort((a, b) => a[1] < b[1]? 1: -1).shift()[0][0];

        turtle.jump(-95, 85);
        text.print(turtle, '' + winner, 1);
        
        return false;
    }
    
    const test = defs[i];
    while(test[1].length < 3) {
        test[1].push('M0,0');
    }
    const testGrid = pixelize(...test[1]);
    
    const match = grid.reduce((a, c, x) => a + grid[x].reduce((a, c, i) => a + (c === testGrid[x][i]? 1: -1), 0), 0) / (grid.length * grid[0].length);

    results.push([test[0], Math.max(0, match)]);

    turtle.jump(-99, -97 + 5 * i);
    if(i < 33) {
        text.print(turtle, test[0][0] + '.' + (test[0][1] + 1) + ': ' + (Math.round(match * 10000)/10000), .13);
    } else if(i == 33) {
        text.print(turtle, '...', .13);
    }

    turtle.jump(-98 + 196 * i / defs.length, 99);
    turtle.goto(-98 + 196 * ((i+1) / defs.length), 99);

    return true;
}

function pixelize(primaryPath, secondaryPath, tertiaryPath, display = null) {
    //Although a new turtle, code in this function first was in walk(i) to do display things for one input character. But I now need to compare with more characters so I need to process more (predefined) paths and I didn't bother rewriting it properly get around the i-variable... so here it goes... ugliness
    const [paths, pathLengths, centerDelta, ratio] = getPathInfo(primaryPath, secondaryPath, tertiaryPath, display);

    const pts = [];
    
    for(let i = 0, max = pathLengths.reduce((a,c)=>a+c,0) + pathLengths.length - 1; i < max; i++) {
        const [pathIndex, step] = pathLengths.map(s => s + 1).reduce((a, c, idx) => a[2]? a: (a[1] < c? [idx, a[1], true]: [idx, a[1] - c, false]), [0, i, false]).slice(0,2);
    
        const raw = paths[pathIndex].p( step/pathLengths[pathIndex] );
        const normalized = scale2(add2(centerDelta, raw), ratio);
        
        pts.push(normalized.map(c => c | 0));

        if(display === 0 || display === 1) {
            if(step == 0) {
                turtle.jump(display == 0? raw: normalized);
            } else {
                turtle.goto(display == 0? raw: normalized);
            }
        }
    }
    
    //fill a grid slightly bigger than 85x100, so 95x110 with set or unset pixels by the path
    const gridSize = [95, 110];
    const grid = Array.from({length: gridSize[0]}).map((v, col) => Array.from({length: gridSize[1]}).map((v, row) => 
        pts.some(pt => col - 5 < pt[0] + 47 && pt[0] + 47 < col + 5 && row - 5 < pt[1] + 55 && pt[1] + 55 < row + 5)
    ));
    //console.log(grid);
    
    if(display == 2) {
        grid.forEach((v, col) => v.forEach((v, row) => {
            if(v) {
                turtle.jump(col - 47, row - 55 - .4);
                turtle.circle(.4);
            }
        }));
    }
    
    return grid;
}

function getPathInfo(primaryPath, secondaryPath, tertiaryPath, display) {
    //Although a new turtle, code in this function first was in in global scope to have walk(i) use it... so here it goes... ugliness with return variables
    const paths = [primaryPath, secondaryPath, tertiaryPath].filter(p => p != 'M0,0').map(p => Path(p.match(/([0-9.-]+|[MLC])/g)));
    
    const bb = paths.map(p => p.bb()).reduce((a, c) => 
        [[Math.min(a[0][0], c[0][0]), Math.min(a[0][1], c[0][1])], [Math.max(a[1][0], c[1][0]), Math.max(a[1][1], c[1][1])]]
    , [[Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER], [Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER]]);

    //constrain everything to a 85x100 box
    const ratio = Math.min(85 / (bb[1][0] - bb[0][0]), 100 / (bb[1][1] - bb[0][1]));
    const centerDelta = scale2(lerp2(bb[0], bb[1], .5), -1);

    const normalizedPaths = [primaryPath, secondaryPath, tertiaryPath].filter(p => p != 'M0,0').map(p => Path( scaleTokens(p.match(/([0-9.-]+|[MLC])/g), ratio)));
    const pathLengths = normalizedPaths.map(p => p.length() | 0);
    
    return [paths, pathLengths, centerDelta, ratio];
}

function scaleTokens(a, s) {
    return a.map((token, index) => {
        if (isNumber(token)) {
            return token * s;
        } else return token;
    });
}

function isNumber(n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
}

function add2(a, b) { return [a[0]+b[0], a[1]+b[1]]; }
function scale2(a, s) { return [a[0]*s,a[1]*s]; }
function lerp2(a,b,t) { return [a[0]*(1-t) + b[0]*t, a[1]*(1-t) + b[1]*t]; }


////////////////////////////////////////////////////////////////
// Modified path utility code. Created by Reinder Nijhoff 2023
// Parses a single SVG path (only M, C and L statements are
// supported). The p-method will return
// [...position, ...derivative] for a normalized point t.
//
// https://turtletoy.net/turtle/46adb0ad70
//
// Modified by Jurgen Westerhof 2024, added bb() and size()
////////////////////////////////////////////////////////////////
function Path(tokens) {
    class MoveTo {
        constructor(p) { this.p0 = p; }
        p(t, s) { return [...this.p0, 1, 0]; }
        length() { return 0; }
    }
    class LineTo {
        constructor(p0, p1) { this.p0 = p0, this.p1 = p1; }
        p(t, s = 1) {
            const nt = 1 - t, p0 = this.p0, p1 = this.p1;
            return [ 
                nt*p0[0] + t*p1[0],
                nt*p0[1] + t*p1[1],
                (p1[0] - p0[0]) * s,
                (p1[1] - p0[1]) * s,
            ];
        }
        length() { 
            const p0 = this.p0, p1 = this.p1;
            return Math.hypot(p0[0]-p1[0], p0[1]-p1[1]);
        }
    }
    class BezierTo {
        constructor(p0, c0, c1, p1) { this.p0 = p0, this.c0 = c0, this.c1 = c1, this.p1 = p1; }
        p(t, s = 1) {
            const nt = 1 - t, p0 = this.p0, c0 = this.c0, c1 = this.c1, p1 = this.p1;
            return [ 
                nt*nt*nt*p0[0] + 3*t*nt*nt*c0[0] + 3*t*t*nt*c1[0] + t*t*t*p1[0],
                nt*nt*nt*p0[1] + 3*t*nt*nt*c0[1] + 3*t*t*nt*c1[1] + t*t*t*p1[1],
                (3*nt*nt*(c0[0]-p0[0]) + 6*t*nt*(c1[0]-c0[0]) + 3*t*t*(p1[0]-c1[0])) * s,
                (3*nt*nt*(c0[1]-p0[1]) + 6*t*nt*(c1[1]-c0[1]) + 3*t*t*(p1[1]-c1[1])) * s,
            ];
        }
        length() {
            return this._length || (
                this._length = Array.from({length:25}, (x, i) => this.p(i/25)).reduce( 
                    (a,c,i,v) => i > 0 ? a + Math.hypot(c[0]-v[i-1][0], c[1]-v[i-1][1]) : a, 0));
        }
    }
    class Path {
        constructor(tokens) {
            this.segments = [];
            this.parsePath(tokens);
        }
        parsePath(t) {
            for (let s, i=0; i<t.length;) {
                switch (t[i++]) {
                    case 'M': this.add(new MoveTo(s=[t[i++],t[i++]]));
                              break;
                    case 'L': this.add(new LineTo(s, s=[t[i++],t[i++]]));
                              break;
                    case 'C': this.add(new BezierTo(s, [t[i++],t[i++]], [t[i++],t[i++]], s=[t[i++],t[i++]]));
                              break;
                    default:  i++;
                }
            }
        }
        add(segment) {
            this.segments.push(segment);
            this._length = 0;
            
            this._bb = undefined;
            this._size = undefined;
        }
        length() {
            return this._length || (this._length = this.segments.reduce((a,c) => a + c.length(), 0));
        }
        bb(sampleRate = .01) {
            if(this._bb === undefined) {
                this._bb = Array.from({length: 1 / sampleRate + 1})
                                .map((v, i) => this.p(i * sampleRate))
                                .reduce((p, c) => [[Math.min(p[0][0], c[0]), Math.min(p[0][1], c[1])],[Math.max(p[1][0], c[0]), Math.max(p[1][1], c[1])]], [[Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER], [Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER]]);
            }
            return this._bb;
        }
        size(sampleRate = .01) {
            if(this._size === undefined) {
                this._size = [this.bb(sampleRate)].map(v => [v[1][0] - v[0][0], v[1][1] - v[0][1]]).pop();
            }
            return this._size;
        }
        p(t) {
            t = Math.max(Math.min(t, 1), 0) * this.length();
            for (let l=0, i=0, sl=0; i<this.segments.length; i++, l+=sl) {
                sl = this.segments[i].length();
                if (t > l && t <= l + sl) {
                    return this.segments[i].p((t-l)/sl, sl/this.length());
                }
            }
            return this.segments[Math.min(1, this.segments.length-1)].p(0);
        }
    }
    return new Path(tokens);
}

function getCharacterDefinitions() {
    return [
        [ //0
            ['M2,-17 L1,-15 C-4,-10 -6,15 6,9 C14,5 20,-9 12,-17 C10,-19 4,-16 4,-15'],
            ['M10,-17 C2,-17 -8,-3 -1,4 L4,4 C20,4 21,-16 10,-16'],
            ['M9,-24 C-1,-24 -3,-2 1,6 C3,9 5,7 7,8 C8,9 12,10 14,9 C16,8 19,8 20,7 C27,0 20,-24 11,-24'],
        ],
        [ //1
            ['M11,-33 C11,-15 4,7 4,26'],
            ['M-9,-22 C1,-22 7,-45 11,-45 L11,-43 C11,-37 10,-31 9,-25 C7,-11 5,5 5,19 C5,21 4,31 6,31'],
            ['M1,-33 C1,-9 0,14 0,38'],
        ],
        [ //2
            ['M-13,-9 C-12,-9 -5,-21 -1,-17 C7,-9 -5,-2 -8,1 L-12,6 C-12,6 -11,5 -10,5 C-6,5 -4,6 0,6 C1,6 7,6 5,6'],
            ['M-17,-16 C-17,-21 10,-27 13,-22 C18,-13 5,12 2,20 C1,23 -0,27 -3,28 L-5,29 L-2,29 C0,29 17,30 17,31'],
            ['M-15,-16 C-15,-18 -4,-25 -1,-22 C2,-19 -8,-10 -9,-9 L-11,-8 C-11,-8 1,-8 2,-8'],
        ],
        [ //3
            ['M10,-15 C19,-15 11,-10 11,-10 C11,-10 16,-10 15,-8 C14,-7 10,-4 10,-7'],
            ['M-15,-40 C-10,-45 7,-44 13,-41 C14,-40 14,-35 14,-33 C14,-31 14,-24 13,-23 C9,-19 5,-16 0,-14 L-6,-13 C-6,-13 -3,-13 -2,-13 C3,-13 9,-10 14,-8 C20,-5 15,15 14,19 C13,22 8,20 5,21 C-1,23 -29,17 -29,10'],
            ['M-3,-28 C2,-33 13,-26 15,-24 C16,-23 14,-21 14,-21 C11,-18 7,-17 3,-19 L1,-19 L11,-16 C21,-14 27,-11 21,0 C20,2 -1,-2 -4,-2'],
            ['M-1,-44 C-1,-50 25,-57 28,-54 C31,-51 37,-49 41,-44 C44,-40 44,-34 45,-29 C47,-15 45,6 39,15 C36,19 24,17 20,17 C20,17 11,17 12,18 C14,20 23,17 26,18 C38,20 46,27 52,38 C54,42 52,49 51,53 C50,57 50,64 48,66 C37,77 -5,76 -5,59']
        ],
        [ //4
            [
                'M3,-24 C3,-20 -7,-2 -10,3 C-11,4 -15,10 -13,12 C-12,13 -3,12 -1,12 C12,12 24,11 37,11',
                'M20,3 C20,19 19,34 19,50',
            ],
            ['M13,27 C13,14 13,-2 16,-14 C16,-16 20,-24 19,-25 C18,-26 13,-22 11,-21 C1,-16 -1,-4 -7,2 L-8,5 C-7,6 1,5 3,5 C13,5 23,6 32,6'],
        ],
        [ //5
            [
                'M-2,-16 C-2,-12 -12,13 -11,14 C-10,15 -8,13 -7,13 C-3,13 0,13 4,13 C9,13 18,14 22,16 C26,18 34,17 36,21 C38,25 36,33 35,38 C32,48 13,66 1,60 C-3,58 -9,57 -13,56 C-14,56 -22,49 -18,49',
                'M-2,-14 C6,-18 40,-16 52,-16',
            ],
            ['M34,-30 C27,-30 6,-31 1,-29 C-3,-27 -4,-10 -4,-4 L-5,-2 C-5,-2 11,-3 16,-3 C30,-3 33,7 29,20 C25,33 12,27 0,27 C-4,27 -14,26 -14,26'],
            [
                'M-17,-47 C-17,-45 -20,-35 -19,-34 C-16,-31 -8,-34 -5,-29 C-2,-24 -1,-16 0,-11 C0,-9 1,-3 -1,-1 C-3,1 -12,-1 -12,-4',
                'M-17,-46 C-11,-46 -5,-46 1,-46'
            ],
        ],
        [ //6
            ['M10,-23 C10,-21 5,-19 3,-17 C1,-15 -7,7 -2,12 C1,15 12,13 14,11 C19,6 10,-4 8,-6 C4,-10 -0,0 -1,0'],
            ['M15,-46 C7,-46 1,-35 -2,-28 C-9,-12 -10,4 -13,21 C-14,26 -18,40 -13,43 C-7,46 5,42 11,41 C17,40 23,41 29,39 C33,38 39,36 42,33 L42,29 C44,20 46,4 34,0 C30,-1 24,0 20,0 C16,0 0,1 0,3'],
        ],
        [ //7
            ['M-9,-26 C-3,-26 9,-25 17,-24 C19,-24 24,-23 24,-23 C24,-23 16,-12 15,-10 C12,0 2,20 2,31'],
            [
                'M-8,-45 C5,-45 18,-45 31,-45 C33,-45 44,-46 45,-45 C47,-43 44,-34 44,-31 C44,-25 41,-19 39,-13 C36,-3 36,9 33,20 C31,26 27,32 26,38 C26,39 24,47 24,45',
                'M9,0 C23,0 41,-2 53,-2'
            ],
        ],
        [ //8
            ['M6,11 C-5,11 2,29 10,21 C14,17 4,10 2,8 C-1,5 6,-2 9,1 C13,5 7,11 7,13'],
            ['M31,-39 C16,-39 2,-39 -8,-29 C-9,-28 -15,-20 -12,-17 C-7,-12 5,0 11,6 C12,7 11,13 11,15 C11,25 -1,34 -12,29 C-16,27 -14,18 -14,15 C-14,-9 13,-5 26,-18 C32,-24 37,-32 30,-39 L29,-41'],
            ['M19,-51 C10,-51 4,-50 -2,-46 C-4,-45 -5,-38 -3,-36 C3,-32 26,-34 29,-28 C38,-10 19,4 0,-2 C-4,-3 0,-15 1,-18 C4,-29 20,-32 28,-40 C30,-42 24,-47 23,-47'],
        ],
        [ //9
            ['M13,2 C13,-0 5,2 7,6 C9,11 13,4 13,4 C13,4 8,17 8,14'],
            ['M-2,30 C-2,25 9,17 13,13 C22,4 22,-12 26,-24 C27,-26 31,-37 28,-40 C19,-49 6,-42 1,-32 C-0,-29 -2,-16 2,-15 C6,-14 12,-16 15,-16'],
            ['M34,-49 C22,-49 4,-54 -5,-45 C-6,-44 -5,-35 -5,-33 C-5,-29 -5,-26 -3,-23 C-1,-20 5,-20 9,-20 C13,-20 21,-19 25,-20 C28,-21 28,-28 30,-30 L30,-32 C29,-34 30,-44 30,-44 L29,-29 C27,-12 27,10 27,27 C25,45 24,52 24,50'],
            ['M25,-44 C25,-47 10,-47 8,-47 C-6,-47 -26,-45 -30,-30 C-30,-28 -32,-24 -31,-22 C-29,-18 -22,-18 -18,-18 C-4,-18 9,-18 22,-26 C26,-28 29,-35 29,-39 C29,-40 29,-41 28,-41 L26,-28 C23,-12 23,4 23,20 C23,25 23,30 23,35 C23,36 23,43 23,41'],
        ]
    ];
}

////////////////////////////////////////////////////////////////
// Text utility code. Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/1713ddbe99
// Jurgen 2021: Fixed Text.print() to restore turtle._fullCircle
//.             if was in e.g. degrees mode (or any other)
////////////////////////////////////////////////////////////////
function Text() {class Text {print (t, str, scale = 1, italic = 0, kerning = 1) {let fc = t._fullCircle;t.radians();let pos = [t.x(), t.y()], h = t.h(), o = pos;str.split('').map(c => {const i = c.charCodeAt(0) - 32;if (i < 0 ) {pos = o = this.rotAdd([0, 48*scale], o, h);} else if (i > 96 ) {pos = this.rotAdd([16*scale, 0], o, h);} else {const d = dat[i], lt = d[0]*scale, rt = d[1]*scale, paths = d[2];paths.map( p => {t.up();p.map( s=> {t.goto(this.rotAdd([(s[0]-s[1]*italic)*scale - lt, s[1]*scale], pos, h));t.down();});});pos = this.rotAdd([(rt - lt)*kerning, 0], pos, h);}});t._fullCircle = fc;}rotAdd (a, b, h) {return [Math.cos(h)*a[0] - Math.sin(h)*a[1] + b[0], Math.cos(h)*a[1] + Math.sin(h)*a[0] + b[1]];}}const dat = ('br>eoj^jl<jqirjskrjq>brf^fe<n^ne>`ukZdz<qZjz<dgrg<cmqm>`thZhw<lZlw<qao_l^h^e_caccdeefggmiojpkqmqporlshsercp>^vs^as<f^h`hbgdeeceacaab_d^f^h_k`n`q_s^<olmmlolqnspsrrspsnqlol>]wtgtfsereqfphnmlpjrhsdsbraq`o`makbjifjekckaj_h^f_eaecffhimporqssstrtq>eoj`i_j^k_kajcid>cqnZl\\j_hcghglhqjulxnz>cqfZh\\j_lcmhmllqjuhxfz>brjdjp<egom<ogem>]wjajs<ajsj>fnkojpiojnkokqis>]wajsj>fnjniojpkojn>_usZaz>`ti^f_dbcgcjdofrisksnrpoqjqgpbn_k^i^>`tfbhak^ks>`tdcdbe`f_h^l^n_o`pbpdofmicsqs>`te^p^jfmfogphqkqmppnrkshserdqco>`tm^clrl<m^ms>`to^e^dgefhekenfphqkqmppnrkshserdqco>`tpao_l^j^g_ebdgdlepgrjsksnrppqmqlpingkfjfggeidl>`tq^gs<c^q^>`th^e_dadceegfkgnhpjqlqopqorlshserdqcocldjfhigmfoepcpao_l^h^>`tpeohmjjkikfjdhcecddaf_i^j^m_oapepjoomrjshserdp>fnjgihjikhjg<jniojpkojn>fnjgihjikhjg<kojpiojnkokqis>^vrabjrs>]wagsg<amsm>^vbarjbs>asdcdbe`f_h^l^n_o`pbpdofngjijl<jqirjskrjq>]xofndlcicgdfeehekfmhnknmmnk<icgefhfkgmhn<ocnknmpnrntluiugtdsbq`o_l^i^f_d`bbad`g`jambodqfrislsorqqrp<pcokompn>asj^bs<j^rs<elol>_tc^cs<c^l^o_p`qbqdpfoglh<chlhoipjqlqopqorlscs>`urcqao_m^i^g_eadccfckdnepgrismsorqprn>_tc^cs<c^j^m_oapcqfqkpnopmrjscs>`sd^ds<d^q^<dhlh<dsqs>`rd^ds<d^q^<dhlh>`urcqao_m^i^g_eadccfckdnepgrismsorqprnrk<mkrk>_uc^cs<q^qs<chqh>fnj^js>brn^nnmqlrjshsfreqdndl>_tc^cs<q^cl<hgqs>`qd^ds<dsps>^vb^bs<b^js<r^js<r^rs>_uc^cs<c^qs<q^qs>_uh^f_daccbfbkcndpfrhslsnrppqnrkrfqcpan_l^h^>_tc^cs<c^l^o_p`qbqepgohlici>_uh^f_daccbfbkcndpfrhslsnrppqnrkrfqcpan_l^h^<koqu>_tc^cs<c^l^o_p`qbqdpfoglhch<jhqs>`tqao_l^h^e_caccdeefggmiojpkqmqporlshsercp>brj^js<c^q^>_uc^cmdpfrisksnrppqmq^>asb^js<r^js>^v`^es<j^es<j^os<t^os>`tc^qs<q^cs>asb^jhjs<r^jh>`tq^cs<c^q^<csqs>cqgZgz<hZhz<gZnZ<gznz>cqc^qv>cqlZlz<mZmz<fZmZ<fzmz>brj\\bj<j\\rj>asazsz>fnkcieigjhkgjfig>atpeps<phnfleiegfehdkdmepgrislsnrpp>`sd^ds<dhffhekemfohpkpmopmrkshsfrdp>asphnfleiegfehdkdmepgrislsnrpp>atp^ps<phnfleiegfehdkdmepgrislsnrpp>asdkpkpiognfleiegfehdkdmepgrislsnrpp>eqo^m^k_jbjs<gene>atpepuoxnylzizgy<phnfleiegfehdkdmepgrislsnrpp>ate^es<eihfjemeofpips>fni^j_k^j]i^<jejs>eoj^k_l^k]j^<kekvjyhzfz>are^es<oeeo<ikps>fnj^js>[y_e_s<_ibfdegeifjijs<jimfoeretfuius>ateees<eihfjemeofpips>atiegfehdkdmepgrislsnrppqmqkphnfleie>`sdedz<dhffhekemfohpkpmopmrkshsfrdp>atpepz<phnfleiegfehdkdmepgrislsnrpp>cpgegs<gkhhjfleoe>bsphofleieffehfjhkmlompopporlsisfrep>eqj^jokrmsos<gene>ateeeofrhsksmrpo<peps>brdejs<pejs>_ubefs<jefs<jens<rens>bseeps<pees>brdejs<pejshwfydzcz>bspees<eepe<esps>cqlZj[i\\h^h`ibjckekgii<j[i]i_jakbldlfkhgjkllnlpkrjsiuiwjy<ikkmkojqirhthvixjylz>fnjZjz>cqhZj[k\\l^l`kbjcieigki<j[k]k_jaibhdhfihmjilhnhpirjskukwjy<kkimiojqkrltlvkxjyhz>^vamakbhdgfghhlknlplrksi<akbidhfhhillnmpmrlsisg>brb^bscsc^d^dsese^f^fsgsg^h^hsisi^j^jsksk^l^lsmsm^n^nsoso^p^psqsq^r^rs').split('>').map(r=> { return [r.charCodeAt(0)-106,r.charCodeAt(1)-106, r.substr(2).split('<').map(a => {const ret = []; for (let i=0; i<a.length; i+=2) {ret.push(a.substr(i, 2).split('').map(b => b.charCodeAt(0)-106));} return ret; })]; });return new Text();}