Skip to content

Instantly share code, notes, and snippets.

@naganowl
Created April 24, 2014 22:43
Show Gist options
  • Save naganowl/11272048 to your computer and use it in GitHub Desktop.
Save naganowl/11272048 to your computer and use it in GitHub Desktop.
Demo script off of http://projectnaptha.com/
// I guess this happens to be the first file which gets included
// so I might as well use this to write a message here for whoever
// ends up reading this
// you're probably gonna be seeing it as one file, but rest assured
// (or afraid), that this is actually a compiled file, and I don't
// generally scroll through four thousand lines of code in order to
// find the valid_img() function.
// that said, the organization is still something that leaves a lot
// to be desired.
function uuid(){
for(var i = 0, s, b = ''; i < 42; i++)
if(/\w/i.test(s = String.fromCharCode(48 + Math.floor(75 * Math.random())))) b += s;
return b;
}
/**
* Display an image in the console.
* @param {string} url The url of the image.
* @param {int} scale Scale factor on the image
* @return {null}
* http://dunxrion.github.io
*/
console.image = function(url, log) {
var img = new Image();
function getBox(width, height) {
return {
string: "+",
style: "font-size: 1px; padding: " + Math.floor(height/2) + "px " + Math.floor(width/2) + "px; line-height: " + height + "px;"
}
}
img.onload = function() {
var dim = getBox(this.width, this.height);
console.log(log, url)
console.log("%c" + dim.string, dim.style + "background: url(" + url + "); background-size: " + (this.width) + "px " + (this.height) + "px; color: transparent;");
};
img.src = url;
};
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
// MIT license
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
|| window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
var global_params = {
ocrad_worker: 'src/ocrad.js/worker.js',
swt_worker: 'src/swt/worker.js',
inpaint_worker: 'src/inpaint.js/worker.js',
mask_worker: 'src/mask/worker.js',
num_workers: 2,
is_extension: false,
simple_ids: true,
demo_mode: true,
queue_expires: 1000 * 2, // two seconds
user_id: "demo",
apiroot: "https://sky-lighter.appspot.com/api/"
}
// really, none of this matters
var storage_cache = {
warn_ocrad: false
};
function get_setting(name){ return storage_cache[name] }
function save_settings(){
// localStorage.settings = JSON.stringify(storage_cache)
}
function put_setting(name, val){
storage_cache[name] = val;
save_settings()
}
// Project Naptha: browser extension enables selecting, copying, and translating text from any image
// Project Naptha: a browser extension that enables text selection on any image
// PR open-sources Copytext, a library for handling spreadsheets as Python objects
// test
// window.addEventListener('message', function(e){
// receive(e.data)
// })
// for(var i = 0; i < document.images.length; i++){
// var image = im(document.images[i]);
// var num_chunks = Math.max(1, Math.ceil((image.height - image.params.chunk_overlap) / (image.params.chunk_size - image.params.chunk_overlap)));
// var chunks = []
// for(var j = 0; j < num_chunks; j++){
// chunks.push(j)
// }
// broadcast({
// type: 'qchunk',
// id: image.id,
// chunks: chunks
// })
// }
var precomputed_images = {};
function broadcast(data){
if(data.type == 'qchunk'){
var whitelist = ["chelsea", "equis", "hilighting", "irate", "nobody", "protobowl", "russia", "thiel", "tyger", "rose", "skid"]
if(data.id in precomputed_images || whitelist.indexOf(data.id) == -1){
}else{
precomputed_images[data.id] = {}
var xhr = new XMLHttpRequest()
xhr.open('GET', 'dat/' + data.id + '/regions.json', true)
xhr.onload = function(){
// console.log(xhr.responseText)
var res = JSON.parse(xhr.responseText)
precomputed_images[data.id] = res;
receive({
type: 'region',
id: data.id,
regions: res.regions,
chunks: res.chunks
})
}
xhr.send(null)
}
}else if(data.type == 'qpaint'){
var res = precomputed_images[data.id];
var url = 'dat/' + data.id + '/' + data.reg_id.replace(/[\:\-]/g, '') + '.png';
var tmp = new Image()
tmp.onload = function(){
// console.log('blah')
var pls = res._plaster[data.reg_id];
receive({
id: data.id,
type: 'painted',
plaster: url,
reg_id: data.reg_id,
colors: pls.colors,
x: pls.x,
y: pls.y,
width: pls.width,
height: pls.height
})
}
tmp.src = url;
}else if(data.type == 'qocr' && data.engine == "ocrad"){
// welp not implementented
// must tesseract
}else{
console.log(data)
}
}
onload = function(){
setTimeout(function(){
initial_selection()
}, 100)
}
function initial_selection(){
var image = im(document.getElementById('tyger'));
function is_loaded(){
sel.img = image.el;
sel.stack = [
[{
region: image.regions[1],
line: image.regions[1].lines[2],
word: image.regions[1].lines[2].words[3],
letter: image.regions[1].lines[2].words[3].letters[0],
}, null, Date.now()],
[{
region: image.regions[3],
}],
[null, {
region: image.regions[4],
line: image.regions[4].lines[1],
word: image.regions[4].lines[1].words[2],
letter: image.regions[4].lines[1].words[2].letters.slice(-1)[0]
}, Date.now()]
]
update_selection()
}
function check_loaded(){
if(image.regions.length == 0){
broadcast({
type: 'qchunk',
id: image.id,
time: Date.now(),
chunks: []
})
setTimeout(check_loaded, 100)
}else{
console.log()
is_loaded()
}
}
check_loaded();
}
// function broadcast(data){
// var fr = document.getElementById('project_naptha_core_frame')
// if(fr && fr.contentWindow){
// fr.contentWindow.postMessage(data, location.protocol + '//' + location.host)
// }else{
// // oops this shit dont exist yet
// broadcast_queue.unshift(data)
// if(!fr){
// var fr = document.createElement('iframe')
// fr.src = 'core.html'
// fr.id = 'project_naptha_core_frame'
// fr.style.display = 'none'
// get_container().appendChild(fr)
// }
// }
// }
// <iframe src="core.html" id="project_naptha_core_frame" style="display: none"></iframe>
var session_params = {
// show_chunks: true,
// show_regions: true,
// show_lines: true,
// show_contours: true
}
var default_params = {
// the kernel size for the gaussian blur before canny
kernel_size: 3,
// low and high thresh are parameters for the canny edge detector
low_thresh: 124,
high_thresh: 204,
// maximum stroke width, this is the number of iterations
// the core stroke width transform loop will go through
// before giving up and saying that there is no stroke here
max_stroke: 35,
// the maximum ratio between adjacent strokes for the
// connected components algorithm to consider part of the
// same actual letter
stroke_ratio: 2,
// this is the pixel connectivity required for stuff to happen
min_connectivity: 4,
// the minimum number of pixels in a connected component to
// be considered a candidate for an actual letter
min_area: 30, //default: 38
// maximum stroke width variation allowed within a letter
std_ratio: 0.83,
// maximum aspect ratio to still be considered a letter
// for instance, a really long line wouldn't be considered
// a letter (obviously if this number is too low, it'll start
// excluding l's 1's and i's which would be bad)
aspect_ratio: 10, // default: 8
// maximum ratio between the median thicknesses of adjacent
// letters to be considered part of the same line
thickness_ratio: 3,
// maximum ratio between adjacent letter heights to be considered
// part of the same line
height_ratio: 2.5, // original: 1.7
// for some reason it's much more effective with non-integer scales
scale: 1.3,
// scale: 1.8,
// text_angle: Math.PI / 6
letter_occlude_thresh: 7, //default 3
// otsu parameter for word breakage
breakdown_ratio: 0.4,
// something something making lines
elongate_ratio: 1.9,
// maximum number of surrounding pixels to explore during the
// flood fill swt augmentation stage
max_substroke: 15,
// linespacing things for spacing lines, used in forming paragraphs/regions
min_linespacing: 0.1, // this is for overlap
max_linespacing: 1.7,
// otsu breakdown ratio for splitting a paragraph
col_breakdown: 0.3,
// the maximum fraction of the width of the larger of two adjacent lines
// by which an alignment may be offset in one column
max_misalign: 0.1,
// the first one is frac of the smaller area, the second is frac of bigger area
col_mergethresh: 0.3,
lettersize: 0.4, // maximum difference between median letter weights of adjacent lines
// letter weight is defined as the number of pixels per letter divided by the width
// which is because quite often entire words get stuck together as one letter
// and medians are used because it's a more robust statistic, this actually works
// remarkably well as a metric
// debugs!?!?
debug: false,
chunk_size: 250,
chunk_overlap: 90,
}
var session_id = uuid()
var image_counter = 0;
var images = {};
function get_id(img){
if(!img) return;
// if you're passing an id, return the id
if(typeof img == 'string') return img;
function clean(str){
// return str.replace(/^.+:\/\//g, '').replace(/[^a-z.\/_]/gi, '')
return str.replace(/[^a-z0-9.\/_\-]/gi, '')
}
if(!('__naptha_id' in img)){
var readable = clean(img.src.replace(/^.*\/(.*)$/g, '$1').split('.')[0]);
img.__naptha_id = (image_counter++) + '**' + readable + '**' + clean(img.src) + '**' + session_id;
if(global_params.simple_ids){
img.__naptha_id = readable
}
}
return img.__naptha_id
}
function im(img){
var id = get_id(img)
if(id in images) return images[id];
function shallow(obj){
var new_obj = {};
for(var i in obj){
new_obj[i] = obj[i]
}
return new_obj;
}
var params = shallow(default_params);
var src = img.src;
if(src.indexOf("http://localhost/Dropbox/Projects/naptha/") == 0 || global_params.demo_mode){
src = "demo:" + img.src.replace(/^.*\/(.*?)\..*?$/g, '$1')
}
var image = images[id] = {
id: id,
el: img,
width: Math.round(img.naturalWidth * params.scale),
height: Math.round(img.naturalHeight * params.scale),
src: src,
real_src: img.src,
chunks: [],
regions: [],
engine: 'default',
params: params
}
collect_contexts()
return image;
}
function receive(data){
if(data.type == 'getparam'){
var image = im(data.id)
broadcast({
type: 'gotparam',
id: image.id,
src: image.src,
real_src: image.real_src,
params: image.params,
initial_chunk: data.initial_chunk
})
}else if(data.type == 'region'){
var image = im(data.id)
// var old_regions = {}
// image.regions.forEach(function(e){ old_regions[e.id] = e; })
// // if you had an old, finished column, keep the same
// // object rather than replacing it with the exact
// // same new one
// image.regions = data.regions.map(function(e){
// if(e.id in old_regions && old_regions[e.id].finished){
// return old_regions[e.id]
// }else{
// return e
// }
// })
image.regions = data.regions
image.chunks = data.chunks
image.stitch_debug = data.stitch_debug
// if(sel.img && image.id == get_id(sel.img)){
// render_selection(image.el, get_selection(sel, image), image.params)
// }
update_selection()
draw_annotations(image.el, image)
}else if(data.type == 'painted'){
// console.log(data.reg_id)
var image = im(data.id)
var mask = new Image()
mask.style.webkitTransform = 'translateZ(0)'
mask.src = data.plaster;
if(!image.plaster) image.plaster = {};
image.plaster[data.reg_id] = {
mask: mask,
colors: data.colors,
x: data.x,
y: data.y,
width: data.width,
height: data.height,
finished: Date.now()
}
update_overlay(image.el)
init_layer(mask, 'plaster')
var sx = (image.el.width / image.el.naturalWidth),
sy = (image.el.height / image.el.naturalHeight);
mask.style.left = (sx * data.x) + 'px'
mask.style.top = (sy * data.y) + 'px'
mask.style.width = (sx * data.width) + 'px'
mask.style.height = (sy * data.height) + 'px'
mask.style.transition = 'opacity 1s'
mask.style.opacity = '0'
image.overlay.appendChild(mask)
// update_translations(image)
image.regions.forEach(function(region){
if(region.id == data.reg_id){
translate_region(image, region)
}
})
draw_overlays(image)
update_selection();
}else if(data.type == 'recognized'){
var image = im(data.id)
if(!image.ocr) image.ocr = {};
// console.log(data)
var plain_text = data.text;
try {
plain_text = JSON.parse(plain_text).text
} catch (err) {
if(data.enc == 'tesseract'){
data.enc = 'error'
}
}
if(data.enc == 'error' || /^ERROR/i.test(plain_text)){
image.regions.forEach(function(region){
if(region.id == data.reg_id){
error_message(image, region, plain_text)
}
})
delete image.ocr[data.reg_id]
return
}
if(data.enc == 'tesseract'){
var json = JSON.parse(data.text);
var raw = parseTesseract(json)
// if(!((image.lookup || {}).chunks || []).some(function(chunk){ return chunk.engine == data.engine })){
;((image.lookup || {}).chunks || []).push({
engine: data.engine,
meta: json.meta,
key: json.key
})
// }
}else{
var raw = data.raw;
}
var ocr = image.ocr[data.reg_id];
if(ocr._engine != data.engine) return;
ocr.finished = Date.now()
delete ocr.processing;
// ocr.colors = data.colors;
ocr.raw = raw
ocr.text = data.text;
var broken = raw.some(function(block){
return isNaN(block.x) || isNaN(block.y) || isNaN(block.w) || isNaN(block.h)
});
if(broken){
var ocr = image.ocr[data.reg_id];
ocr.raw = null;
ocr.error = true;
ocr.text = 'ERROR: ' + data.text;
}
if(check_selection()) modify_clipboard();
update_translations(image)
draw_overlays(image)
update_selection();
}
}
function virtualize_region(image, region){
function transform(region){
if(image.translate && region.id in image.translate && image.translate[region.id].finished && image.virtual && region.id in image.virtual){
var lang = (image.translate[region.id] || {}).language
if(lang && lang != 'erase') return image.virtual[region.id];
}
return region
}
if(region.map)
return region.map(transform);
return transform(region)
}
// function urlencode(obj){
// return Object.keys(obj).map(function(e){
// return e + '=' + encodeURIComponent(obj[e])
// }).join('&')
// }
function get_lookup_chunks(image, region){
function frac_intersect(a, b){
var width = Math.min(a.x1, b.x1) - Math.max(a.x0, b.x0),
height = Math.min(a.y1, b.y1) - Math.max(a.y0, b.y0);
var max_area = Math.max((1 + a.x1 - a.x0) * (1 + a.y1 - a.y0),
(1 + b.x1 - b.x0) * (1 + b.y1 - b.y0))
return (width > 0 && height > 0) ? (width * height / max_area) : 0;
}
var filtered = ((image.lookup || {}).chunks || []).filter(function(chunk){
// if(region.x0)
// chunk.meta
// check to see if its the right region
var meta = chunk.meta;
var intersect = frac_intersect({
x0: meta.x0 / meta.sws,
y0: meta.y0 / meta.sws,
x1: meta.x1 / meta.sws,
y1: meta.y1 / meta.sws
}, {
x0: region.x0 / image.params.scale,
y0: region.y0 / image.params.scale,
x1: region.x1 / image.params.scale,
y1: region.y1 / image.params.scale
});
// console.log(intersect, meta, region)
return meta.v == 3 && meta.dir == region.direction && intersect > 0.9
})
return filtered
}
function get_ocr_engine(image, region, def){
var ocr = image.ocr[region.id]
if(ocr.engine == 'default'){
var filtered = get_lookup_chunks(image, region)
// TODO: sort by popularity?
return (filtered[0] && filtered[0].engine) || def || 'ocrad'
// console.log(filtered, 'filteahz')
// return "ocrad"
}else{
return ocr.engine;
}
}
function ocr_region(image, col){
if(!image.ocr) image.ocr = {};
if(col.finished != true) return;
if(!(col.id in image.ocr)){
image.ocr[col.id] = {
engine: "default"
}
}
// console.log(col, "QUEUEING SOMETHING")
// TODO: there's some code which exists that
// filters out the virtual ones and it's like
// not this
if(col.virtual){
for(var i = 0; i < image.regions.length; i++){
if(image.regions[i].id == col.id)
col = image.regions[i];
}
}
var ocr = image.ocr[col.id];
// var eng = ocr.engine == 'default' ? 'tess:eng' : ocr.engine;
var eng = get_ocr_engine(image, col)
if(ocr._engine != eng){
ocr._engine = eng
delete ocr.finished;
delete ocr.processing;
}
if(ocr.finished || ocr.processing) return;
delete ocr.waiting;
var matches = get_lookup_chunks(image, col).filter(function(chunk){
return chunk.engine == eng
})
if(ocr._engine != 'ocrad'){
// gotta have lookup loaded or else cant do something
if(!image.lookup){
do_lookup(image)
}else if(image.lookup.error){
error_message(image, col, "Can't access lookup server.")
}else if(!image.lookup.finished){
setTimeout(function(){
ocr_region(image, col)
}, 100);
return;
}
}
if(matches.length > 0){
// TODO: figure out the ideal candidate
var chunk = matches[0];
var xhr = new XMLHttpRequest()
xhr.open('GET', global_params.apiroot + 'read/' + chunk.key, true)
xhr.send()
xhr.onload = function(){
// var result = JSON.parse(xhr.responseText);
// console.log(parseTesseract(result))
receive({
type: 'recognized',
enc: 'tesseract',
reg_id: col.id,
id: image.id,
engine: eng,
text: xhr.responseText
})
}
}else{
queue_broadcast({
src: image.src,
type: 'qocr',
apiroot: global_params.apiroot,
region: col,
reg_id: col.id,
id: image.id,
engine: ocr._engine,
swtscale: image.params.scale,
swtwidth: image.width
})
}
// image.ocr[col.id] = { processing: Date.now() }
image.ocr[col.id].processing = Date.now()
}
var broadcast_queue = [], is_casting = false;
function queue_broadcast(data){
broadcast_queue.push(data)
if(!is_casting) dequeue_broadcast();
}
function dequeue_broadcast(){
is_casting = false
if(broadcast_queue.length){
broadcast(broadcast_queue.shift())
setTimeout(dequeue_broadcast, 500)
is_casting = true;
}
}
// this function takes a y_orig, that is, a y coordinate in terms of the
// dimensions of the original image and then returns an ordered list of
// chunks which may be involved (containing text pertaining to said coordinate)
// it may return up to two chunks, in which case the ordering refers to which
// block the text is "most likely" to reside on (but it could actually end
// up on the other one too)
function to_chunks(sY, image){
var num_chunks = Math.max(1, Math.ceil((image.height - image.params.chunk_overlap) / (image.params.chunk_size - image.params.chunk_overlap)))
var base = Math.min(num_chunks - 1, Math.floor(sY / (image.params.chunk_size - image.params.chunk_overlap)))
var offset = sY - base * (image.params.chunk_size - image.params.chunk_overlap);
if(base <= 0){
return [0]
}else if(offset < image.params.chunk_overlap / 2){
return [base - 1, base]
}else if(offset < image.params.chunk_overlap){
return [base, base - 1]
}else{
return [base]
}
}
function valid_image(img){
// disable this for small links!
if(!(
img &&
img.tagName == 'IMG' &&
img.complete &&
img.width > 150 && img.naturalWidth > 150 &&
Math.min(img.width, img.naturalWidth) * Math.min(img.height, img.naturalHeight) > 19000 &&
img.height > 60 && img.naturalHeight > 60 &&
img.naturalWidth < 2000 &&
img.src.length < 300 && // no really long urls
img.ownerDocument.designMode != 'on' &&
/^(https?|file):/i.test(img.src) // http, https or file protocols
)) return false;
var n = img, is_link = false;
do {
if(n.getAttribute('ocr') == 'off') return false;
if(global_params.is_extension){
if(n.getAttribute('ocr') == 'custom') return false;
}
if(n.tagName == 'A'){
is_link = true;
}
} while ( (n = n.parentNode) && n.getAttribute);
// this is kind of an odd test, it happens post-facto
// that is, it happens after it's already been validated
// and it might invalidate a valid image
if(is_link && img.__naptha_id){
var image = im(img);
// hopefully this aint recursive
var regpad = 10;
var total_area = 0;
image.regions.forEach(function(region){
total_area += (regpad * 2 + region.width) * (regpad * 2 + region.height)
})
var frac_text = total_area / (image.width * image.height);
if(frac_text > 0.85){
// if it's a link and mostly text, then dont interfere with it
return false;
}
}
return true
// /zoom/.test(img.style.cursor) == false // the blown up image pics have un-preventable zoom effcts
}
function image_layout(el){
var dim = el.getBoundingClientRect(),
cmp = window.getComputedStyle(el);
// if(dim.width == 0 || dim.height == 0){
// delete images[get_id(el)]
// dispose_overlay(el)
// }
function sty(prop){ return parseInt(cmp.getPropertyValue(prop), 10) }
var X = dim.left + sty('padding-left') + sty('border-left-width'),
Y = dim.top + sty('padding-top') + sty('border-top-width');
return {
width: el.width, height: el.height,
X: X, Y: Y,
left: pageXOffset + X, top: pageYOffset + Y
}
}
function extract_region(ocr, start, end){
var col = (start && start.region) || end.region;
if(ocr.error){
return [ ocr.text ];
}
if(!start || !start.line) start = { line: col.lines[0], region: col };
if(!end || !end.line) end = { line: col.lines[col.lines.length - 1], region: col };
if(start.line.id == end.line.id){
return [extract_line(ocr, start, end)]
}else{
var within = false;
return col.lines.map(function(line){
if(line.id == start.line.id){
within = true;
return extract_line(ocr, start, null)
}else if(line.id == end.line.id){
within = false;
return extract_line(ocr, null, end)
}else if(within){
return extract_line(ocr, { line: line, region: col })
}
return null
}).filter(function(e){ return e })
}
}
function extract_line(ocr, start, end){
var line = (start && start.line) || end.line; // one of them needs to have it defined
var region = (start && start.region) || end.region;
var letters = [].concat.apply([], line.words.map(function(word){ return word.letters }))
if(region.virtual){
return line.words.map(function(word){
return word.letters.filter(function(letter){
var rcx = letter.x0 / 2 + letter.x1 / 2;
var in_range = (!(start && start.letter) || rcx > start.letter.x0) &&
(!(end && end.letter) || rcx < end.letter.x1);
return in_range
}).map(function(e){
return e._
}).join('')
}).join(' ').trim()
}
var yr0 = Infinity, yr1 = -Infinity
letters.forEach(function(letter){
var y_pred = (letter.cx - line.cx) * Math.tan(line.angle) + line.cy
yr0 = Math.min(yr0, letter.y0 - y_pred)
yr1 = Math.max(yr1, letter.y1 - y_pred)
})
// var c = document.createElement('canvas')
// c.width = 750
// c.height = 500
// var ctx = c.getContext('2d')
// var blahe = new Image()
// blahe.src = 'http://localhost/Dropbox/Projects/naptha/img/eink.jpg'
// ctx.drawImage(blahe, 0, 0, blahe.width * region.scale, blahe.height * region.scale)
var matches = ocr.raw.filter(function(rec){
var rcx = (rec.x + rec.w / 2) * region.scale,
rcy = (rec.y + rec.h / 2) * region.scale;
var y_pred = (rcx - line.cx) * Math.tan(line.angle) + line.cy
// ctx.fillRect(rcx, y_pred + yr0, 4, 1);
// ctx.fillRect(rcx, y_pred + yr1, 4, 1);
// ctx.strokeRect(rec.x * region.scale, rec.y * region.scale, rec.w * region.scale, rec.h * region.scale)
var in_line = (rcy > y_pred + yr0 && rcy < y_pred + yr1)
// if the lines dont exist there are no limits
var in_range = (!(start && start.letter) || rcx > start.letter.x0) &&
(!(end && end.letter) || rcx < end.letter.x1);
var is_rec = rec.matches.length > 0;
return in_line && in_range && is_rec
});
// console.log('line', start, end, ocr, matches)
// console.image(c.toDataURL('image/png'))
return matches.sort(function(a, b){
return (a.x + a.w) - (b.x + b.w)
}).map(function(rec){
if(rec.sw){
// tesseract encodes its spaces as a startword flag
return ' ' + rec.matches[0][0]
}
return rec.matches[0][0]
}).join('').trim()
}
var PROCESSING_PREAMBLE = '<[ TEXT RECOGNITION IN PROGRESS / MORE INFO: http://projectnaptha.com/process/ '
var PROCESSING_CONCLUDE = ' / TEXT RECOGNITION IN PROGRESS ]>'
// http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
function RegEsc(s) { return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); };
var PROCESSING_MATCHER = new RegExp(RegEsc(PROCESSING_PREAMBLE) + '(.*?)' + RegEsc(PROCESSING_CONCLUDE), 'g')
function extract_selection(sel, image){
var output = get_selection(sel, image).map(function(pair){
var start = pair[0],
end = pair[1]
var col = (start && start.region) || end.region;
var locator = [
col.id,
start && start.line && start.line.id,
end && end.line && end.line.id,
start && start.letter && start.letter.x0,
end && end.letter && end.letter.x1
].map(function(e){ return e || '!' }).join('&')
// synthesize_pair(null, locator)
// if(!((col.id in image.ocr) && image.ocr[col.id].finished)){
var variable = '(IDX:'+ locator +':XDI)'
if(col.id in image.ocr && image.ocr[col.id].processing){
variable += ' / ELAPSED ' + ((Date.now() - image.ocr[col.id].processing) / 1000).toFixed(2) + 'SEC'
}
variable += ' / DATE ' + (new Date).toUTCString()
return PROCESSING_PREAMBLE + variable + PROCESSING_CONCLUDE;
// return extract_region(image.ocr[col.id], start, end).join('\n').trim()
}).join('\n\n')
// actually, there's a good chance you could create an image
// with text which matches the ocrad pattern and it might act
// as a rather interesting glitch
// i doubt it constitutes a serious vulnerability or problem
// though, but it might be wise to fix this in future versions
// with some kind of padding or something
var incomplete = 0;
var has_ocrad = false;
var text = substitute_recognition(output, function(region_id){
if((region_id in image.ocr) && image.ocr[region_id].finished){
var region = virtualize_region(image, image.regions.filter(function(region){
return region.id == region_id
})[0]);
var ocr = image.ocr[region_id];
if(ocr && ocr._engine == 'ocrad' && ocr.engine == "default") has_ocrad = true;
return {
region: region,
ocr: ocr
}
}
incomplete++
return null;
});
// this is a little ocr thing that predicts basically whether or not something was
// recognized poorly, it's just a kind of heuristic
// var bad_ocr = /[^A-Z \.\,][A-Z\.\,]+/.test(text) || /[^a-z \.\,][a-z\.\,]+/.test(text);
var bad_ocr = text.trim().split(/[\s\-\:\;\&\']+/).some(function(word){
// short words are exempt
if(word.length <= 2) return false;
// all caps words are exempt
if(word.toUpperCase() == word) return false;
// if there exists a word which is like hELLO
return !/^[A-za-z][a-z]*[a-z\.\'\"\,\-\!\?]$/.test(word)
});
if(bad_ocr && has_ocrad && get_setting('warn_ocrad')){
text += '\n\nThis text was recognized by the built-in Ocrad engine. A better transcription may be attained by changing the OCR Engine (under the Language menu) to Tesseract. This message can be removed in the future by unchecking "OCR Disclaimer" (under the Options menu). More info: http://projectnaptha.com/ocrad'
}
return {
text: text,
incomplete: incomplete
}
}
function substitute_recognition(text, interactor){
return text.replace(PROCESSING_MATCHER, function(all, interior){
var locmat = interior.match(/\(IDX:(.*?):XDI\)/)
if(locmat && locmat[1]){
var locator = locmat[1];
var loc = locator.split('&').map(function(e){ return e == '!' ? null : e })
var dat = interactor(loc[0]);
if(dat && dat.ocr){
var region = dat.region;
var start = {
line: loc[1] && region.lines.filter(function(line){
return line.id == loc[1]
})[0],
letter: loc[3] && { x0: loc[3] },
region: region
}
var end = {
line: loc[2] && region.lines.filter(function(line){
return line.id == loc[2]
})[0],
letter: loc[4] && { x1: loc[4] },
region: region
}
return extract_region(dat.ocr, start, end).join('\n').trim()
}
}
return all
});
}
function parseTesseract(response){
var meta = response.meta;
var rotw = (meta.x1 - meta.x0 + 1) / meta.sws * meta.cos + meta.xp * 2,
roth = (meta.y1 - meta.y0 + 1) / meta.sws * meta.cos + meta.yp * 2;
var text = response.text.trim();
if(text.length == 0) return [];
var raw = text.split('\n').map(function(e){
var first = e.split('\t')[0];
var d = first.trim().split(' ');
var x = parseInt(d[0]),
y = parseInt(d[1]),
w = parseInt(d[2]),
h = parseInt(d[3]),
conf = parseFloat(d[4]);
var cx = x + w / 2 - rotw / 2,
cy = y + h / 2 - roth / 2;
var rcx = (cx * Math.cos(meta.ang) - cy * Math.sin(meta.ang) + rotw / 2) / meta.red,
rcy = (cx * Math.sin(meta.ang) + cy * Math.cos(meta.ang) + roth / 2) / meta.red
return {
x: (rcx - w / 2 / meta.red) / meta.cos + meta.x0 / meta.sws - meta.xp,
y: (rcy - h / 2 / meta.red) / meta.cos + meta.y0 / meta.sws - meta.yp,
w: w / meta.red / meta.cos,
h: h / meta.red / meta.cos,
sw: /SW$/.test(first.trim()),
matches: [ [e.slice(first.length + 1), conf] ]
}
})
return raw;
}
function parseOcrad(response){
var meta = response.meta;
var rotw = (meta.x1 - meta.x0 + 1) / meta.sws * meta.cos + meta.xp * 2,
roth = (meta.y1 - meta.y0 + 1) / meta.sws * meta.cos + meta.yp * 2;
var raw = response.raw.map(function(e){
return e.match(/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*;\s*(\d+)(\,?.+)?$/)
}).filter(function(e){
return e
}).map(function(e){
var x = parseInt(e[1]),
y = parseInt(e[2]),
w = parseInt(e[3]),
h = parseInt(e[4]),
g = parseInt(e[5]);
var matches = [];
if(g > 0){
var etc = e[6].trim();
while(etc[0] == ',' && etc[1] == ' '){
etc = etc.slice(2)
var m = etc.match(/^\'(.+?)\'(\d+)/)
matches.push([m[1], parseInt(m[2])])
etc = etc.slice(m[0].length)
}
}
if(matches.length != g){
console.error('recognition count mismatch', g, matches)
}
// console.log(x, y, w, h, g, etc)
var cx = x + w / 2 - rotw / 2,
cy = y + h / 2 - roth / 2;
var rcx = (cx * Math.cos(meta.ang) - cy * Math.sin(meta.ang) + rotw / 2) / meta.red,
rcy = (cx * Math.sin(meta.ang) + cy * Math.cos(meta.ang) + roth / 2) / meta.red
return {
// convert everything back to transformed scaled coordinates
x: (rcx - w / 2 / meta.red) / meta.cos + meta.x0 / meta.sws - meta.xp,
y: (rcy - h / 2 / meta.red) / meta.cos + meta.y0 / meta.sws - meta.yp,
w: w / meta.red / meta.cos,
h: h / meta.red / meta.cos,
matches: matches
}
});
return raw;
}
function UnionFind(count) {
this.roots = new Array(count);
this.ranks = new Array(count);
for(var i=0; i<count; ++i) {
this.roots[i] = i;
this.ranks[i] = 0;
}
}
UnionFind.prototype.length = function() {
return this.roots.length;
}
UnionFind.prototype.makeSet = function() {
var n = this.roots.length;
this.roots.push(n);
this.ranks.push(0);
return n;
}
UnionFind.prototype.find = function(x) {
var roots = this.roots;
while(roots[x] !== x) {
var y = roots[x];
roots[x] = roots[y];
x = y;
}
return x;
}
UnionFind.prototype.link = function(x, y) {
var xr = this.find(x)
, yr = this.find(y);
if(xr === yr) {
return;
}
var ranks = this.ranks
, roots = this.roots
, xd = ranks[xr]
, yd = ranks[yr];
if(xd < yd) {
roots[xr] = yr;
} else if(yd < xd) {
roots[yr] = xr;
} else {
roots[yr] = xr;
++ranks[xr];
}
}
// function link(root, root2){
// var root = node[i]
// while(root.parent){
// root = root.parent;
// }
// var root2 = node[j];
// while(root2.parent){
// root2 = root2.parent;
// }
// if(root2 != root){
// if(root.rank > root2.rank){
// root2.parent = root;
// }else{
// root.parent = root2;
// if(root.rank == root2.rank){
// root2.rank++
// }
// root = root2;
// }
// var node2 = node[j];
// while(node2.parent){
// var temp = node2;
// node2 = node2.parent;
// temp.parent = root;
// }
// var node2 = node[i];
// while(node2.parent){
// var temp = node2;
// node2 = node2.parent;
// temp.parent = root;
// }
// }
// }
// this is a port of something from libccv which
// is a port of something from opencv which is
// a port of an algorithm in some textbook from
// somewhere
// it has rough functional parity with ccv_array_group
// and cvSeqPartition and the union-find algorithm
// except rather than returning a list of list
// indicies in case one is so inclined to construct
// a list, it actually just returns the list
// this is a quadratic algorithm as far as I'm aware
// which means that the is_equal function will be called
// n(n - 1) times where n is the length of your elements
// array. For things with large numbers of elements,
// this can become very slow.
// it might be wise because of this to inform the
// algorithm with some notion of geometry. i.e.
// "these elements are really really far apart
// so they probably don't have anything to do with
// each other so lets just kind of let them do
// their thing and have incestuous relations with
// people closer to them"
function equivalence_classes(elements, is_equal){
var node = []
for(var i = 0; i < elements.length; i++){
node.push({
parent: 0,
element: elements[i],
rank: 0
})
}
for(var i = 0; i < node.length; i++){
var root = node[i]
while(root.parent){
root = root.parent;
}
for(var j = 0; j < node.length; j++){
if(i == j) continue;
if(!is_equal(node[i].element, node[j].element)) continue;
var root2 = node[j];
while(root2.parent){
root2 = root2.parent;
}
if(root2 != root){
if(root.rank > root2.rank){
root2.parent = root;
}else{
root.parent = root2;
if(root.rank == root2.rank){
root2.rank++
}
root = root2;
}
var node2 = node[j];
while(node2.parent){
var temp = node2;
node2 = node2.parent;
temp.parent = root;
}
var node2 = node[i];
while(node2.parent){
var temp = node2;
node2 = node2.parent;
temp.parent = root;
}
}
}
}
var index = 0;
var clusters = [];
for(var i = 0; i < node.length; i++){
var j = -1;
var node1 = node[i]
while(node1.parent){
node1 = node1.parent
}
if(node1.rank >= 0){
node1.rank = ~index++;
}
j = ~node1.rank;
if(clusters[j]){
clusters[j].push(elements[i])
}else{
clusters[j] = [elements[i]]
}
}
return clusters;
}
// selection is i guess a singleton because otherwise it's
// a little confusing when you can select text from more than
// one picture on a single page, especially when it doesn't
// quite jive with the fact that normal selections are kinda
// not multi-selections.
var sel = {
img: null,
stack: [],
start: null,
end: null,
deselect_time: 0
}
// onselectionchange is only supported on chrome and restricting
// the app to the other way of detecting prevents browser dependent
// selection detection bugs from arising
// document.addEventListener('selectionchange', change_select)
window.addEventListener('resize', handle_resize)
// document.addEventListener('paste', function(e){
// console.log('paste', e.clipboardData.getData('text'))
// console.log('cmd', document.execCommand('copy'));
// })
document.addEventListener('copy', function(e){
// if(check_selection()) modify_clipboard();
// get_selection(sel, images[sel.img.src]).length
if(sel.start || sel.stack.length){
// TODO: better check for whether or not we have a thing
modify_clipboard()
// here we should kick off the whole
// background page clipboard monitoring
// thing so that when someone copies
// something while it isn't yet OCR'd
// it'll be all aight as long as it's
// before the thing is done
var image = im(sel.img);
var block = extract_selection(sel, image);
if(block.incomplete > 0){
broadcast({
type: 'clipwatch',
id: image.id
})
}
if(/\.(jpe?g|png|gif)$/i.test(location.pathname)){
// this is a weird chrome thing
// where you cant copy anything from
// an image except like raw text
setTimeout(function(){
broadcast({
type: 'copy',
id: image.id,
text: block.text
})
}, 10)
}
setTimeout(function(){
// check if not tesseract
var image = im(sel.img)
selected_regions().forEach(function(region){
var ocr = image.ocr[region.id];
if(ocr._engine == 'ocrad'){
// walp shitty local ocr
// error_message(image, region, 'walp you have a shitty ocr engine')
}
// if(!(ocr && ocr._engine)) return;
})
}, 10)
}
})
function collect_contexts(){
for(var i in images){
var image = images[i];
if(image.el && image.el.getBoundingClientRect){
var dim = image.el.getBoundingClientRect()
if(dim.width == 0 || dim.height == 0){
dispose_overlay(image.el)
delete images[get_id(image.el)]
}
}
if(image.overlay && image.overlay.childNodes.length == 0){
dispose_overlay(image.el)
}
}
var container = get_container();
if(container.childNodes.length == 0){
if(container.parentNode){
container.parentNode.removeChild(container)
}
}
}
function handle_resize(e){
if(sel.img){
var image = im(sel.img);
update_selection()
}
collect_contexts()
for(var id in images){
update_overlay(images[id].el);
}
}
var text_layer;
function modify_clipboard(){
if(!text_layer){
text_layer = document.createElement('pre')
}
text_layer.className = 'project_naptha_text_layer'
text_layer.innerHTML = ''
text_layer.style.top = text_layer.style.left = '-100px'
text_layer.style.width = text_layer.style.height = '10px'
text_layer.style.overflow = 'hidden'
text_layer.style.position = 'absolute'
// for some reason, when you copy from something
// which is an element outside of the document body
// then it strips out all the newlines
document.body.appendChild(text_layer)
var range = document.createRange();
var text = '<error extracting text ' +(new Date)+ '>'
if(sel.img){
var image = im(sel.img);
text = extract_selection(sel, image).text
}
if(text){
text_layer.textContent = text;
text_layer.focus();
range.selectNodeContents(text_layer)
var selection = window.getSelection()
selection.removeAllRanges();
selection.addRange(range);
}else{
remove_textlayer()
}
}
function push_sel(start, end){ sel.stack.push([start, end, Date.now()]) }
function clear_selection(){
// console.trace()
if(sel.img)
render_selection(sel.img, [], {});
sel.stack = [];
// sel.img = new_img;
// modify_clipboard()
}
function change_select(){
if(!check_selection()){
clear_selection();
setTimeout(remove_textlayer, 0)
}
}
function remove_textlayer(){
if(text_layer){
if(text_layer.parentNode)
text_layer.parentNode.removeChild(text_layer);
}
}
function check_selection(){
var selection = window.getSelection();
if(selection.rangeCount != 1) return false;
var node = selection.anchorNode;
var container = get_container();
do {
if(node == text_layer || node == container){
return true;
}
} while (node = node.parentNode);
return false;
// return (selection.rangeCount == 1 && (selection.anchorNode.parentNode == text_layer || selection.anchorNode == text_layer))
}
function do_lookup(image){
image.lookup = {
loading: Date.now()
}
var xhr = new XMLHttpRequest();
if(window.XDomainRequest){
xhr = new XDomainRequest()
}
xhr.open('GET', global_params.apiroot + 'lookup?url=' + encodeURIComponent(image.src), true)
xhr.send()
xhr.onerror = function(){
image.lookup = {
error: true
}
}
xhr.onload = function(){
image.lookup = JSON.parse(xhr.responseText)
image.lookup.finished = Date.now()
// console.log('finished with lookup', image.lookup)
}
}
function update_selection(){
if(!sel || !sel.img) return;
var image = im(sel.img)
var selection = get_selection(sel, image);
if(!image.lookup && navigator.onLine && !session_params.no_lookup){ // try again later if you're online
do_lookup(image)
}
render_selection(sel.img, selection, image.params)
if(selection.length > 0){
selection.forEach(function(pair){
var col = (pair[0] && pair[0].region) || pair[1].region;
ocr_region(image, col)
})
}else if(sel.start){
var col = get_cursor(sel.start).region;
ocr_region(image, col)
}
}
// there's a shortcut syntax where you omit various parts of the cursor
// and then it magically figures it all out for you because it's like really
// clever and shit, but it's kind of a pain in the ass to make sure that
// everything works for the edge cases that this syntax creates
function fix_squig(start, end){
var col = (start && start.region) || end.region;
if(!start || !start.letter)
start = { letter: { x0: col.x0 }, line: col.lines[0], region: col };
if(!end || !end.letter)
end = { letter: { x1: col.x1 }, line: col.lines[col.lines.length - 1], region: col };
return [start, end, col]
}
// every squig can be decomposed into either a single rectangle or a combination
// of three rectangles (two adjacent lines is a case which is dealt with by creating
// a zero-or-negative height middle rectangle which gets immediately removed by a
// simple arithmetic sanity check filter)
function rectify_squig(start, end){
var _ = fix_squig(start, end),
start = _[0],
end = _[1],
col = _[2];
function sanity_check(box){ return box.x1 > box.x0 && box.y1 > box.y0 }
if(start.line && start.line == end.line){
return [ { x0: start.letter.x0, y0: start.line.y0, x1: end.letter.x1, y1: start.line.y1 } ].filter(sanity_check)
}else{
return [
{ x0: start.letter.x0, y0: start.line.y0, x1: col.x1, y1: start.line.y1 },
{ x0: col.x0, y0: start.line.y1, x1: col.x1, y1: end.line.y0 },
{ x0: col.x0, y0: end.line.y0, x1: end.letter.x1, y1: end.line.y1 }
].filter(sanity_check)
}
}
function box_intersect(a, b){
var width = Math.min(a.x1, b.x1) - Math.max(a.x0, b.x0),
height = Math.min(a.y1, b.y1) - Math.max(a.y0, b.y0);
return (width > 0 && height > 0) ? width * height : 0;
}
function selected_regions(){
var image = im(sel.img);
return get_selection(sel, image).map(function(pair){
var col = (pair[0] && pair[0].region) || pair[1].region;
return image.regions.filter(function(region){
return region.id == col.id;
})[0]
});
}
// I'm reasonably sure that the secret to writing good code is just rewriting early and often
// I think things generally get better each time you do it.
function get_selection(sel, image){
var squigs = sel.stack.map(function(e){
// here we need to clone everything to make sure
// that our changes don't change the selection stack
// for like reals
var start = e[0],
end = e[1];
// also if things are bass-ackwards then we should
// switch dem so they's proper and shit
if(start && end){
if(start.line == end.line){
if(end.letter.x1 < start.letter.x0){
return [end, start]
}
}else if(end.line.y1 < start.line.y0){
return [end, start]
}
}
// return things because errythin's a'ight
return [start, end]
}).concat(current_selection(sel, image)).filter(function(sel){
var start = sel[0], end = sel[1]
var sel_col = (start && start.region) || end.region;
var regions = virtualize_region(image, image.regions)
// check to make sure that everything in the selection exists
var col_exists = regions.some(function(col){
return sel_col == col
})
if(!col_exists && sel_col){
// find closest matching analagous regions, usually
// a superset, and use that as the parent region
// there may be pathological cases with the layout
// which really screw this thing up, but in general
// this should provide for a better user experience
// when dealing with regions that may expand
var col_can = regions.map(function(col){
return [box_intersect(sel_col, col), col]
}).filter(function(n){
return n[0] > 0
}).sort(function(a, b){
return b[0] - a[0]
}).map(function(n){
return n[1]
});
if(col_can.length){
// var candidate = col_can[0] && (col_can[0].virtual || col_can[0]);
var candidate = col_can[0]
if(start && start.region) start.region = candidate;
if(end && end.region) end.region = candidate;
return true
}
// console.log(start, end, candidates)
}
// TODO: upgrade regions to analagous extant regions
return col_exists
});
// what is this algorithm? O(n^4) whatever
// actually it's probably still just O(n^2)
// in spite of all this nesting because the
// a_rects and b_rects comparison is probably
// on average constant complexity because
// it's either 1 or 3, i.e. O(n^2 * m) where
// 1 <= m <= 9
function cursor_adjacent(a, b){
if(a.line != b.line) return false;
var line = a.line;
var lax = -1, lbx = -1;
var wax = -1, wbx = -1;
line.words.forEach(function(word, wi){
if(word == a.letter) wax = wi;
if(word == b.letter) wbx = wi;
word.letters.forEach(function(letter, li){
if(letter == a.letter) lax = li;
if(letter == b.letter) lbx = li;
})
})
if(lax != -1 && lbx != -1){
return (Math.abs(lbx - lax) == 1)
}
if(wax != -1 && wbx != -1){
return (Math.abs(wbx - wax) == 1)
}
return false
}
var merged = equivalence_classes(squigs, function(a, b){
var a_col = (a[0] && a[0].region) || a[1].region;
var b_col = (b[0] && b[0].region) || b[1].region;
// if two squigs arent of the same region, then they cant
// possibly be considered intersecting because otherwise the
// entire world would implode or something
if(a_col != b_col) return false;
// decompose each selection range into a squig, which is either
// a single rectangle or a combination of three
var a_rects = rectify_squig(a[0], a[1]);
var b_rects = rectify_squig(b[0], b[1]);
// perhaps there should be a squig adjacency detector
// because maybe we should merge adjacent selections,
// i.e. things that start immediately after another starts
// note that this isn't something that sublime text appears
// to do but of course the paradigm for editing is different
// from selecting.
var does_intersect = a_rects.some(function(a_rect){
return b_rects.some(function(b_rect){
return box_intersect(a_rect, b_rect)
})
});
// if(!does_intersect){
// var a_squig = fix_squig(a[0], a[1]),
// b_squig = fix_squig(b[0], b[1])
// var is_adj = cursor_adjacent(a_squig[0], b_squig[1]) || cursor_adjacent(a_squig[1], b_squig[0])
// return is_adj
// }
return does_intersect
})
// so now we have a list of selection regions which happen
// to at least slightly intersect with each other, now we just
// iterate through each of these groups and determine the true
// bounding box of these by sorting them first by vertically by line
// and then horizontally by letter bounds
return merged.map(function(ranges){
if(ranges.length == 1) return ranges[0];
// using a sort to pick out the first element is kinda handy
// but it's O(n log n) rather than the achievable O(n) but
// doing it this way is cleaner. Perhaps there should be an
// Array::first(fn) which functions identically to
// Array::sort(fn)[0] but operates in O(n) time.
var sorted_starts = ranges.map(function(e){
// return e[0] // starts
return fix_squig(e[0], e[1])[0] // starts
}).sort(function(a_start, b_start){
if(a_start.line.y0 < b_start.line.y0){
return -1
}else if(a_start.line.y0 == b_start.line.y0){
if(a_start.letter.x0 < b_start.letter.x0){
return -1
}
}
return 1
})
var sorted_ends = ranges.map(function(e){
// return e[1] // ends
return fix_squig(e[0], e[1])[1] // ends
}).sort(function(a_start, b_start){
if(a_start.line.y1 > b_start.line.y1){
return -1
}else if(a_start.line.y1 == b_start.line.y1){
if(a_start.letter.x1 > b_start.letter.x1){
return -1
}
}
return 1
})
return [sorted_starts[0], sorted_ends[0]]
})
}
// this is the squig which results from pixel range of the selection
// that is, it deals with a start and end which aren't the kind of abstract
// high level entities which get_selection and render_selection deal with,
// but rather x and y coordinates.
function current_selection(sel, image){
if(!image) return [];
var params = image.params;
var start = sel.start && get_cursor(sel.start)
var end = sel.end && get_cursor(sel.end)
if(!start || !end) return [];
function swap(){var t = start; start = end; end = t; }
var selection = [];
var now = Date.now()
var regions = virtualize_region(image, image.regions)
if(start.region == end.region && start.line && end.line){
if(start.line == end.line){
// if(sel.start.X > sel.end.X) swap();
if(start.letter.x0 >= end.letter.x1) swap();
}else{
if(sel.start.Y > sel.end.Y) swap();
}
// return [[start, end]]
selection.push([start, end, now])
}else if((!start.region || !end.region) || start.region != end.region){
if(end.region && start.region){
// if(start.region.y0 > end.region.y1 || start.region.x0 > end.region.x1){
// swap();
// }
if(start.region.x0 > end.region.x1){
swap()
}
if(start.region.y0 > end.region.y1){
swap();
}
}else if(sel.start.Y > sel.end.Y){
swap()
}
// intersect the set of all regions and the rectangle bounding the selection
// sort such that the first element is the region which the selection
// start lies on and the last element is the region which the selection
// end lies on
// perhaps make this a complex sorting routine that involves merging
// things with equivalence_classes into real regions rather than
// whatever misnomer regions here are (since they actually represent
// paragraphs) and then do a hierarchical kind of sort which goes
// top to bottom and left to right
// render all the intermediate regions as fully selected
// console.error("algorithm can't handle intraregion selection yet")
var intersector = {
x0: Math.min(sel.start.X, sel.end.X) * params.scale - 1,
x1: Math.max(sel.start.X, sel.end.X) * params.scale + 1,
y0: Math.min(sel.start.Y, sel.end.Y) * params.scale - 1,
y1: Math.max(sel.start.Y, sel.end.Y) * params.scale + 1,
}
// var path = image.regions.filter(function(region){
// var width = Math.min(intersector.x1, region.x1) - Math.max(intersector.x0, region.x0),
// height = Math.min(intersector.y1, region.y1) - Math.max(intersector.y0, region.y0);
// return width > 0 && height > 0
// })
var x0 = Infinity, y0 = Infinity, x1 = 0, y1 = 0;
regions.forEach(function(region){
var width = Math.min(intersector.x1, region.x1) - Math.max(intersector.x0, region.x0),
height = Math.min(intersector.y1, region.y1) - Math.max(intersector.y0, region.y0);
if(width > 0 && height > 0){
x0 = Math.min(x0, region.x0)
y0 = Math.min(y0, region.y0)
x1 = Math.max(x1, region.x1)
y1 = Math.max(y1, region.y1)
}
})
var path = regions.filter(function(region){
var width = Math.min(x1, region.x1) - Math.max(x0, region.x0),
height = Math.min(y1, region.y1) - Math.max(y0, region.y0);
return width > 0 && height > 0
})
path.forEach(function(region){
if(region == start.region){
selection.push([start, null, now])
}else if(region == end.region){
selection.push([null, end, now])
}else{
selection.push([{region: region}, null, now])
}
})
// console.log(selection)
}
// console.log(selection[0])
return selection;
}
// TODO: replace this with something that renders to SVG
// because that's better, right?
function render_selection(img, selection, params){
// var paper = img.naptha_highlight_canvas;
var image = im(img)
if(selection.length == 0){
layer_clear(img, 'highlight')
if(image.flame){
image.flame.stopEmit = true;
}
return
}
// unfinished selected columns should be finished asap
var chunks = [];
selection.forEach(function(pair){
var col = (pair[0] && pair[0].region) || pair[1].region;
if(col.finished) return; // if its finished its ok
[].concat(
to_chunks(col.y0, image),
to_chunks(col.y1, image),
to_chunks(col.y0 - image.params.chunk_size, image),
to_chunks(col.y1 + image.params.chunk_size, image)
).forEach(function(chunk){
if(chunks.indexOf(chunk) == -1 &&
image.chunks.indexOf(chunk) == -1)
chunks.push(chunk);
})
})
if(chunks.length > 0){
// console.log('unfinished queueing', chunks)
broadcast({
type: 'qchunk',
id: image.id,
time: Date.now(),
chunks: chunks
})
}
// if(!end.region) return;
var r = 2; // this is the arc radius for the squigs
var lastX, lastY, _ = {}, stack = [];
// all draw ops are stuck into a stack rather than drawing directly
// onto some canvas context because we want to process these ops and
// calculate the minimum bounding box that we can make the canvas
function setRotation(angle, x, y){ stack.push(['setrot', [angle, x, y]]) }
function closePath(){ stack.push(['closePath']) }
function setFill(color){ stack.push(['setfill', [color]]) }
function setStroke(color){ stack.push(['setstroke', [color]]) }
function moveTo(x, y){ stack.push(['moveTo', [lastX = x, lastY = y]]) }
function scale(x, y){ stack.push(['scale', [x, y]]) }
function setLineDash(dash){ stack.push(['dash', dash])}
function arcTo(x, y){
stack.push(['arcTo', [lastX, lastY, // we actually lag behind by one every time
lastX = (x == _ ? lastX : x), lastY = (y == _ ? lastY : y), // underscore to reuse
r]]) // plunk the radius onto there (TODO: retina scaling?)
}
// in case you didn't know or guess, this is what a squig looks like
//
// /----------------\
// | |
// /----------------/ |
// | |
// | |
// | /--------------------/
// | |
// \------------/
//
function selectionSquig(start, end){
// make some dummy cursors if either is null
var region = (start && start.region) || end.region;
if(!start || !start.letter){
start = {
region: region,
letter: { x0: region.x0 },
line: region.lines[0]
}
}
if(!end || !end.letter){
end = {
region: region,
letter: { x1: region.x1 },
line: region.lines[region.lines.length - 1]
}
}
var xpad = 1, ypad = 3;
if(start.line != end.line){
// if the top of the line on the bottom is higher than the bottom of the line on the top
// this is meant to deal with tightly adjacent lines
if((end.line.cy - end.line.lineheight / 2 - ypad) - (start.line.cy + start.line.lineheight / 2 + ypad) < 1){
// and the end of the bottom is left of the top
if(end.letter.x1 <= start.letter.x0){
// selectionSquig(start, { line: start.line, letter: { x1: start.line.x1 }})
// selectionSquig({ line: end.line, letter: { x0: end.line.x0 }}, end)
selectionSquig(start, { line: start.line, letter: { x1: region.x1 }})
selectionSquig({ line: end.line, letter: { x0: region.x0 }}, end)
return;
}
// if(Math.abs(end.line.y0 - start.line.y0) < 2 &&
// Math.abs(end.line.x0 - start.line.x0) < 2 &&
if(end.line.x0 >= start.line.x1){
selectionSquig(start, { line: start.line, letter: { x1: start.line.x1 }})
selectionSquig({ line: end.line, letter: { x0: end.line.x0 }}, end)
return;
}
}
}
var angle = region.angle;
// if(Math.abs(angle) < 0.01) angle = 0;
// two pixels
if(Math.abs(angle * region.width / params.scale) < 2) angle = 0;
setRotation(angle, region.cx, region.cy)
// scale(1 / Math.cos(region.angle), 1)
scale(1 / Math.cos(region.angle), 1 / Math.cos(region.angle))
if(session_params.flame_on){
setFill('rgba(255, 255, 0, 0.1)')
}else{
if(region.virtual){
setFill('rgba(0, 173, 255, 0.3)') // light blue-green
}else{
setFill('rgba(0,100,255,0.4)') // blue
}
if(region.finished){
setLineDash([])
}else{
setLineDash([4,4])
}
// setFill('rgba(0,100,255,0.1)') // blue
// var frac = (Math.sin((Date.now() % 2000) / 2000 * 2 * Math.PI) + 1) / 2;
// function i(a, b){ return Math.round((b - a) * frac + a); }
// var str = 'rgba(' + [
// // 0,
// // i(100, 231),
// // i(255, 139),
// // i(0, 255),
// // i(100, 133),
// // i(255, 0),
// i(50, 0),
// i(100, 163),
// i(255, 240),
// 0.4
// ].join(',') +')';
// var str = 'hsla(' + [i(200, 240), '100%', '50%', 0.4].join(',') + ')'
// console.log(str)
// var opacity = frac * 0.2 + 0.3;
// setFill(str)
// setFill('rgba(0,100,255,' + opacity +')') // blue
// rgba(0, 69, 175, 0.4)
// setFill('rgba(0, 231, 139, 0.4)') // green
}
// if(image.ocr && region.id in image.ocr && image.ocr[region.id].finished){
// setStroke('rgba(255,0,0,0.8)')
// }else
if(region.lines[0].direction == -1){
setStroke('rgba(40,40,40,0.8)')
}else{
setStroke('rgba(211, 229, 255, 0.8)')
}
// moveTo(start.letter.x0 + r * 2, start.line.y0 - ypad)
moveTo(start.letter.x0 + r * 2, start.line.cy - start.line.lineheight / 2 - ypad)
if(start.line != end.line){
arcTo(region.x1 + xpad, _)
// arcTo(_, end.line.y0 - ypad)
arcTo(_, end.line.cy - end.line.lineheight / 2 - ypad) // here
}
if(Math.abs(region.x1 - end.letter.x1) > 3 * r || start.line == end.line){
arcTo(end.letter.x1 + xpad, _)
}
// arcTo(_, end.line.y1 + ypad)
arcTo(_, end.line.cy + end.line.lineheight / 2 + ypad)
if(start.line != end.line){
arcTo(region.x0 - xpad, _)
// arcTo(_, start.line.y1 + ypad)
arcTo(_, start.line.cy + start.line.lineheight / 2 + ypad) //here
}
// if we're really close to the edge, just declare it a rounding error
if(Math.abs(region.x0 - start.letter.x0) > 3 * r || start.line == end.line){
arcTo(start.letter.x0 - xpad, _)
}
// arcTo(_, start.line.y0 - ypad)
arcTo(_, start.line.cy - start.line.lineheight / 2 - ypad)
if(start.line == end.line){
arcTo(end.letter.x1 + xpad, _);
}else{
arcTo(region.x1 + xpad, _);
}
closePath()
}
selection.forEach(function(pair){ selectionSquig(pair[0], pair[1]) })
var cx = 0, cy = 0, theta = 0;
var x0 = Infinity, y0 = Infinity, x1 = 0, y1 = 0;
var ys_rot = 1, xs_rot = 1;
// we need to calculate the bounding box of the selection squigs
// and do some transformations so that it's aware of the rotations
// and translations of random things.
function trm(x, y){
x -= cx; y -= cy; // translate
var nx = ((x * Math.cos(theta) - y * Math.sin(theta))) * xs_rot + cx, // rotate & translate
ny = ((x * Math.sin(theta) + y * Math.cos(theta))) * ys_rot + cy; // rotate & translate
x0 = Math.min(x0, nx); y0 = Math.min(y0, ny);
x1 = Math.max(x1, nx); y1 = Math.max(y1, ny);
}
stack.forEach(function(op){
var args = op[1]; op = op[0];
if(op == 'setrot'){
cx = args[1]; cy = args[2];
theta = args[0];
}else if(op == 'moveTo'){
trm(args[0], args[1])
}else if(op == 'arcTo'){
trm(args[0], args[1]);
trm(args[2], args[3])
}else if(op == 'scale'){
xs_rot = args[0]; ys_rot = args[1]
}
})
// this get slightly weird with non-integer device pixel ratio
// todo: change x0, y0, x1, y1 so that width * dpr is always an int
// TODO: also that thing with safari and its backingstore
var dpr = window.devicePixelRatio;
var ratio_x = img.width / img.naturalWidth / params.scale,
ratio_y = img.height / img.naturalHeight / params.scale;
// increase the margins because why not
var margin = 5;
x0 = Math.round(x0 * ratio_x - margin) ; y0 = Math.round(y0 * ratio_y - margin);
x1 = Math.round(x1 * ratio_x + margin) ; y1 = Math.round(y1 * ratio_y + margin);
// var layout = image_layout(img)
var paper = layer(img, 'highlight', x0, y0)
// document.body.appendChild(paper)
var sfx = paper.getContext('2d');
paper.width = Math.round((x1 - x0) * dpr)
paper.height = Math.round((y1 - y0) * dpr) //sty('height') * window.devicePixelRatio
paper.style.width = (x1 - x0) + 'px'
paper.style.height = (y1 - y0) + 'px'
sfx.translate(-dpr * x0, -dpr * y0)
sfx.save()
sfx.lineWidth = 1;
sfx.beginPath()
var cx = 0, cy = 0;
var rx = ratio_x * dpr,
ry = ratio_y * dpr;
var ys_rot = 1, xs_rot = 1;
stack.forEach(function(op){
var args = op[1]; op = op[0];
if(op == 'moveTo'){
sfx.moveTo((rx * args[0] - cx) * xs_rot, (ry * args[1] - cy) * ys_rot)
}else if(op == 'arcTo'){
sfx.arcTo((rx * args[0] - cx) * xs_rot, (ry * args[1] - cy) * ys_rot,
(rx * args[2] - cx) * xs_rot, (ry * args[3] - cy) * ys_rot,
args[4] * dpr)
}else if(op == 'closePath'){
sfx.closePath()
sfx.fill(); sfx.stroke()
sfx.beginPath()
sfx.restore(); sfx.save()
}else if(op == 'setrot'){
sfx.translate(cx = rx * args[1],
cy = ry * args[2]);
sfx.rotate(args[0])
}else if(op == 'setfill'){
sfx.fillStyle = args[0]
}else if(op == 'setstroke'){
sfx.strokeStyle = args[0]
}else if(op == 'scale'){
xs_rot = args[0]; ys_rot = args[1]
}else if(op == 'dash'){
if(sfx.setLineDash)
sfx.setLineDash(args);
}
})
if(window.CanvasFlame && session_params.flame_on){
var flame_height = 90; // allow the flame to extend beyond the canvas
// var flamecanvas = layer(img, 'pun', x0, y0 - flame_height)
if(!image.flame){
var flamecanvas = layer(img, 'pun', 0, -flame_height)
flamecanvas.width = img.width
flamecanvas.height = img.height + flame_height
image.flame = new CanvasFlame(flamecanvas);
}
var flame = image.flame;
// console.log(flame)
// if(!paper.flame){
// var canvas = document.createElement('canvas')
// canvas.width = img.width
// canvas.height = img.height + flame_height
// paper.flame = new CanvasFlame(canvas);
// document.body.appendChild(paper.flame.canvas)
// paper.flame.canvas.style.position = 'absolute'
// paper.flame.canvas.style.top = (layout.top - flame_height) + 'px'
// paper.flame.canvas.style.left = layout.left + 'px'
// paper.flame.canvas.style.userSelect = 'none'
// paper.flame.canvas.style.pointerEvents = 'none'
// paper.flame.canvas.className = 'project_naptha_meet_pun'
// }
// Math.round(img.naturalHeight * params.scale)
flame.orig_context.clearRect(0, 0, flame.width, flame.height)
flame.orig_context.drawImage(paper, x0, y0 + flame_height, x1 - x0, y1 - y0);
flame.updateEmbers()
flame.stopEmit = false;
flame.start(-1)
}
}
var lastClientX = 0, lastClientY = 0;
var lastPageX = 0, lastPageY = 0, lastPageT;
var meanDX = 0, meanDY = 0;
var lastPrediction = 0;
document.addEventListener('mousemove', track_mouse, true)
document.addEventListener('mousedown', begin_select, true)
document.addEventListener('mouseup', finish_select, true)
document.addEventListener('keydown', key_select, false)
document.addEventListener('click', function(e){
if(Date.now() - sel.deselect_time < 100){
e.preventDefault()
e.stopPropagation()
e.stopImmediatePropagation()
}
if(!valid_image(e.target)) return;
if(e.button != 0) return;
var mouse = get_mouse(e); // update the cursor tracking yo
if(!mouse.img) return; // well we cant do it if we cant find a pic
var cursor = get_cursor(mouse);
if(cursor.letter){
e.preventDefault()
e.stopPropagation()
e.stopImmediatePropagation()
}
}, true)
Element.prototype.has = function(cls){
return this.className.split(" ").indexOf(cls) != -1
}
function begin_select(e){
if(!menu_levels.some(function(menu){ return is_child(e.target, menu) })){
// target is not in any menus
create_menu([], 0)
}
// the context menu overlay stuff
if(e.target.has('contextmenu_overlay')){
e.preventDefault()
return;
}
// this function is only for like when you're on an image
if(!valid_image(e.target)){
// this might be the beginning of a selection somewhere else
// on the page, so we should like check if the selection
// changes because firefox doesn't support a selectionchange
// event. also we only need to do this when the image
// selection actually exists
if(sel.start || sel.stack.length > 0){
requestAnimationFrame(change_select);
}
return;
}
var mouse = get_mouse(e); // update the cursor tracking yo
if(!mouse.img) return; // well we cant do it if we cant find a pic
if(e.button != 0) return; // must be a left click or whatever
predict_mouse()
var img = mouse.img;
if(sel.img != img){
clear_selection()
}
sel.img = img;
var image = im(img);
if(!image) return;
// var sel = mouse.sel;
update_pointer(mouse, image); // why not update the cursor
var cursor = get_cursor(mouse);
// console.log('blah', cursor)
if(e.detail == 1){ // number of clicks
if(cursor.letter){
if(!e.shiftKey) clear_selection();
sel.start = mouse;
sel.end = null;
e.preventDefault()
e.stopPropagation()
e.stopImmediatePropagation()
}else if(!e.shiftKey){
clear_selection()
}
}else if(e.detail > 1){ // multiple clicks
if(e.detail == 2 && cursor.word){
if(!e.shiftKey) clear_selection();
if(cursor.line.words.length == 1){
push_sel(cursor, cursor) // cursor cursor on the wall, which is the shortest line of all?
}else{
push_sel(
{region: cursor.region, line: cursor.line, letter: cursor.word.letters[0]},
{region: cursor.region, line: cursor.line, letter: cursor.word.letters[cursor.word.letters.length - 1]}
)
}
}else if(e.detail == 3 && cursor.region){
if(!e.shiftKey) clear_selection();
push_sel({ region: cursor.region })
}else{
if(e.detail == 10) session_params.flame_on = true;
if(e.detail == 42) alert("Hi. My name is dug. I have just met you. I love you.");
}
}
// sel.img = mouse.img
update_selection()
requestAnimationFrame(modify_clipboard)
}
function key_select(e){
if(menu_levels.length){
menu_keyhandle(e)
}
if(!sel.img) return;
var image = im(sel.img);
if(sel.stack.length == 0) return;
var end = sel.stack[sel.stack.length - 1][1]
function get_letters(line){ return [].concat.apply([], line.words.map(function(e){ return e.letters })) }
if(e.keyIdentifier == 'Down' && e.shiftKey){
// console.log('\\/')
var index = end.region.lines.indexOf(end.line)
var line = end.region.lines[index + 1];
if(line){
end.line = line;
end.letter = get_letters(line).sort(function(a, b){
return Math.abs(a.x0 - end.letter.x0) - Math.abs(b.x0 - end.letter.x0)
})[0]
}
e.preventDefault()
}else if(e.keyIdentifier == 'Up' && e.shiftKey){
// console.log('/\\')
var end = sel.stack[sel.stack.length - 1][1]
var index = end.region.lines.indexOf(end.line)
var line = end.region.lines[index - 1];
if(line){
end.line = line;
end.letter = get_letters(line).sort(function(a, b){
return Math.abs(a.x0 - end.letter.x0) - Math.abs(b.x0 - end.letter.x0)
})[0]
}
e.preventDefault()
}else if(e.keyIdentifier == 'Right' && e.shiftKey){
var end = sel.stack[sel.stack.length - 1][1]
var eletters = get_letters(end.line);
var index = eletters.indexOf(end.letter)
if(index < eletters.length - 1 && eletters[index + 1]){
end.letter = eletters[index + 1]
}
// console.log('->', end.letter, index, end.line.letters)
e.preventDefault()
}else if(e.keyIdentifier == 'Left' && e.shiftKey){
var end = sel.stack[sel.stack.length - 1][1]
var eletters = get_letters(end.line);
var index = eletters.indexOf(end.letter)
if(index > 0 && eletters[index - 1]){
end.letter = eletters[index - 1]
}
// console.log('<-', end.letter, index, end.line.letters)
e.preventDefault()
}else if(String.fromCharCode(e.keyCode) == 'Z' && (e.ctrlKey || e.metaKey)){
sel.stack.pop(); // get rid of most recent thing
e.preventDefault()
}else if(String.fromCharCode(e.keyCode) == 'A' && (e.ctrlKey || e.metaKey)){
image.regions.forEach(function(col){
push_sel({region: col})
})
e.preventDefault()
}
update_selection()
}
function finish_select(e){
if(sel.start){
var img = sel.img;
var image = im(img);
if(valid_image(e.target)){
var mouse = get_mouse(e);
update_pointer(mouse, image); // why not update the cursor
}
sel.stack = sel.stack.concat(current_selection(sel, image))
sel.start = null
sel.deselect_time = Date.now()
update_selection()
requestAnimationFrame(modify_clipboard)
// TODO: create a general API for queuing
// certain actions such that a function
// is only called once in a given
// span of time
setTimeout(collect_contexts, 100)
e.preventDefault()
e.stopPropagation()
e.stopImmediatePropagation()
}
}
/*
It's April 16, 2014.
It's been six months since I started this project.
Just under two years after I first came up with the idea.
It's weird to think of time as something that happens,
to think of code as something that evolves. And it may
be obvious to recognize that code is not organic, that
it changes only in discrete steps as dictated by some
intelligence's urging, but coupled with a faulty and
mortal memory, its gradual slopes are indistinguishable
from autonomy.
Hopefully, this project is going to launch soon. It
looks like there's actually a chance that this will
be able to happen.
The proximity of its launch has kind of been my own little
perpetual delusion. During the hackathon, I announced that
it would be released in two weeks time.
When winter break rolled by, I had determined to finish
and release before the end of the year 2013.
This deadline rolled further way, to the end of January
term, IAP as it is known. But like all the artificial
dates set earlier, it too folded against the tides of
procrastination.
I'll spare you February and March, but they too simply
happened with a modicum of dread. This brings us to the
present day, which hopefully will have the good luck to
be spared from the fate of its predecessors.
After all, it is the gaseous vaporware that burns.
*/
function update_pointer(mouse, image){
var img = mouse.img,
imgX = mouse.X,
imgY = mouse.Y;
if(image && image.params){
var params = image.params;
var sX = imgX * params.scale,
sY = imgY * params.scale;
var linpad = 3;
var regpad = 10;
var vreg = virtualize_region(image, image.regions);
// console.log(image, frac_text)
// if it's a link, the frac text is big and the area is small, then
// disable naptha so that you have somewhere to click
var in_region = vreg.some(function(region){
return (sX + regpad >= region.x0 && sX - regpad <= region.x1 &&
sY + regpad >= region.y0 && sY - regpad <= region.y1);
});
var has_text = vreg.some(function(region){
return region.lines.some(function(line){
return (sX + linpad >= line.x0 && sX - linpad <= line.x1 &&
sY + linpad >= line.y0 && sY - linpad <= line.y1);
})
})
if(has_text && mouse.inside){
// img.style.cursor = 'text'
// this is so it doesn't interfere
img.setAttribute('naptha_cursor', 'text')
}else if(in_region && mouse.inside){
img.setAttribute('naptha_cursor', 'region')
}else{
img.removeAttribute('naptha_cursor')
// img.style.cursor = ''
}
}
}
function track_mouse(e){
var mouse = get_mouse(e)
// DEBUG DEBUG DEBUG
// var cursor = get_cursor(mouse)
// if(cursor.letter){
// // console.log(cursor.letter, cursor.dist)
// console.log(cursor.line.lettersize, cursor.line.xheight, cursor.line.height, cursor.line.thickness, cursor.line.weight)
// }
var now = Date.now()
if(now - lastPrediction > 100){
// on occasion we should predict where the mouse is and
// where it will be so that we can preemptively and
// intelligently determine what exactly we'd like to
// process and in what order
requestAnimationFrame(predict_mouse);
lastPrediction = now;
}
// update the cursor if we find the current position
if(mouse.img){
var image = im(mouse.img);
if(!image) return;
update_pointer(mouse, image)
if(sel.start){
sel.end = mouse;
}
}
if(sel.start){
e.preventDefault()
e.stopPropagation()
e.stopImmediatePropagation()
// var image = im(sel.img)
// var selection = get_selection(sel, image)
// render_selection(sel.img, selection, image.params)
update_selection()
// console.log(selection)
// selection.forEach(function(pair){
// var col = (pair[0] && pair[0].region) || pair[1].region;
// console.log(col)
// // shitty_regions(pair[0], pair[1], image)
// })
}
menu_handle(e)
}
function predict_mouse(){
// here we extrapolate in order to find an image
// and where on the image the cursor might land
var minVel = 1; // pixels per millisecond
var tMax = 500, tStep = 100;
var curVel = Math.sqrt(meanDX * meanDX + meanDY * meanDY),
velX = meanDX / curVel * Math.max(curVel, minVel),
velY = meanDY / curVel * Math.max(curVel, minVel);
// here's the core extrapolation process that coarsely locates candidate images
var t = 0;
do {
var offsetX = lastClientX + velX * t
var offsetY = lastClientY + velY * t
if(offsetX < 0 || offsetY < 0 || offsetX >= innerWidth || offsetY >= innerHeight) break;
el = document.elementFromPoint(offsetX, offsetY)
t += tStep;
} while ( el && t < tMax && !valid_image(el));
if(!valid_image(el)){
if(el) el.removeAttribute('naptha_cursor');
return;
}
t -= tStep; // roll back the last thing
var layout = image_layout(el);
var relX = lastClientX - layout.X,
relY = lastClientY - layout.Y;
// var y_coords = [];
var chunks = [];
var image = im(el);
var expires = Date.now() - 1000 * 10; // ten seconds is long enough right?
do {
var imgX = relX + meanDX / curVel * Math.max(curVel, minVel) * t,
imgY = relY + meanDY / curVel * Math.max(curVel, minVel) * t;
var natX = Math.max(0, Math.min(1, imgX / el.width)) * el.naturalWidth,
natY = Math.max(0, Math.min(1, imgY / el.height) * el.naturalHeight);
// y_coords.push(natY); // plop it on the end so that the list is sorted by distance from cursor
// chunks = chunks.concat(to_chunks(natY, image))
to_chunks(natY * image.params.scale, image).forEach(function(chunk){
if(chunks.indexOf(chunk) == -1 && image.chunks.indexOf(chunk) == -1){
chunks.push(chunk)
}
})
t += tStep;
} while ( t < tMax && imgX > 0 && imgY > 0 && imgX < el.width && imgY < el.height );
// console.log('chu', chunks)
// queue_chunks(el, y_coords)
// queue_chunks(el, chunks)
if(chunks.length > 0){
var image = im(el)
// console.log(chunks, image.chunks)
// TODO: only send params when it's the first time
// so that we can send less data less often
// document.getElementById('project_naptha_core_frame').contentWindow.postMessage({
// chunks: chunks,
// time: Date.now(),
// id: image.id,
// params: image.params,
// src: image.src
// }, location.protocol + '//' + location.host)
broadcast({
type: 'qchunk',
id: image.id,
time: Date.now(),
chunks: chunks
})
}
// var cursor_set = document.querySelectorAll('[naptha_cursor]');
// for(var i = 0; i < cursor_set.length; i++){
// if(!valid_image(cursor_set[i])){
// cursor_set[i].removeAttribute('naptha_cursor')
// }
// }
}
function get_mouse(e){
lastClientX = e.clientX;
lastClientY = e.clientY;
// we continuously differentiate and dampen the derivative of the
// motion of the cursor so that we can extrapolate into the future
// and anticipate out which images are likely to be treaded upon later
// for advance processing since the swt is a rather slow process
var damp = 0.5,
now = Date.now(),
dt = now - lastPageT,
dx = (e.pageX - lastPageX) / dt,
dy = (e.pageY - lastPageY) / dt;
if(dt != 0){
lastPageX = e.pageX; lastPageY = e.pageY; lastPageT = now;
meanDX = meanDX * damp + (1 - damp) * dx;
meanDY = meanDY * damp + (1 - damp) * dy;
if(!isFinite(meanDX) || !isFinite(meanDY)) meanDX = meanDY = 1;
}
// var target = document.elementFromPoint(e.clientX, e.clientY);
// check if the cursor is directly on top of some image which is
// valid and searchable for text
if(valid_image(e.target)){
var img = e.target;
var layout = image_layout(img)
// transform the cursor into coordinates on the image
// var imgX = Math.min(1, Math.max(0, (e.clientX - layout.X) / img.width)) * img.naturalWidth,
// imgY = Math.min(1, Math.max(0, (e.clientY - layout.Y) / img.height)) * img.naturalHeight;
var fracX = (e.clientX - layout.X) / img.width,
fracY = (e.clientY - layout.Y) / img.height;
// if(!img.naptha_sel){
// img.naptha_sel = {
// stack: [],
// start: null,
// end: null
// }
// }
// var sel = img.naptha_sel;
// okay, so we's already gots an image
// now we do things like decide whether the cursor
// should change and shit like that
return {
img: img,
sel: sel,
inside: (fracX > 0 && fracY > 0 && fracX < 1 && fracY < 1),
X: fracX * img.naturalWidth,
Y: fracY * img.naturalHeight
}
}
return {}
}
// so I guess there's a chain of object transitions and transformations
// first is the native dom events, in this case mousemove or mousedown
// which have element targets with associated locations on the page and
// clientX and clientY coordinates embedded within in them. the first
// stage of the transformation takes all that into account and also
// keeps track of the random derivative keeping and returns an object
// with the image and the set of transformed x and y coordinates in a
// natural coordinate system ranging from 0 to the width of the underlying
// image. then is the conversion into something called a cursor, which
// is a localization system in terms of regions, letters, lines and words.
// here's a little helper method
function point2rect(x, y, rect){
// clamp (x, y) to edge of rectangle to find closest point that lies within rect
var ex = Math.max(Math.min(x, rect.x1), rect.x0),
ey = Math.max(Math.min(y, rect.y1), rect.y0);
// euclidean distance from clamped to point (pythagorean theorem)
return Math.sqrt((x - ex) * (x - ex) + (y - ey) * (y - ey))
}
function get_cursor(mouse){
var img = mouse.img;
var image = images[get_id(img)];
// gotta make sure we have regions
if(!image || !image.regions) return {};
var params = image.params,
sX = mouse.X * params.scale,
sY = mouse.Y * params.scale;
// UNTESTED I HAVE NO IDEA IF THIS WORKS
// function point2rrect(x, y, rect){
// // this is a generalization of the helper method that works
// // when the rectangle is rotated and stuff
// var xp = (x - rect.cx) * Math.cos(-rect.angle) - (y - rect.cy) * Math.sin(-rect.angle)
// var yp = (x - rect.cx) * Math.sin(-rect.angle) + (y - rect.cy) * Math.cos(-rect.angle)
// return point2rect(xp, yp, {
// x0: -rect.width / 2,
// x1: rect.width / 2,
// y0: -rect.height / 2,
// y1: rect.height / 2
// })
// }
function search_region(col_sel){
var line_sel = null, letter_sel = null, word_sel = null, min_dist = Infinity;
// if region is defined, then all the other location parameters
// are found by simply brute forcing all the elements inside
// and finding out which ones are closest
// wow such loop. very nested.
for(var i = 0; i < col_sel.lines.length; i++){
var line = col_sel.lines[i];
for(var j = 0; j < line.words.length; j++){
var word = line.words[j]
for(var k = 0; k < word.letters.length; k++){
var letter = word.letters[k], box = letter;
// expand the effective region of the last letter to that of the entire
// rest of the region
if(i == col_sel.lines.length - 1 &&
j == line.words.length - 1 &&
k == word.letters.length - 1){
box = { x0: letter.x0, y0: letter.y0, x1: col_sel.x1, y1: col_sel.y1}
}
// todo: the analagous thing for the first letter
var box_width = box.x1 - box.x0;
var d = point2rect(sX, sY, box);
if(d < min_dist){
min_dist = d;
line_sel = line;
word_sel = word;
letter_sel = letter;
}
}
}
}
return {
line: line_sel,
letter: letter_sel,
word: word_sel,
dist: min_dist,
region: col_sel
}
}
// you have to be within 10px (arbitrary parameter) of a region to be considered
// part of that region - otherwise, function will return with null region
if(image.regions){
var regions = virtualize_region(image, image.regions)
var region_candidates = regions.map(function(col){
var col_dist = point2rect(sX, sY, col);
if(col_dist > 10) return;
var match = search_region(col)
return match
})
.filter(function(e){ return e })
.sort(function(a, b){ return a.dist - b.dist });
var result = region_candidates[0];
}
if(result){
if(result.line){
var widths = [], wordlen = [];
result.line.words.forEach(function(word){
wordlen.push(word.letters.length);
word.letters.forEach(function(letter){
widths.push(letter.x1 - letter.x0)
})
});
function S(a, b){ return a + b }
var meanlen = wordlen.reduce(S) / wordlen.length;
var mean = widths.reduce(S) / widths.length;
var std = Math.sqrt(widths.map(function(e){ return (e - mean) * (e - mean) }).reduce(S) / widths.length)
var max = Math.max.apply(Math, widths),
min = Math.min.apply(Math, widths);
// console.log(result.region.xheight)
if(std > 10 && meanlen <= 3 && result.region.xheight < 40){
// criterion for words that are kinda connected and therefore we should use
// words instead of letters as the atomic selection size
result.letter = result.word;
}
}
}
return (result || {})
}
// this is the code for managing layers
// because just about all the visual interface is presented
// in the form of a series of canvas elements positioned on
// top of a certain image.
// these are different layers, like the selection boxes
// the optional flame overlay for the selection boxes,
// the inpainting mask, and the translated text
// there's also the pseudo-layer which manages clipboard
// interaction, but that's managed separately because it's
// not inhernetly element-bound or position-sensitive
// this means that we have to keep track of every image and
// the respective involved canvases, when the page is
// transformed (i.e. resized), we have to invalidate the
// positioning and reposition the canvases to be in their
// appropriate positions
function layer_clear(img, name){
name = name || 'default';
var image = im(img)
if(!('layers' in image)) return;
if(name == '*'){
for(var n in image.layers)
layer_clear(img, n);
// console.log('cleared shit')
return;
}
if(!(name in image.layers)) return;
var paper = image.layers[name];
if(paper.parentNode){
paper.parentNode.removeChild(paper)
}
delete image.layers[name]
}
function get_container(){
// make sure there's a container for all the naptha
// related things
var container_id = "project_naptha_container"
var container = document.getElementById(container_id);
if(!container){
container = document.createElement('div')
container.id = container_id
// document.body.appendChild(container)
// inject the element outside the body so that
// most css doesn't affect it
document.documentElement.appendChild(container)
}
return container
}
function dispose_overlay(img){
var image = im(img)
if(image.overlay && image.overlay.parentNode){
image.overlay.parentNode.removeChild(image.overlay)
}
image.overlay = null
delete image.overlay;
}
function update_overlay(img){
var image = im(img)
if(!('overlay' in image)){
image.overlay = document.createElement('div')
get_container().appendChild(image.overlay)
}
var overlay = image.overlay
// lets position teh elment and stuff i guess
var layout = image_layout(img)
overlay.setAttribute('data-imageid', image.id)
overlay.style.position = 'absolute'
overlay.style.userSelect = 'none'
overlay.style.pointerEvents = 'none'
overlay.style.margin = '0'
overlay.style.border = '0'
overlay.style.padding = '0'
overlay.style.opacity = '1'
overlay.style.left = layout.left + 'px'
overlay.style.top = layout.top + 'px'
}
function layer(img, name, x0, y0){
name = name || 'default';
x0 = x0 || 0;
y0 = y0 || 0;
var image = im(img)
if(!('layers' in image))
image.layers = {};
// now actually access and create the element which
// is the currentlayer
if(!(name in image.layers)){
var paper = image.layers[name] = document.createElement('canvas');
update_overlay(img);
image.overlay.appendChild(paper)
}
var paper = image.layers[name];
paper.setAttribute('data-layername', "project_naptha_layer_"+name)
paper.style.userSelect = 'none'
paper.style.pointerEvents = 'none'
paper.style.left = x0 + 'px'
paper.style.top = y0 + 'px'
init_layer(paper, name)
return paper
}
function depth(name){
var ordering = [
'plaster',
'translate',
'debug',
'highlight',
'pun',
'shimmer',
'error',
'overlay',
'menu'
];
if(ordering.indexOf(name) == -1){
console.warn("Error: ", name, "not found in ordering schema")
}
// it's a really big number but still smaller than the max z-index and its the unix timestamp
// of a photo which was taken shortly after walking off the stage where it was announced
// that I had won second place at HackMIT 2013
// return 1381080740 + ordering.indexOf(name) * 14;
// the max z-index is 2147483647
// some tumblr lightbox had a higher z-index than that
return 2147483645 - ordering.length + ordering.indexOf(name);
// perhaps we should automatically enumerate all teh elmeents
// and then lower their z-indexes so that we can fit
}
function init_layer(el, name){
el.style.zIndex = depth(name);
el.style.position = 'absolute'
el.style.margin = '0'
el.style.border = '0'
el.style.padding = '0'
// el.style.opacity = '1'
el.style.boxShadow = 'none'
}
function error_message(image, region, text){
var div = document.createElement('div')
if(text){
div.innerHTML = "<b>Error</b> "
div.appendChild(document.createTextNode(text.replace(/^\s*Error:?\s*/i, '')))
}else{
div.innerHTML = "<b>Error</b> something went wrong"
}
div.innerHTML += ' <button type="button" class="close" data-dismiss="alert" onclick="this.parentNode.parentNode.removeChild(this.parentNode)">&times;</button>'
div.className = "alert-danger"
div.style.zIndex = depth('error')
update_overlay(image.el)
var xpad = 10,
ypad = 10;
var sx = (image.el.width / image.el.naturalWidth / image.params.scale),
sy = (image.el.height / image.el.naturalHeight / image.params.scale);
var layout = image_layout(image.el)
div.style.position = "absolute"
div.style.left = (layout.left + sx * region.x0 + xpad) + 'px'
div.style.top = (layout.top + sy * region.y0 + ypad) + 'px'
div.style.width = Math.max(250, (sx * region.width - (35 + 14 + 1 + 1) - xpad * 2)) + 'px'
// init_layer(div, 'error')
// image.overlay.appendChild(div)
get_container().appendChild(div)
}
function update_translations(image){
if(!image.translate) image.translate = {};
image.regions.forEach(function(region){
if(!(region.id in image.translate)) return;
var translate = image.translate[region.id];
function finish_translation(){
translate.processing = false
translate.waiting = false;
translate.finished = Date.now()
translate_region(image, region)
draw_overlays(image)
update_selection()
}
if(!translate.language || translate.language == 'erase'){
finish_translation()
}else if(image.ocr && translate.waiting && region.id in image.ocr){
var ocr = image.ocr[region.id]
if(ocr.finished){
var text = extract_region(ocr, { region: region })
.join('\n').trim().replace(/\n/g, ' ').replace(/- +/g, '').trim();
translate.waiting = false;
translate.processing = Date.now()
if(translate.language == 'esrever'){
translate.text = text.split('').reverse().join('')
finish_translation()
}else if(translate.language == 'echo'){
translate.text = text;
finish_translation()
}else if(translate.language == 'pig'){
translate.text = text.replace(/[a-z]+/ig, function(e){
if(e.length < 3) return e;
if(!/^[a-z]*$/i.test(e)) return e;
var proc = /[aeiou]/i.test(e[0]) ? (e + 'way') : (e.slice(1) + e[0].toLowerCase() + 'ay');
if(e[0] == e[0].toUpperCase()) proc = proc[0].toUpperCase() + proc.slice(1);
if(e == e.toUpperCase()) proc = proc.toUpperCase();
return proc
})
finish_translation()
}else{
var xhr = new XMLHttpRequest();
xhr.open('POST', global_params.apiroot + 'translate', true)
var formData = new FormData();
var lang = translate.language;
formData.append('target', lang)
formData.append('url', image.src)
formData.append('text', text)
formData.append('user', global_params.user_id)
xhr.send(formData)
xhr.onerror = function(){
// image.regions.forEach(function(region){
// if(region.id == data.reg_id){
// error_message(image, region, data.text)
// }
// })
error_message(image, region, "The translation server could not be reached")
delete image.translate[region.id]
// finish_translation()
}
xhr.onload = function(){
// image.lookup = JSON.parse(xhr.responseText)
try {
var json = JSON.parse(xhr.responseText);
translate.text = json.text
finish_translation()
} catch (err){
error_message(image, region, xhr.responseText)
delete image.translate[region.id]
}
if(!((image.lookup || {}).translations || []).some(function(chunk){ return chunk.target == lang })){
((image.lookup || {}).translations || []).push({
target: lang
})
}
}
}
}
}
if(!(region.id in image.plaster) && translate.language){
queue_broadcast({
src: image.src,
type: 'qpaint',
id: image.id,
reg_id: region.id,
region: region,
swtscale: image.params.scale,
swtwidth: image.width
})
image.plaster[region.id] = { processing: Date.now() }
region.shimmer = Date.now();
}
})
}
function draw_overlays(image){
setTimeout(function(){
image.regions.forEach(function(region){
var elapsed = get_elapsed(image, region)
var plaster = image.plaster[region.id]
var translate = image.translate[region.id] || {};
if(elapsed > 0){ // && elapsed < 1000
if(translate.language){
if(plaster && plaster.mask)
plaster.mask.style.opacity = 1;
if(translate.language != 'erase'){
if(translate && translate.paper)
translate.paper.style.opacity = 1;
}else{
if(translate && translate.paper)
translate.paper.style.opacity = 0;
}
}else{
if(plaster && plaster.mask)
plaster.mask.style.opacity = 0;
if(translate && translate.paper)
translate.paper.style.opacity = 0;
}
}else{
if(plaster && plaster.mask)
plaster.mask.style.opacity = 0;
if(translate && translate.paper)
translate.paper.style.opacity = 0;
// layer(image.el, 'translate', 0, 0).style.opacity = 0;
}
})
}, 0)
}
function translate_region(image, region){
if(!image.ocr) return;
if(!image.plaster) image.plaster = {};
if(!image.virtual) image.virtual = {};
if(!image.translate) image.translate = {};
if(!(region.id in image.translate)) return;
var translate = image.translate[region.id];
if(!translate.paper){
translate.paper = document.createElement('canvas')
update_overlay(image.el)
image.overlay.appendChild(translate.paper)
translate.paper.style.transition = 'opacity 1s'
translate.paper.style.opacity = '0'
}
var img = image.el
var paper = translate.paper;
init_layer(paper, 'translate')
paper.style.left = '0px'
paper.style.top = '0px'
paper.setAttribute('data-layername', "project_naptha_layer_translate")
paper.width = img.naturalWidth
paper.height = img.naturalHeight
paper.style.width = img.width + 'px'
paper.style.height = img.height + 'px'
var ctx = paper.getContext('2d')
if(!translate.language) return;
if(translate.waiting || translate.processing) return;
if(!(region.id in image.ocr)) return;
var ocr = image.ocr[region.id];
if(ocr.processing) return;
if(translate.language == 'erase') return;
var plaster = image.plaster[region.id];
if(!plaster || plaster.processing) return;
// this propagates positional information upwards
function wrap(children, extend){
extend = extend || {};
extend.x0 = Infinity; extend.y0 = Infinity;
extend.x1 = 0; extend.y1 = 0;
children.forEach(function(child){
extend.x0 = Math.min(extend.x0, child.x0); extend.y0 = Math.min(extend.y0, child.y0);
extend.x1 = Math.max(extend.x1, child.x1); extend.y1 = Math.max(extend.y1, child.y1);
})
extend.cx = extend.x1 / 2 + extend.x0 / 2;
extend.cy = extend.y1 / 2 + extend.y0 / 2;
return extend;
}
function get_letters(region){
return [].concat.apply([], region.lines.map(function letters_from_line(line){
return [].concat.apply([], line.words.map(function(e){ return e.letters }))
}))
}
function html_decode_entities(text){
var x = document.createElement('textarea')
x.innerHTML = text;
return x.value
}
var text = html_decode_entities(translate.text);
// var text = ocr.text.replace(/\n/g, ' ').replace(/- +/g, '').trim();
// function align_stats(prop){
// var align_thresh = 0.1;
// var met = region.lines.map(function(line){ return line[prop] })
// var mean = met.reduce(function(a, b){ return a + b }) / region.lines.length;
// return !met.some(function(line_dim){
// return Math.abs(line_dim - mean) / region.width > align_thresh
// })
// }
function align_mad(prop){ // mad props, dawg!
var met = region.lines.map(function(line){ return line[prop] })
var mean = met.reduce(function(a, b){ return a + b }) / region.lines.length;
var mad = met
.map(function(line_dim){ return Math.abs(line_dim - mean) / region.width })
.sort(function(a, b){ return a - b })[Math.floor(region.lines.length / 2)]
// .reduce(function(a, b){ return a + b }) / region.lines.length;
return mad
}
var align = 'left'
if((align_mad('cx') < align_mad('x0') && align_mad('cx') < align_mad('x1')) || region.lines.length == 1){
// centered
align = 'center'
}else if(align_mad('x0') < align_mad('x1') && align_mad('x0') < align_mad('cx')){
// left align
align = 'left'
}else if(align_mad('x1') < align_mad('x0') && align_mad('x1') < align_mad('cx')){
// right align
align = 'right'
}
// var centered = align_stats('cx'),
// right = align_stats('x1'),
// left = align_stats('x0'),
// justified = right && left;
var letters = get_letters(region);
var frac_up = letters.filter(function(e){ return e.height / region.xheight > 1.2 }).length / letters.length;
// console.log(frac_up, region.xheight, letters.map(function(e){ return e.height }))
if(frac_up < 0.05){
// oh look its all uppercased text
var font_size = Math.round(1.0 * region.xheight);
text = text.toUpperCase()
}else{
// 1.7095x + 0.1124 where x is the normalized x-height
// this was acquired by means of a linear regression
// on some data
var font_size = Math.round(1.7 * (region.xheight / image.params.scale) + 0.1124);
}
// ctx.shadowColor = "#000"
ctx.shadowBlur = 0;
var font_weight = 400,
font_name = '';
if(frac_up < 0.05 && region.lettersize / region.xheight > 0.4){
var rgb = plaster.colors[0][1]
var luma = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];
if(luma > 200){
ctx.shadowColor = "#000"
ctx.shadowBlur = 5;
}
if((region.lettersize / region.xheight) > 0.7){
// ctx.font = ' ' + font_size + 'px Impact'
font_name = 'Impact'
}else{
font_name = 'xkcd'
// ctx.font = ' ' + font_size + 'px xkcd'
}
}else{
var font_weight_th = (38.85 * region.thickness - 33.65) / font_size
var font_weight_ls = (13.97 * region.lettersize - 64.69) / font_size
// var font_weight = Math.min(900, Math.max(100, Math.round(font_weight_th / 2 + font_weight_ls / 2) * 100))
font_weight = Math.min(900, Math.max(100, Math.round(font_weight_th) * 100))
// var font_weight = Math.min(900, Math.max(100, Math.round(font_weight_ls) * 100))
// var font_weight = Math.min(900, Math.max(100, Math.round((region.lettersize / region.xheight) * 18.30 - 7.20) * 100))
// var font_weight = Math.min(900, Math.max(100, Math.round((region.lettersize / region.xheight) * 16.63 - 4.37) * 100))
// var font_weight = Math.min(900, Math.max(100, Math.round((region.thickness / region.xheight) * 17.29 - 8.59) * 100))
// var font_weight = 'light';
// console.log('font weight', font_weight)
// ctx.font = font_weight + ' ' + font_size + 'px "Helvetica Neue"'
font_name = '"Helvetica Neue"'
}
function measure(line){ return ctx.measureText(line).width }
// TODO: something really intelligent, like looking into the letter layouts
// to see if the raggedness is due to the presence of some object, rather than
// the mere action of text wrapping, and have an intelligent line breaking
// system which compensates for that
while(true){
ctx.font = font_weight + ' ' + font_size + 'px ' + font_name;
var words = breakWords(text.split(' '), measure, region.width / image.params.scale);
if(region.lines.length == 1){
var textlines = [words.join(' ')]
}else{
var textlines = breakLineGreedy(words, measure, region.width / image.params.scale)
}
var min_height = textlines.length * font_size * image.params.scale;
if(min_height > region.height && textlines.length > 1){
// console.log("KLDFJSOIDFJSOIDJFOISJDFOISJDF WALP WALP WLAP", font_size)
font_size--
continue;
}
var max_width = Math.max.apply(Math, textlines.map(measure))
if(max_width > region.width / image.params.scale){
font_size--
continue;
}
break;
}
console.log(textlines.join('\n'))
ctx.save()
var ox = region.cx / image.params.scale,
oy = region.cy / image.params.scale;
ctx.translate(ox, oy)
ctx.rotate(region.angle)
var lines = textlines.map(function(line, index){
if(!line) return;
if(plaster.colors && plaster.colors[0] && plaster.colors[0][1]){
var rgb = plaster.colors[0][1]
// var luma = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];
// if(luma < 30){
// rgb = [0, 0, 0]
// }else if(luma > 230){
// rgb = [255, 255, 255]
// }
// ctx.fillStyle = 'rgb(' + ocr.colors[0][1].map(Math.round).join(',') + ')'
ctx.fillStyle = 'rgb(' + rgb.map(Math.round).join(',') + ')'
}
var vpad = 5;
var region_height = (region.lines[region.lines.length - 1].cy + region.lines[region.lines.length - 1].lineheight / 2) - (region.lines[0].cy - region.lines[0].lineheight / 2)
var cy = (region.cy - region_height / 2 - vpad + (region_height + vpad * 2) * ((0.5 + index) / textlines.length));
ctx.textBaseline = 'middle';
if(align == 'center'){
ctx.textAlign = 'center'
ctx.fillText(line, region.cx / image.params.scale - ox, cy / image.params.scale - oy)
}else if(align == 'left'){
ctx.textAlign = 'left'
ctx.fillText(line, region.x0 / image.params.scale - ox, cy / image.params.scale - oy)
}else if(align == 'right'){
ctx.textAlign = 'right'
ctx.fillText(line, region.x1 / image.params.scale - ox, cy / image.params.scale - oy)
}
// ctx.strokeRect(region.cx / image.params.scale - ox, cy / image.params.scale - oy, 10, 10)
var letters = [];
var h = font_size * image.params.scale;
// ctx.strokeRect(region.x0 / image.params.scale, cy / image.params.scale, measure(line), h / image.params.scale)
for(var i = 0; i < line.length; i++){
var w = measure(line[i]) * image.params.scale;
if(align == 'center'){
var x0 = region.x0 + (region.width - measure(line) * image.params.scale) / 2 + measure(line.slice(0, i)) * image.params.scale
}else if(align == 'left'){
var x0 = region.x0 + measure(line.slice(0, i)) * image.params.scale
}else if(align == 'right'){
var x0 = region.x1 - measure(line.slice(i)) * image.params.scale
}
letters.push({
x0: x0, x1: x0 + w,
y0: cy - h / 2, y1: cy + h / 2,
_: line[i]
})
}
var words = [], buf = [];
for(var i = 0; i < letters.length; i++){
if(letters[i]._ == ' '){
words.push(buf)
buf = []
}else buf.push(letters[i]);
}
words.push(buf)
return wrap(letters, {
lineheight: h,
id: 'VLINE_' + index,
direction: region.direction,
words: words.filter(function(e){
return e.length
}).map(function(letters){
return wrap(letters, {
letters: letters
})
})
})
});
ctx.restore()
// console.log('region angle', region.angle, region.id)
image.virtual[region.id] = wrap(lines, {
id: region.id,
lines: lines,
angle: region.angle,
finished: true,
virtual: true,
direction: region.direction
})
}
function breakWord(word, measure, L){
var i = 0, j = 0, words = [];
while(j < word.length){
var j = i + 1;
while(j <= word.length && measure(word.slice(i, j + 1)) < L){
j++;
}
words.push(word.slice(i, j))
i = j;
}
return words;
}
function breakWords(words, measure, L){
return [].concat.apply([], words.map(function(word){
return breakWord(word, measure, L)
}));
}
function breakLineGreedy(words, measure, L){
var i = 0, j = 0, lines = [];
while(j < words.length){
var j = i + 1;
while(j <= words.length && measure(words.slice(i, j + 1).join(' ')) < L){
j++;
}
lines.push(words.slice(i, j).join(' '))
i = j;
}
return lines;
}
// http://stackoverflow.com/questions/18200593/implementing-text-justification-with-dynamic-programming
function breakLineDP(words, measure, L){
var wl = words.map(measure),
space = measure('m');
var n = wl.length;
var m = { '0': 0 }; // total "badness"
var s = { }; // aux array
function length(wordLengths, i, j){
var arr = wordLengths.slice(i - 1, j)
var sum = arr.length == 0 ? 0 :
arr.reduce(function(a, b){ return a + b });
return sum + j - i + space
}
for(var i = 1; i < n + 1; i++){
var sums = {};
var k = i;
while(length(wl, k, i) <= L && k > 0){
sums[Math.pow(L - length(wl, k, i), 3) + m[k - 1]] = k
k--;
}
m[i] = Math.min.apply(Math, Object.keys(sums))
s[i] = sums[m[i]]
}
var line = 1, lines = [];
while(n > 0){
lines.unshift(words.slice(s[n] - 1, n).join(' '))
n = s[n] - 1
line++
}
return lines
}
function draw_annotations(img, image){
var params = session_params;
if(!((params.show_contours || params.show_letters || params.show_lines || params.show_regions || params.show_chunks || params.show_stitching) && image)){
layer_clear(img, 'debug')
return;
}
var layout = image_layout(img);
var paper = layer(img, 'debug', 0, 0)
paper.width = image.width
paper.height = image.height
paper.style.width = img.width + 'px'
paper.style.height = img.height + 'px'
var ctx = paper.getContext('2d')
var num_chunks = Math.max(1, Math.ceil((image.height - image.params.chunk_overlap) / (image.params.chunk_size - image.params.chunk_overlap)))
image.chunks.forEach(function(chunk){
var offset = chunk * (image.params.chunk_size - image.params.chunk_overlap);
var chunk_color = (chunk % 3 > 0) ? ((chunk % 3 > 1) ? 'red' : 'green') : "blue";
if(params.show_chunks){
ctx.lineWidth = 3;
ctx.fillStyle = ctx.strokeStyle = chunk_color
if(ctx.setLineDash) ctx.setLineDash([4,4]);
ctx.strokeRect(10 * (chunk % 2) + 0.5 + ctx.lineWidth, offset + ctx.lineWidth + 0.5, image.width - 2 * 10 * (chunk % 2) - 2 * ctx.lineWidth, image.params.chunk_size)
ctx.fillText('Chunk ' + chunk, 20, offset + 20)
}
})
if(params.show_stitching && image.stitch_debug){
image.stitch_debug.forEach(function(line){
if(ctx.setLineDash) ctx.setLineDash([]);
ctx.lineWidth = 1
if(line.type == 'orphan'){
ctx.strokeStyle = 'orange'
}else if(line.type == 'valid'){
ctx.strokeStyle = 'green'
}
ctx.strokeRect(line.x0 + .5, line.y0 + .5, line.width, line.height)
})
}
image.regions.forEach(function(col){
if(params.show_regions){
if(col.lines[0].direction == -1){
ctx.fillStyle = 'rgba(255,255,0,' + ( col.finished ? 0.3 : 0.1 )+')'
ctx.strokeStyle = 'black'
}else{
ctx.fillStyle = 'rgba(0,255,255,' + ( col.finished ? 0.3 : 0.1 )+')'
ctx.strokeStyle = 'white'
}
ctx.lineWidth = 2
if(col.finished){
ctx.lineWidth = 3
if(ctx.setLineDash) ctx.setLineDash([]);
}else{
ctx.lineWidth = 3
if(ctx.setLineDash) ctx.setLineDash([10,10]);
}
// ctx.strokeRect(col.x0 - 1, col.y0 - 1, col.width + 2, col.height + 2)
// ctx.fillRect(col.x0, col.y0, col.width, col.height)
ctx.save();
ctx.translate(col.cx, col.cy)
ctx.rotate(col.angle)
ctx.strokeRect(-col.width/2, -col.height/2, col.width, col.height)
// ctx.fillRect(-col.width/2, -col.height/2, col.width, col.height)
ctx.restore()
}
ctx.lineWidth = 1
var strokecolors = ['#FF69B4', 'red', 'green', 'blue', 'purple']
col.lines.forEach(function(line, index){
var chunk = line.chunk;
var chunk_color = (chunk % 3 > 0) ? ((chunk % 3 > 1) ? 'red' : 'green') : "blue";
ctx.strokeStyle = strokecolors[index % strokecolors.length]
ctx.lineWidth = 1
line.words.forEach(function(word){
word.letters.forEach(function(letter){
if(params.show_letters){
if(ctx.setLineDash) ctx.setLineDash([]);
ctx.strokeRect(letter.x0, letter.y0, letter.width, letter.height);
}
if(params.show_contours && letter.shape){
// var offset = (image.params.chunk_size - image.params.chunk_overlap) * line.chunk
letter.shape.forEach(function(c){
if(line.direction == -1){
ctx.fillStyle = '#FF69B4'
}else{
ctx.fillStyle = 'yellow'
}
ctx.fillRect(letter.x0 + (c % letter.width), letter.y0 + Math.floor(c / letter.width), 1, 1)
// ctx.fillRect(c % image.width, offset + Math.floor(c / image.width), 1, 1)
})
}
})
if(params.show_words){
ctx.strokeRect(word.x0 , word.y0, word.width, word.height)
}
})
if(params.show_lines){
ctx.beginPath()
if(ctx.setLineDash) ctx.setLineDash([]);
// console.log(line)
// ctx.moveTo(line.x0, line.cy)
// ctx.lineTo(line.x1, line.cy)
// ctx.strokeStyle = chunk_color
// ctx.stroke()
// var cy = line.cy - (params.chunk_size - params.chunk_overlap) * chunk;
// if(!((cy >= params.chunk_overlap / 2 || chunk == 0) &&
// (cy < params.chunk_size - params.chunk_overlap / 2 || chunk == num_chunks - 1))) return false;
// var cy = (params.chunk_size - params.chunk_overlap) * chunk + params.chunk_overlap / 2
ctx.strokeStyle = 'purple'
ctx.lineWidth = 1;
if(ctx.setLineDash) ctx.setLineDash([]);
ctx.strokeStyle = strokecolors[index % strokecolors.length]
if(line.direction == -1){
ctx.fillStyle = 'rgba(255,255,0,0.3)'
}else{
ctx.fillStyle = 'rgba(0,255,255,0.3)'
}
ctx.save();
ctx.translate(line.cx, line.cy)
ctx.rotate(line.angle)
ctx.fillRect(-line.width/2, -line.lineheight/2, line.width, line.lineheight)
ctx.restore()
// ctx.fillRect(line.x0, line.y0, line.width, line.height)
ctx.beginPath()
ctx.lineWidth = 3
line.words.forEach(function(word){
word.letters.forEach(function(letter){
ctx.lineTo(letter.cx, letter.cy)
})
})
ctx.stroke()
}
})
})
}
var is_shimmering = false;
function start_shimmer(){
if(!is_shimmering) render_shimmer();
return is_shimmering
}
function get_elapsed(image, region){
if(!image.ocr) image.ocr = {};
if(!image.plaster) image.plaster = {};
if(!image.translate) image.translate = {};
var elapsed = []
if(region.id in image.ocr){
if(image.ocr[region.id].processing){
elapsed.push(-1)
}else{
elapsed.push(Date.now() - image.ocr[region.id].finished)
}
}
if(region.id in image.plaster){
if(image.plaster[region.id].processing){
elapsed.push(-1)
}else{
elapsed.push(Date.now() - image.plaster[region.id].finished)
}
}
if(region.id in image.translate && image.translate[region.id].language){
if(image.translate[region.id].waiting){
elapsed.push(-2)
}else if(image.translate[region.id].processing){
elapsed.push(-1)
}else{
elapsed.push(Date.now() - image.translate[region.id].finished)
}
}
return Math.min.apply(Math, elapsed)
}
function render_shimmer(){
is_shimmering = false
if(typeof sel == 'undefined' || !sel || !sel.img) return;
var img = sel.img
var image = im(img);
var cols = image.regions.filter(function(region){
if(!region.shimmer) return false;
var elapsed = get_elapsed(image, region)
if(elapsed > 1000) return false;
return true;
});
if(cols.length == 0){
layer_clear(img, 'shimmer')
return;
}
var x0 = Infinity, y0 = Infinity, x1 = 0, y1 = 0;
cols.forEach(function(col){
x0 = Math.min(x0, col.x0); y0 = Math.min(y0, col.y0)
x1 = Math.max(x1, col.x1); y1 = Math.max(y1, col.y1)
});
var sx = (img.width / img.naturalWidth) / image.params.scale,
sy = (img.height / img.naturalHeight) / image.params.scale;
var paper = layer(img, 'shimmer', x0 * sx, y0 * sy)
paper.width = Math.round((x1 - x0 + 1) * sx)
paper.height = Math.round((y1 - y0 + 1) * sy)
var ctx = paper.getContext('2d')
var dat = ctx.createImageData(paper.width, paper.height)
cols.forEach(function(col){
// var elapsed = Date.now() - image.ocr[col.id].finished;
var elapsed = get_elapsed(image, col)
var time = (Date.now() - col.shimmer);
col.lines.forEach(function(line){
var offset = (image.params.chunk_size - image.params.chunk_overlap) * line.chunk
var seed = line.y0;
var seed2 = line.y1;
for(var i = 0; i < 17; i++){
// this is a linear congruential pseudorandom number generator
// it's a pretty lame prng but all we need is something good
// enough to semi-randomly distribute the initial positions
// and parameters of the things
seed = ((1664525 * seed + 1013904223) % 4294967296);
seed2 = ((1664525 * seed2 + 1013904223) % 4294967296);
}
seed /= 4294967296;
seed2 /= 4294967296;
line.words.forEach(function(word){
word.letters.forEach(function(letter){
if(!letter.shape) return;
for(var i = 0; i < letter.shape.length; i++){
var c = letter.shape[i];
var x = letter.x0 + (c % letter.width),
y = (letter.y0 + Math.floor(c / letter.width));
var frac = 2 * seed + time / (2000 * (0.5 + seed2)) - x / Math.min(800, image.width);
var interp = Math.pow(0.5 - Math.cos(frac * Math.PI * 2) / 2, 8);
var opacity = Math.max(0, interp - 0.5) * Math.pow(Math.min(1, time / 500), 2)
if(elapsed < 1000) opacity *= Math.max(0, 1 - elapsed / 300);
opacity *= (line.direction > 0 ? 1 : 0.8);
if(opacity > 0){
var p = (dat.width * Math.round((y - y0) * sy) + Math.round((x - x0) * sx)) * 4;
dat.data[p] = dat.data[p + 1] = dat.data[p + 2] = 255;
dat.data[p + 3] = Math.floor(256 * opacity)
}
}
})
})
})
})
ctx.putImageData(dat, 0, 0)
requestAnimationFrame(render_shimmer)
is_shimmering = true
}
var menu_levels = [];
var lastX, lastY;
var menu_overlay;
var typeCompletionTimeout;
var typeBuffer = '';
function menu_typedone(){
clearTimeout(typeCompletionTimeout);
// console.log(typeBuffer)
typeBuffer = ''
var menu = menu_levels[menu_levels.length - 1];
if(menu && menu.selectedIndex > 0){
[].forEach.call(menu.children, function(el){
if(el.original) menu.replaceChild(el.original, el);
})
menu.change_selection(menu.selectedIndex, true)
}
}
function menu_keyhandle(e){
if(e.shiftKey) return;
function invalid_el(el){
return el.tagName == "HR" || el.has("disabled")
}
if(e.keyIdentifier == 'Down'){
var menu = menu_levels[menu_levels.length - 1];
var index = menu.selectedIndex + 1;
var cur_sel;
while(invalid_el(cur_sel = menu.children[(index + menu.children.length) % menu.children.length])){
index++;
}
menu.change_selection(cur_sel, true)
e.preventDefault()
}else if(e.keyIdentifier == 'Up'){
var menu = menu_levels[menu_levels.length - 1];
var index = menu.selectedIndex - 1;
var cur_sel;
while(invalid_el(cur_sel = menu.children[(index + menu.children.length) % menu.children.length])){
index--;
}
menu.change_selection(cur_sel, true)
e.preventDefault()
}else if(e.keyIdentifier == 'Right'){
menu_typedone()
var menu = menu_levels[menu_levels.length - 1];
if(menu){
var index = menu.selectedIndex;
if(index == -1){
menu.change_selection(0)
}else{
var cur_sel = menu.children[(index + menu.children.length) % menu.children.length]
menu.change_selection(cur_sel)
if(menu_levels[menu_levels.length - 1] != menu){
menu_levels[menu_levels.length - 1].change_selection(0, true)
}
}
}
e.preventDefault()
}else if(e.keyIdentifier == 'Left'){
if(menu_levels.length){
create_menu([], menu_levels.length - 1)
e.preventDefault()
}
}else if(e.keyIdentifier == 'Enter' || e.keyIdentifier == 'U+0020'){
menu_typedone()
var menu = menu_levels[menu_levels.length - 1];
menu.do_action(menu.selectedIndex)
e.preventDefault()
}else if(e.keyIdentifier == 'Esc' || e.keyIdentifier == 'U+001B'){
create_menu([], 0) // close the menu
e.preventDefault()
}
var keycode = e.keyCode;
var valid = (keycode > 64 && keycode < 91); // letter keys
var menu = menu_levels[menu_levels.length - 1];
if(valid && menu){
var letter = String.fromCharCode(e.keyCode);
clearTimeout(typeCompletionTimeout);
typeCompletionTimeout = setTimeout(menu_typedone, 500);
typeBuffer += letter;
// based off http://james.padolsey.com/javascript/replacing-text-in-the-dom-its-not-that-simple/
var re = new RegExp(typeBuffer.split('').join('.*?'), 'ig');
var has_match = false;
[].forEach.call(menu.children, function(el){
var orig = el.original || el;
orig.removeAttribute('selected');
var clone = orig.cloneNode(true);
clone.original = orig;
var temp = document.createElement('div');
var text = [].map.call(clone.childNodes, function(node){
return node.nodeType == 3 ? node.data : ''
}).join('')
re.lastIndex = 0;
if(text.match(re)){
has_match = true;
clone.is_match = true;
}
;[].forEach.call(clone.childNodes, function(node){
if(node.nodeType != 3) return;
re.lastIndex = 0;
temp.innerHTML = node.data.replace(re, '<u>$&</u>');
while(temp.firstChild){
node.parentNode.insertBefore(temp.firstChild, node)
}
node.parentNode.removeChild(node)
})
menu.replaceChild(clone, el);
});
var index = menu.selectedIndex;
if(has_match){
var cur_sel;
while(!(cur_sel = menu.children[(index + menu.children.length) % menu.children.length]).is_match){
index++;
}
menu.change_selection(cur_sel, true)
}else{
menu.change_selection(index, true)
}
}else{
menu_typedone()
}
}
function menu_handle(e){
if(is_child(e.target, menu_levels[menu_levels.length - 1])){
// the currently expanded menu
var menu = menu_levels[menu_levels.length - 1];
menu.change_selection(e.target)
// console.log('change target', e.target)
}else if(is_child(e.target, menu_levels[menu_levels.length - 2]) || e.target == menu_levels[menu_levels.length - 2]){
// the parent menu
var child_rect = menu_levels[menu_levels.length - 1].getBoundingClientRect();
// we need to check to see if the cursor vector lies within the two
// adjacent sides of the triangle formed by the edges of the child rect
// in this case, p3 is the one which gets used as the origin
// in our case, the origin is represented by lastX and lastY
function side(px, py, x1, y1, x2, y2){
return (y2 - y1) * (px - x1) + (x1 - x2) * (py - y1);
}
function PointInSector(px, py, x1, y1, x2, y2, x3, y3){
var cp1 = side(px, py, x1, y1, x2, y2) > 0,
cp3 = side(px, py, x3, y3, x1, y1) > 0;
return cp1 == cp3
}
var dx = ((child_rect.left / 2 + child_rect.right / 2) - lastX);
// is the box going to the left or right?
var far_edge = dx > 0 ? child_rect.right : child_rect.left,
close_edge = dx > 0 ? child_rect.left : child_rect.right;
if( (e.clientX - lastX) / dx > 0 && // cursor must go in direction of the gradient
PointInSector(e.clientX, e.clientY,
lastX, lastY,
lastY > child_rect.bottom ? far_edge : close_edge, child_rect.top,
lastY < child_rect.top ? far_edge : close_edge, child_rect.bottom)){
// the cursor trajectory is plausibly toward the child
}else{
var menu = menu_levels[menu_levels.length - 2];
menu.change_selection(e.target)
// console.log('change target2', e.target)
}
}else{
// anything else
var indices = menu_levels.map(function(menu, i){ return [is_child(e.target, menu), i] })
.filter(function(result){ return result[0] })
.map(function(result){ return result[1] });
if(indices.length){
create_menu([], indices[0] + 1)
}else if(menu_levels[menu_levels.length - 1]){
menu_levels[menu_levels.length - 1].change_selection(null)
// create_menu([], menu_levels.length)
}
// console.log('change target3', e.target, indices)
// console.log('creating menu', index)
}
var cdamp = 0.8;
lastX = e.clientX * cdamp + lastX * (1 - cdamp)
lastY = e.clientY * cdamp + lastY * (1 - cdamp)
if(!isFinite(lastX)) lastX = e.clientX;
if(!isFinite(lastY)) lastY = e.clientY;
}
function create_menu(items, level){
if(menu_levels.length == 0 && items.length == 0) return;
for(var i = level; i < menu_levels.length; i++){
var m = menu_levels[i];
if(m && m.parentNode){
m.parentNode.removeChild(m)
}
menu_levels[i] = null;
}
menu_levels = menu_levels.filter(function(e){return e});
if(level == 0 && items.length == 0){
if(menu_overlay && menu_overlay.parentNode){
menu_overlay.parentNode.removeChild(menu_overlay)
}
}
if(items.length == 0){
// menu_typedone()
return
}
var menu = document.createElement('menu')
menu.style.fontFamily = systemFonts[getPlatform()];
menu_levels[level] = menu;
menu.level = level;
items.forEach(function(item){
if(item == '-'){
menu.appendChild(document.createElement('hr'))
return
}
var btn = document.createElement('button')
btn.innerHTML = item.html
if(item.checked){
btn.setAttribute('checked', 'checked')
}
if(item.group){
btn.group = item.group;
}
menu.appendChild(btn)
btn.action = item.action;
btn.toggle = item.toggle;
if(item.items){
// oh look it has a submenu
btn.className += ' dropdown'
btn.items = item.items;
}
if(item.disabled){
// btn.setAttribute('disabled', 'disabled')
btn.className += ' disabled gray'
}else{
if(item.gray){
btn.className += ' gray'
}
if(item.cls){
btn.className += ' ' + item.cls
}
}
})
// var selectedItem;
menu.addEventListener('mouseup', function(e){
var index = menu.change_selection(e.target)
sel.deselect_time = Date.now()
menu.do_action(index)
})
menu.do_action = function(index){
if(index != -1){
var btn = menu.children[index]
if(btn.group){
[].forEach.call(menu.children, function(alt){
if(alt.group === btn.group){
alt.removeAttribute('checked')
}
})
btn.setAttribute('checked', 'checked')
}else if(btn.toggle){
if(btn.hasAttribute('checked')){
if(btn.toggle(false)) create_menu([], 0);
btn.removeAttribute('checked')
}else{
if(btn.toggle(true)) create_menu([], 0);
btn.setAttribute('checked', 'checked')
}
}else if(btn.action && !btn.has('disabled')){
btn.action()
// console.log('woop', btn.innerText)
create_menu([], 0)
}
}
}
menu.selectedIndex = -1;
menu.change_selection = function(index, no_expand){
// console.log(index)
if(typeof index != 'number'){
for(var i = 0; i < menu.children.length; i++){
if(menu.children[i] == index || is_child(index, menu.children[i])){
index = i;
break;
}
}
if(typeof index != 'number') index = -1;
}
var old = menu.children[menu.selectedIndex],
select = menu.children[index];
if(old) old.removeAttribute('selected');
if(select && !select.has('disabled')){
select.setAttribute('selected', true);
menu.selectedIndex = index;
}else{
menu.selectedIndex = null;
}
if(select && select.items){
if(!no_expand){
menu_typedone();
var sub = typeof select.items == 'function' ? select.items() : select.items;
sub_menu(sub, menu.level + 1, select.getBoundingClientRect())
}
}else{
create_menu([], menu.level + 1)
}
return index
}
menu.style.position = 'fixed'
menu.style.top = '-1000px';
menu.style.left = '-1000px';
menu.style.zIndex = depth('menu')
// document.body.appendChild(menu)
if(!(menu_overlay && menu_overlay.parentNode)){
menu_overlay = document.createElement('div')
menu_overlay.className = "contextmenu_overlay"
menu_overlay.style.zIndex = depth('overlay')
// document.body.appendChild(menu_overlay)
get_container().appendChild(menu_overlay)
menu_overlay.addEventListener("mousewheel", function (e) {
e.preventDefault();
e.stopPropagation();
return false;
}, true);
}
menu_overlay.appendChild(menu)
return menu
}
function sub_menu(items, level, rect){
var menu = create_menu(items, level);
var pad = 10;
if(rect.top + menu.offsetHeight + pad > innerHeight){
menu.style.top = (innerHeight - menu.offsetHeight - pad) + 'px';
}else{
menu.style.top = rect.top + 'px';
}
if(rect.right + menu.offsetWidth > innerWidth){
menu.style.left = (rect.left - menu.offsetWidth) + 'px';
}else{
menu.style.left = rect.right + 'px';
}
}
function context_menu(items, x, y){
var menu = create_menu(items, 0)
if(y + menu.offsetHeight > innerHeight){
if(y - menu.offsetHeight < 0){
menu.style.top = (innerHeight - menu.offsetHeight) + 'px'
}else{
menu.style.top = (y - menu.offsetHeight) + 'px'
}
}else{
menu.style.top = y + 'px'
}
if(x + menu.offsetWidth > innerWidth){
if(x - menu.offsetWidth < 0){
menu.style.left = (innerWidth - menu.offsetWidth) + 'px'
}else{
menu.style.left = (x - menu.offsetWidth) + 'px'
}
}else{
menu.style.left = x + 'px'
}
menu.focus()
}
function is_child(el, parent){
if(!parent) return null;
while(el && el.parentNode != parent)
el = el.parentNode;
return el
}
systemFonts = {
cros: 'Noto Sans UI, sans-serif',
linux: 'Ubuntu, sans-serif',
mac: 'Lucida Grande, sans-serif',
win: 'Segoe UI, Tahoma, sans-serif',
unknown: 'sans-serif'
};
// /**
// * @return {string} The platform this extension is running on.
// */
function getPlatform() {
var ua = window.navigator.appVersion;
if (ua.indexOf('Win') != -1) return 'win';
if (ua.indexOf('Mac') != -1) return 'mac';
if (ua.indexOf('Linux') != -1) return 'linux';
if (ua.indexOf('CrOS') != -1) return 'cros';
return 'unknown';
};
document.addEventListener('contextmenu', right_click, true)
function get_letters(region){
return [].concat.apply([], region.lines.map(function letters_from_line(line){
return [].concat.apply([], line.words.map(function(e){ return e.letters }))
}))
}
function guess_engine(){
var image = im(sel.img)
var cols = (!sel.start && sel.stack.length == 0) ? image.regions : selected_regions();
var popularity = {
'tess:eng': 0.1 // bias it toward something
};
;((image.lookup || {}).chunks || []).forEach(function(e){
// TODO: keep track of popularity on server of each
// translation type
popularity[e.engine] = (popularity[e.engine] || 0) + 1
});
var most_popular = Object.keys(popularity).sort(function(b, a){
return popularity[a] - popularity[b]
})[0];
if(cols.filter(function(region){
var letters = get_letters(region);
var frac_up = letters.filter(function(e){ return e.height / region.xheight > 1.2 }).length / letters.length;
// console.log('something something', region, frac_up, region.lettersize / region.xheight)
if(frac_up < 0.05 && (region.lettersize / region.xheight) > 0.7){
// oh look its all uppercased text
return true
}
return false
}).length > 0.8 * cols.length){
return "tess:joh"
}else{
return most_popular
}
}
function right_click(e){
var mouse = get_mouse(e);
if(!mouse.img) return;
var cursor = get_cursor(mouse);
if(!cursor.region) return;
e.preventDefault()
e.stopImmediatePropagation()
e.stopPropagation()
if(menu_levels.some(function(menu){ return is_child(e.target, menu) })) return;
var items = [];
var image = im(mouse.img);
var params = image.params;
function check_sel(){
if(sel.img != mouse.img){
clear_selection()
sel.img = mouse.img
}
}
check_sel()
// this code determines how far the current cursor position is
// from the selection squig in spite of its pretty non rectilinear
// or contiguous nature. it works by decomposing a squig into
// little rectangles, measuring the distance between that and
// the cursor after taking into account all the weird layout
// stuff and scaling
var min_dist = Infinity;
if(sel.stack.length > 0 || sel.start){
var image = im(sel.img);
var layout = image_layout(sel.img)
var sx = sel.img.width / sel.img.naturalWidth / image.params.scale,
sy = sel.img.height / sel.img.naturalHeight / image.params.scale;
var boxes = [].concat.apply([], get_selection(sel, image).map(function(pair){
return rectify_squig(pair[0], pair[1])
})).map(function(box){
return {
x0: box.x0 * sx + layout.X,
y0: box.y0 * sy + layout.Y,
x1: box.x1 * sx + layout.X,
y1: box.y1 * sy + layout.Y
}
})
for(var i = 0; i < boxes.length && min_dist > 0; i++){
var dist = point2rect(e.clientX, e.clientY, boxes[i])
if(dist < min_dist){
min_dist = dist;
}
}
}
// if the current selection isn't what was clicked on
// if(!selected_regions().some(function(region){ return region.id == cursor.region.id })){
if(min_dist > 30){
clear_selection()
if(cursor.line.words.length == 1){
push_sel(cursor, cursor) // cursor cursor on the wall, which is the shortest line of all?
}else{
push_sel(
{region: cursor.region, line: cursor.line, letter: cursor.word.letters[0]},
{region: cursor.region, line: cursor.line, letter: cursor.word.letters[cursor.word.letters.length - 1]}
)
}
update_selection()
requestAnimationFrame(modify_clipboard)
}
var selection = get_selection(sel, image);
items.push({
html: "Copy Text",
disabled: selection.length == 0 || global_params.demo_mode,
action: function(){
// document.execCommand('copy')
// alert('not supported, use Ctrl+C or Cmd+C instead.')
broadcast({
type: 'copy',
text: extract_selection(sel, image).text,
id: image.id
})
}
})
if(virtualize_region(image, selected_regions()).some(function(region){ return region.virtual })){
items.push({
html: "Modify Text <small>(BETA)</small>",
action: function(){
selected_regions().filter(function(region){
return virtualize_region(image, region).virtual == true
}).forEach(function(region){
var translate = image.translate[region.id]
translate.text = prompt("Enter new text for region", translate.text)
translate.language = 'custom'
translate_region(image, region)
update_selection()
})
}
})
}
// items.push({ html: "Search for 'blah'...", disabled: selection.length == 0 })
items.push({
html: "Select All",
// if everything's already selected, then uh don't like show this
disabled: image.regions.length == selection.length,
action: function(){
check_sel()
image.regions.forEach(function(col){
push_sel({ region: col })
})
update_selection()
}
})
items.push({ html: "Open in New Tab", action: function(){
// var base = document.createElement('canvas')
// base.width = image.el.naturalWidth;
// base.height = image.el.naturalHeight;
// var btx = base.getContext('2d')
// btx.drawImage(image.el, 0, 0)
// open(base.toDataURL('image/png'))
open(image.el.src)
}})
items.push('-')
function engine(id, html){
// var has_lookup = ((image.lookup || {}).chunks || []).some(function(e){
// return e.engine == id
// });
// var has_lookup = ((image.lookup || {}).chunks || []).some(function(e){
// return e.engine == id
// });
var has_lookup = selected_regions().some(function(region){
return get_lookup_chunks(image, region).some(function(chunk){
return chunk.engine == id
})
});
var is_ideal = guess_engine() == id;
var is_local = (id == 'ocrad');
// console.log(guess_engine())
return {
html: html,
// checked: get_ocr_engine(image) == id,
checked: selected_regions().some(function(region){
return (image.ocr[region.id] || { })._engine == id
}),
disabled: (global_params.demo_mode && !has_lookup) || (location.protocol == 'file:' && !is_local),
// cls: (has_lookup || id == 'ocrad' || image.engine == id || id == guess_engine()) ? '': 'halfgray',
cls: [is_ideal ? 'ideal' : '', (has_lookup || is_local) ? '' : 'halfgray'].join(' '),
action: function(){
check_sel()
if(!image.ocr) image.ocr = {};
selected_regions().forEach(function(region){
var ocr = image.ocr[region.id];
if(ocr && ocr.engine == id) return;
image.ocr[region.id] = {
engine: id,
waiting: Date.now()
}
if(image.translate && image.translate[region.id]){
var translate = image.translate[region.id]
translate.waiting = Date.now()
delete translate.finished
delete translate.processing
}
region.shimmer = Date.now()
})
update_translations(image)
update_selection()
draw_overlays(image)
start_shimmer()
}
}
}
function escape_html(text){
var d = document.createElement('div');
d.appendChild(document.createTextNode(text));
return d.innerHTML
}
function language_items(){
var items = [
engine('ocrad', "English <small>Ocrad.js</small>"),
]
if(/https?\:/.test(location.protocol)){
items = items.concat([
'-',
engine("tess:eng", "English <small>Tesseract</small>"),
engine("tess:joh", "Internet Meme"),
engine("tess:rus", "Russian"),
engine("tess:deu", "German"),
engine("tess:spa", "Spanish"),
engine("tess:chi_sim", "Chinese Simplified"),
engine("tess:chi_tra", "Chinese Traditional"),
engine("tess:fra", "French"),
engine("tess:jpn", "Japanese"),
// engine("tess:ara", "Arabic"), // arabic probably wont work well
])
}else{
items.push({
html: "<small>Remote OCR only for http(s) URLs</small>",
disabled: true
})
}
return items;
}
items.push({html: "Language", items: language_items})
if(!image.plaster) image.plaster = {};
function translate(id, html){
var unplastered = selected_regions().some(function(region){
return !(region.id in image.plaster)
})
var checked = selected_regions().some(function(region){
return (!image.translate || image.translate[region.id] || {}).language == id
})
// console.log(((image.lookup || {}).translations || []))
var has_lookup = ((image.lookup || {}).translations || []).some(function(e){ return e.target == id });
var is_local = !id || (['erase', 'echo', 'esrever', 'pig'].indexOf(id) != -1)
// var checked = unplastered ? (id == null) : (id == image.translate_lang);
return {
html: html,
cls: (has_lookup || is_local || checked) ? '': 'halfgray',
// disabled: tess_to_gtran[image.engine.split(':')[1]] == id,
// checked: id == image.translate_lang
disabled: (location.protocol == 'file:' && !is_local),
checked: checked,
action: function(){
check_sel()
// image.translate_lang = id
if(!image.translate) image.translate = {};
// image.translate = {}
var cols = (!sel.start && sel.stack.length == 0) ? image.regions : selected_regions();
// var cols = image.regions;
console.log(cols, sel.start, sel.stack)
if(id != 'erase'){
// console.log("OMG RESET SOMETHING")
// image.ocr = null;
// image.engine = guess_engine()
selected_regions().forEach(function(region){
if(!image.ocr[region.id] || image.ocr[region.id].engine == 'default'){
image.ocr[region.id] = {
engine: get_ocr_engine(image, region, guess_engine()),
waiting: Date.now()
}
}
})
}
// re-add every selection but this time reincarnated
// as something which selects all the regions
clear_selection()
cols.forEach(function(col){ push_sel({ region: col }) })
cols.forEach(function(col){
if(image.translate[col.id]){
// nothing to change here
if(image.translate[col.id].language == id) return;
var el = image.translate[col.id].paper
el.style.opacity = 0;
setTimeout(function(){
if(el.parentNode) el.parentNode.removeChild(el);
}, 2000)
}
image.translate[col.id] = {
language: id,
waiting: Date.now()
}
})
update_translations(image)
start_shimmer()
draw_overlays(image)
update_selection()
}
}
}
function translate_items(){
var items = [];
items = items.concat([
translate(null, 'No Translation'),
translate('erase', 'Erase Text'),
translate('echo', 'Reprint Text'),
])
if(selected_regions().some(function(region){ return (!image.translate || image.translate[region.id] || {}).language == 'custom' })){
items.push(translate('custom', 'Custom Text'))
}
if(/https?\:/.test(location.protocol)){
items = items.concat([
// translate('echo', 'Change Text'),
// {
// html: 'Change Text',
// action: function(){
// console.log("merp")
// }
// },
// translate('esrever', 'Reverse Text'),
// translate('pig', 'Pig Latin'),
'-',
translate('en', 'English <small>Google Translate</small>'),
translate('es', 'Spanish <small>Microsoft Translate</small>'),
translate('ru', 'Russian <small>Yandex Translate</small>'),
translate('zh-CN', 'Chinese Simplified'),
translate('zh-TW', 'Chinese Traditional'),
translate('ja', 'Japanese'),
// translate('pt', 'Portuguese'),
translate('de', 'German'),
// translate('ar', 'Arabic'), // rtl probably doesnt work
translate('fr', 'French'),
// '-',
// translate('echo', 'Change Text'),
// translate('esrever', 'Reverse Text'),
// translate('pig', 'Pig Latin'),
])
}else{
items = items.concat([
'-',
translate('esrever', 'Reverse Text'),
translate('pig', 'Pig Latin'),
{
html: "<small>Remote OCR only for http(s) URLs</small>",
disabled: true
}
])
}
return items;
}
items.push({html: "Translate <small>(BETA)</small>", items: translate_items})
items.push('-')
items.push({html: "Options", items: [
// {html: "Fast Plaster", checked: params.fast_plaster, toggle: function(val){
// check_sel()
// params.fast_plaster = val;
// draw_plaster(sel.img, image)
// }},
// '-',
{html: 'Show OCR Disclaimer', disabled: global_params.demo_mode, checked: get_setting('warn_ocrad'), toggle: function(val){
// session_params.warn_ocrad = val;
put_setting('warn_ocrad', val)
}},
{html: 'Disable lookup', disabled: global_params.demo_mode, checked: get_setting('no_lookup'), toggle: function(val){
// session_params.no_lookup = val;
put_setting('no_lookup', val)
}},
'-',
{html: 'Disable for this image', disabled: true},
{html: 'Disable on this page', disabled: true},
// {html: 'Disable Naptha'},
{html: 'Disable on <i>' + escape_html(location.hostname) + '</i>', disabled: true},
'-',
{html: 'Add Translate Key', disabled: global_params.demo_mode,
action: function(){
open(global_params.apiroot + 'enter_key/' + global_params.user_id)
}
},
{html: 'Report Issue', action: function(){
open("https://docs.google.com/forms/d/1WLitLvYOPefYOd9S2SeCcYCWb-fsfbyDCd8_moIR0C4/viewform?entry.467368810="+encodeURIComponent(image.src)+"&entry.149654426=" + encodeURIComponent(location.href))
}}
// {html: 'Dynamic Clock', items: function(){
// return [
// {html: (new Date).toString()}
// ]
// }}
]})
items.push({html: "Advanced", items: [
{html: "Show Regions", checked: session_params.show_regions, toggle: function(val){
check_sel()
session_params.show_regions = val
draw_annotations(sel.img, image)
}},
{html: "Show Lines", checked: session_params.show_lines, toggle: function(val){
check_sel()
session_params.show_lines = val;
draw_annotations(sel.img, image)
}},
{html: "Show Stitching", checked: session_params.show_stitching, disabled: !image.stitch_debug, toggle: function(val){
check_sel()
session_params.show_stitching = val;
draw_annotations(sel.img, image)
}},
{html: "Show Letters", checked: session_params.show_letters, toggle: function(val){
check_sel()
session_params.show_letters = val;
draw_annotations(sel.img, image)
}},
{html: "Show Contours", checked: session_params.show_contours, disabled: global_params.demo_mode, toggle: function(val){
check_sel()
session_params.show_contours = val;
draw_annotations(sel.img, image)
}},
{html: "Show Chunks", checked: session_params.show_chunks, toggle: function(val){
check_sel()
session_params.show_chunks = val;
draw_annotations(sel.img, image)
}},
'-',
{html: "Clear State", disabled: global_params.demo_mode, action: function(val){
check_sel()
layer_clear(sel.img, '*')
delete images[get_id(sel.img)];
}},
{html: "Debug Mode", disabled: global_params.demo_mode, checked: image.params.debug, toggle: function(val){
check_sel()
layer_clear(sel.img, '*')
delete images[get_id(sel.img)];
im(sel.img).params.debug = val;
return true
}}
]})
var button = "";
if(!(image.lookup || {}).finished && (session_params.no_lookup || !navigator.onLine)){
button = "<span class='credits-button offline'>offline</span>"
}else if(global_params.demo_mode){
button = "<span class='credits-button low'>demo</span>"
}else{
// var button = "<span class='credits-button low'>100</span>"
}
items.push({html: "naptha <small>0.7.5</small> "+button, gray: true, action: function(){
open("http://projectnaptha.com/#"+global_params.user_id)
}})
context_menu(items, e.clientX, e.clientY)
}
/*
https://github.com/paulirish/lazyweb-requests/issues#issue/19
http://dev.w3.org/html5/2dcontext/#dom-context-2d-globalalpha
http://radikalfx.com/files/collage/jcollage.js
http://www.student.kuleuven.be/~m0216922/CG/fire.html
http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
*/
function CanvasFlame (canvas) {
this.orig_canvas = document.createElement ("canvas");
this.orig_context = this.orig_canvas.getContext ("2d");
this.canvas = canvas;
this.width = this.orig_canvas.width = this.canvas.width;
this.height = this.orig_canvas.height = this.canvas.height;
this.context = this.canvas.getContext ("2d");
this.context.fillStyle = "rgba(0, 0, 0, 0)"
this.context.fillRect (0, 0, this.width, this.height);
this.image = this.context.getImageData (0, 0, this.width, this.height);
this.data = this.image.data;
this.palette = new Array (256);
for (var i = 0; i <= 64;i++) {
var alpha = Math.floor(Math.pow(Math.random(), 0.1) * 256)
this.palette[i] = [i * 4, 0, 0, alpha];
this.palette[i + 64] = [255, i * 4, 0, alpha];
this.palette[i + 128] = [255, 255, i * 4, alpha];
this.palette[i + 192] = [255, 255, 255, alpha];
}
this.updateEmbers()
this.flames = new Uint8Array (this.width * this.height);
for (var i = 0;i < this.width * this.height;i++) {
this.flames[i] = 0;
}
this.started = false;
}
CanvasFlame.prototype.updateEmbers = function(){
this.orig_image = this.orig_context.getImageData (0, 0, this.orig_canvas.width, this.orig_canvas.height);
this.orig_data = this.orig_image.data;
this.emits = new Uint8Array (this.width * this.height);
for (var i = 0;i < this.emits.length;i++) {
this.emits[i] = (this.orig_data[i * 4] + this.orig_data[i * 4 + 1] + this.orig_data[i * 4 + 2]) / 3 > 70;
}
}
CanvasFlame.prototype.start = function (effect_duration, emit_duration) {
if (this.started) return;
this.started = true;
this.stopEmit = false;
var myself = this;
var effect_time = effect_duration || 6.0;
var emit_time = emit_duration || (effect_time - 2.0);
if (emit_time > effect_time || emit_time < 0)
emit_time = effect_time / 2.0;
myself.loop()
};
CanvasFlame.prototype.stop = function () {
this.started = false;
};
/* T.E. Lawrence, eponymously of Arabia, but very much an Englishman,
favored pinching a burning match between his fingers to put it out.
When asked by his colleague, William Potter, to reveal his trick --
how is it he so effectively extinguished the flame without hurting
himself whatsoever -- Lawrence just smiled and said, "The trick,
Potter, is not minding it hurts."
The fire that danced at the end of that match was a gift from the
Titan, Prometheus, a gift that he stole from the gods. When Prometheus
was caught and brought to justice for his theft, the gods, well, you
might say they overreacted a little. The poor man was tied to a rock
as an eagle ripped through his belly and ate his liver over and over,
day after day, ad infinitum. All because he gave us fire, our first
true piece of technology: Fire.
*/
CanvasFlame.prototype.loop = function () {
var x, y, i;
var run_count = 0;
for (y = this.height - 2;y > -1;y--) {
for (x = 0;x < this.width;x++) {
i = y * this.width + x;
this.flames[i] = (! this.stopEmit && this.emits[i]) ? (255 - Math.round (Math.random () * 70)) : Math.round (
(
/* (x-1,y) */ + (x > 0 ? this.flames[i - 1] : 0)
/* (x,y) */ + this.flames[i]
/* (x+1,y) */ + (x < this.width - 1 ? this.flames[i + 1] : 0)
/* (x-2,y+1) */ + (x > 1 ? this.flames[i - 2 + this.width] : 0)
/* (x-1,y+1) */ + (x > 0 ? this.flames[i - 1 + this.width] : 0)
/* (x,y+1) */ + this.flames[i + this.width] * 2
/* (x+1,y+1) */ + (x < this.width - 1 ? this.flames[i + 1 + this.width] : 0)
/* (x+2,y+1) */ + (x < this.width - 2 ? this.flames[i + 2 + this.width] : 0)
)
* (0.97 + ((Math.random () - 0.5) * 0.3)) / 9.0);
if (this.flames[i] > 10 && this.flames[i] < 170) {
for (j = 0; j < 4; j++){
this.data[i * 4 + j] = this.palette[this.flames[i] > 255 ? 255 : this.flames[i]][j];
}
// this.data[i * 4 + 3] = 255;
run_count++
} else {
this.data[i * 4 + 3] = 0;
}
}
}
if(run_count < 5){
this.stop();
}
this.context.putImageData (this.image, 0, 0); //should be (0,0,this.width,this.height) but chrome throws NOT_SUPPORTED_ERR
if(this.started){
var myself = this;
requestAnimationFrame(function(){
myself.loop()
})
}
};
javascript:(function()%7Bfunction uuid()%7Bfor(var i%3D0,s,b%3D%27%27%3Bi<42%3Bi%2B%2B)if(/%5Cw/i.test(s%3DString.fromCharCode(48%2BMath.floor(75*Math.random()))))b%2B%3Ds%3Breturn b%3B%7Dconsole.image%3Dfunction(url,log)%7Bvar img%3Dnew Image()%3Bfunction getBox(width,height)%7Breturn%7Bstring:"%2B",style:"font-size: 1px%3B padding: "%2BMath.floor(height/2)%2B"px "%2BMath.floor(width/2)%2B"px%3B line-height: "%2Bheight%2B"px%3B"%7D%7Dimg.onload%3Dfunction()%7Bvar dim%3DgetBox(this.width,this.height)%3Bconsole.log(log,url)console.log("%25c"%2Bdim.string,dim.style%2B"background: url("%2Burl%2B")%3B background-size: "%2B(this.width)%2B"px "%2B(this.height)%2B"px%3B color: transparent%3B")%3B%7D%3Bimg.src%3Durl%3B%7D%3B(function()%7Bvar lastTime%3D0%3Bvar vendors%3D%5B%27ms%27,%27moz%27,%27webkit%27,%27o%27%5D%3Bfor(var x%3D0%3Bx<vendors.length%26%26!window.requestAnimationFrame%3B%2B%2Bx)%7Bwindow.requestAnimationFrame%3Dwindow%5Bvendors%5Bx%5D%2B%27RequestAnimationFrame%27%5D%3Bwindow.cancelAnimationFrame%3Dwindow%5Bvendors%5Bx%5D%2B%27CancelAnimationFrame%27%5D%7C%7Cwindow%5Bvendors%5Bx%5D%2B%27CancelRequestAnimationFrame%27%5D%3B%7Dif(!window.requestAnimationFrame)window.requestAnimationFrame%3Dfunction(callback,element)%7Bvar currTime%3Dnew Date().getTime()%3Bvar timeToCall%3DMath.max(0,16-(currTime-lastTime))%3Bvar id%3Dwindow.setTimeout(function()%7Bcallback(currTime%2BtimeToCall)%3B%7D,timeToCall)%3BlastTime%3DcurrTime%2BtimeToCall%3Breturn id%3B%7D%3Bif(!window.cancelAnimationFrame)window.cancelAnimationFrame%3Dfunction(id)%7BclearTimeout(id)%3B%7D%3B%7D())%3Bvar global_params%3D%7Bocrad_worker:%27src/ocrad.js/worker.js%27,swt_worker:%27src/swt/worker.js%27,inpaint_worker:%27src/inpaint.js/worker.js%27,mask_worker:%27src/mask/worker.js%27,num_workers:2,is_extension:false,simple_ids:true,demo_mode:true,queue_expires:1000*2,user_id:"demo",apiroot:"https://sky-lighter.appspot.com/api/"%7Dvar storage_cache%3D%7Bwarn_ocrad:false%7D%3Bfunction get_setting(name)%7Breturn storage_cache%5Bname%5D%7Dfunction save_settings()%7B%7Dfunction put_setting(name,val)%7Bstorage_cache%5Bname%5D%3Dval%3Bsave_settings()%7Dvar precomputed_images%3D%7B%7D%3Bfunction broadcast(data)%7Bif(data.type%3D%3D%27qchunk%27)%7Bvar whitelist%3D%5B"chelsea","equis","hilighting","irate","nobody","protobowl","russia","thiel","tyger","rose","skid"%5Dif(data.id in precomputed_images%7C%7Cwhitelist.indexOf(data.id)%3D%3D-1)%7B%7Delse%7Bprecomputed_images%5Bdata.id%5D%3D%7B%7Dvar xhr%3Dnew XMLHttpRequest()xhr.open(%27GET%27,%27dat/%27%2Bdata.id%2B%27/regions.json%27,true)xhr.onload%3Dfunction()%7Bvar res%3DJSON.parse(xhr.responseText)precomputed_images%5Bdata.id%5D%3Dres%3Breceive(%7Btype:%27region%27,id:data.id,regions:res.regions,chunks:res.chunks%7D)%7Dxhr.send(null)%7D%7Delse if(data.type%3D%3D%27qpaint%27)%7Bvar res%3Dprecomputed_images%5Bdata.id%5D%3Bvar url%3D%27dat/%27%2Bdata.id%2B%27/%27%2Bdata.reg_id.replace(/%5B%5C:%5C-%5D/g,%27%27)%2B%27.png%27%3Bvar tmp%3Dnew Image()tmp.onload%3Dfunction()%7Bvar pls%3Dres._plaster%5Bdata.reg_id%5D%3Breceive(%7Bid:data.id,type:%27painted%27,plaster:url,reg_id:data.reg_id,colors:pls.colors,x:pls.x,y:pls.y,width:pls.width,height:pls.height%7D)%7Dtmp.src%3Durl%3B%7Delse if(data.type%3D%3D%27qocr%27%26%26data.engine%3D%3D"ocrad")%7B%7Delse%7Bconsole.log(data)%7D%7Donload%3Dfunction()%7BsetTimeout(function()%7Binitial_selection()%7D,100)%7Dfunction initial_selection()%7Bvar image%3Dim(document.getElementById(%27tyger%27))%3Bfunction is_loaded()%7Bsel.img%3Dimage.el%3Bsel.stack%3D%5B%5B%7Bregion:image.regions%5B1%5D,line:image.regions%5B1%5D.lines%5B2%5D,word:image.regions%5B1%5D.lines%5B2%5D.words%5B3%5D,letter:image.regions%5B1%5D.lines%5B2%5D.words%5B3%5D.letters%5B0%5D,%7D,null,Date.now()%5D,%5B%7Bregion:image.regions%5B3%5D,%7D%5D,%5Bnull,%7Bregion:image.regions%5B4%5D,line:image.regions%5B4%5D.lines%5B1%5D,word:image.regions%5B4%5D.lines%5B1%5D.words%5B2%5D,letter:image.regions%5B4%5D.lines%5B1%5D.words%5B2%5D.letters.slice(-1)%5B0%5D%7D,Date.now()%5D%5Dupdate_selection()%7Dfunction check_loaded()%7Bif(image.regions.length%3D%3D0)%7Bbroadcast(%7Btype:%27qchunk%27,id:image.id,time:Date.now(),chunks:%5B%5D%7D)setTimeout(check_loaded,100)%7Delse%7Bconsole.log()is_loaded()%7D%7Dcheck_loaded()%3B%7Dvar session_params%3D%7B%7Dvar default_params%3D%7Bkernel_size:3,low_thresh:124,high_thresh:204,max_stroke:35,stroke_ratio:2,min_connectivity:4,min_area:30,std_ratio:0.83,aspect_ratio:10,thickness_ratio:3,height_ratio:2.5,scale:1.3,letter_occlude_thresh:7,breakdown_ratio:0.4,elongate_ratio:1.9,max_substroke:15,min_linespacing:0.1,max_linespacing:1.7,col_breakdown:0.3,max_misalign:0.1,col_mergethresh:0.3,lettersize:0.4,debug:false,chunk_size:250,chunk_overlap:90,%7Dvar session_id%3Duuid()var image_counter%3D0%3Bvar images%3D%7B%7D%3Bfunction get_id(img)%7Bif(!img)return%3Bif(typeof img%3D%3D%27string%27)return img%3Bfunction clean(str)%7Breturn str.replace(/%5B%5Ea-z0-9.%5C/_%5C-%5D/gi,%27%27)%7Dif(!(%27__naptha_id%27 in img))%7Bvar readable%3Dclean(img.src.replace(/%5E.*%5C/(.*)%24/g,%27%241%27).split(%27.%27)%5B0%5D)%3Bimg.__naptha_id%3D(image_counter%2B%2B)%2B%27**%27%2Breadable%2B%27**%27%2Bclean(img.src)%2B%27**%27%2Bsession_id%3Bif(global_params.simple_ids)%7Bimg.__naptha_id%3Dreadable%7D%7Dreturn img.__naptha_id%7Dfunction im(img)%7Bvar id%3Dget_id(img)if(id in images)return images%5Bid%5D%3Bfunction shallow(obj)%7Bvar new_obj%3D%7B%7D%3Bfor(var i in obj)%7Bnew_obj%5Bi%5D%3Dobj%5Bi%5D%7Dreturn new_obj%3B%7Dvar params%3Dshallow(default_params)%3Bvar src%3Dimg.src%3Bif(src.indexOf("http://localhost/Dropbox/Projects/naptha/")%3D%3D0%7C%7Cglobal_params.demo_mode)%7Bsrc%3D"demo:"%2Bimg.src.replace(/%5E.*%5C/(.*%3F)%5C..*%3F%24/g,%27%241%27)%7Dvar image%3Dimages%5Bid%5D%3D%7Bid:id,el:img,width:Math.round(img.naturalWidth*params.scale),height:Math.round(img.naturalHeight*params.scale),src:src,real_src:img.src,chunks:%5B%5D,regions:%5B%5D,engine:%27default%27,params:params%7Dcollect_contexts()return image%3B%7Dfunction receive(data)%7Bif(data.type%3D%3D%27getparam%27)%7Bvar image%3Dim(data.id)broadcast(%7Btype:%27gotparam%27,id:image.id,src:image.src,real_src:image.real_src,params:image.params,initial_chunk:data.initial_chunk%7D)%7Delse if(data.type%3D%3D%27region%27)%7Bvar image%3Dim(data.id)image.regions%3Ddata.regions image.chunks%3Ddata.chunks image.stitch_debug%3Ddata.stitch_debug update_selection()draw_annotations(image.el,image)%7Delse if(data.type%3D%3D%27painted%27)%7Bvar image%3Dim(data.id)var mask%3Dnew Image()mask.style.webkitTransform%3D%27translateZ(0)%27 mask.src%3Ddata.plaster%3Bif(!image.plaster)image.plaster%3D%7B%7D%3Bimage.plaster%5Bdata.reg_id%5D%3D%7Bmask:mask,colors:data.colors,x:data.x,y:data.y,width:data.width,height:data.height,finished:Date.now()%7Dupdate_overlay(image.el)init_layer(mask,%27plaster%27)var sx%3D(image.el.width/image.el.naturalWidth),sy%3D(image.el.height/image.el.naturalHeight)%3Bmask.style.left%3D(sx*data.x)%2B%27px%27 mask.style.top%3D(sy*data.y)%2B%27px%27 mask.style.width%3D(sx*data.width)%2B%27px%27 mask.style.height%3D(sy*data.height)%2B%27px%27 mask.style.transition%3D%27opacity 1s%27 mask.style.opacity%3D%270%27 image.overlay.appendChild(mask)image.regions.forEach(function(region)%7Bif(region.id%3D%3Ddata.reg_id)%7Btranslate_region(image,region)%7D%7D)draw_overlays(image)update_selection()%3B%7Delse if(data.type%3D%3D%27recognized%27)%7Bvar image%3Dim(data.id)if(!image.ocr)image.ocr%3D%7B%7D%3Bvar plain_text%3Ddata.text%3Btry%7Bplain_text%3DJSON.parse(plain_text).text%7Dcatch(err)%7Bif(data.enc%3D%3D%27tesseract%27)%7Bdata.enc%3D%27error%27%7D%7Dif(data.enc%3D%3D%27error%27%7C%7C/%5EERROR/i.test(plain_text))%7Bimage.regions.forEach(function(region)%7Bif(region.id%3D%3Ddata.reg_id)%7Berror_message(image,region,plain_text)%7D%7D)delete image.ocr%5Bdata.reg_id%5Dreturn%7Dif(data.enc%3D%3D%27tesseract%27)%7Bvar json%3DJSON.parse(data.text)%3Bvar raw%3DparseTesseract(json)%3B((image.lookup%7C%7C%7B%7D).chunks%7C%7C%5B%5D).push(%7Bengine:data.engine,meta:json.meta,key:json.key%7D)%7Delse%7Bvar raw%3Ddata.raw%3B%7Dvar ocr%3Dimage.ocr%5Bdata.reg_id%5D%3Bif(ocr._engine!%3Ddata.engine)return%3Bocr.finished%3DDate.now()delete ocr.processing%3Bocr.raw%3Draw ocr.text%3Ddata.text%3Bvar broken%3Draw.some(function(block)%7Breturn isNaN(block.x)%7C%7CisNaN(block.y)%7C%7CisNaN(block.w)%7C%7CisNaN(block.h)%7D)%3Bif(broken)%7Bvar ocr%3Dimage.ocr%5Bdata.reg_id%5D%3Bocr.raw%3Dnull%3Bocr.error%3Dtrue%3Bocr.text%3D%27ERROR: %27%2Bdata.text%3B%7Dif(check_selection())modify_clipboard()%3Bupdate_translations(image)draw_overlays(image)update_selection()%3B%7D%7Dfunction virtualize_region(image,region)%7Bfunction transform(region)%7Bif(image.translate%26%26region.id in image.translate%26%26image.translate%5Bregion.id%5D.finished%26%26image.virtual%26%26region.id in image.virtual)%7Bvar lang%3D(image.translate%5Bregion.id%5D%7C%7C%7B%7D).language if(lang%26%26lang!%3D%27erase%27)return image.virtual%5Bregion.id%5D%3B%7Dreturn region%7Dif(region.map)return region.map(transform)%3Breturn transform(region)%7Dfunction get_lookup_chunks(image,region)%7Bfunction frac_intersect(a,b)%7Bvar width%3DMath.min(a.x1,b.x1)-Math.max(a.x0,b.x0),height%3DMath.min(a.y1,b.y1)-Math.max(a.y0,b.y0)%3Bvar max_area%3DMath.max((1%2Ba.x1-a.x0)*(1%2Ba.y1-a.y0),(1%2Bb.x1-b.x0)*(1%2Bb.y1-b.y0))return(width>0%26%26height>0)%3F(width*height/max_area):0%3B%7Dvar filtered%3D((image.lookup%7C%7C%7B%7D).chunks%7C%7C%5B%5D).filter(function(chunk)%7Bvar meta%3Dchunk.meta%3Bvar intersect%3Dfrac_intersect(%7Bx0:meta.x0/meta.sws,y0:meta.y0/meta.sws,x1:meta.x1/meta.sws,y1:meta.y1/meta.sws%7D,%7Bx0:region.x0/image.params.scale,y0:region.y0/image.params.scale,x1:region.x1/image.params.scale,y1:region.y1/image.params.scale%7D)%3Breturn meta.v%3D%3D3%26%26meta.dir%3D%3Dregion.direction%26%26intersect>0.9%7D)return filtered%7Dfunction get_ocr_engine(image,region,def)%7Bvar ocr%3Dimage.ocr%5Bregion.id%5Dif(ocr.engine%3D%3D%27default%27)%7Bvar filtered%3Dget_lookup_chunks(image,region)return(filtered%5B0%5D%26%26filtered%5B0%5D.engine)%7C%7Cdef%7C%7C%27ocrad%27%7Delse%7Breturn ocr.engine%3B%7D%7Dfunction ocr_region(image,col)%7Bif(!image.ocr)image.ocr%3D%7B%7D%3Bif(col.finished!%3Dtrue)return%3Bif(!(col.id in image.ocr))%7Bimage.ocr%5Bcol.id%5D%3D%7Bengine:"default"%7D%7Dif(col.virtual)%7Bfor(var i%3D0%3Bi<image.regions.length%3Bi%2B%2B)%7Bif(image.regions%5Bi%5D.id%3D%3Dcol.id)col%3Dimage.regions%5Bi%5D%3B%7D%7Dvar ocr%3Dimage.ocr%5Bcol.id%5D%3Bvar eng%3Dget_ocr_engine(image,col)if(ocr._engine!%3Deng)%7Bocr._engine%3Deng delete ocr.finished%3Bdelete ocr.processing%3B%7Dif(ocr.finished%7C%7Cocr.processing)return%3Bdelete ocr.waiting%3Bvar matches%3Dget_lookup_chunks(image,col).filter(function(chunk)%7Breturn chunk.engine%3D%3Deng%7D)if(ocr._engine!%3D%27ocrad%27)%7Bif(!image.lookup)%7Bdo_lookup(image)%7Delse if(image.lookup.error)%7Berror_message(image,col,"Can%27t access lookup server.")%7Delse if(!image.lookup.finished)%7BsetTimeout(function()%7Bocr_region(image,col)%7D,100)%3Breturn%3B%7D%7Dif(matches.length>0)%7Bvar chunk%3Dmatches%5B0%5D%3Bvar xhr%3Dnew XMLHttpRequest()xhr.open(%27GET%27,global_params.apiroot%2B%27read/%27%2Bchunk.key,true)xhr.send()xhr.onload%3Dfunction()%7Breceive(%7Btype:%27recognized%27,enc:%27tesseract%27,reg_id:col.id,id:image.id,engine:eng,text:xhr.responseText%7D)%7D%7Delse%7Bqueue_broadcast(%7Bsrc:image.src,type:%27qocr%27,apiroot:global_params.apiroot,region:col,reg_id:col.id,id:image.id,engine:ocr._engine,swtscale:image.params.scale,swtwidth:image.width%7D)%7Dimage.ocr%5Bcol.id%5D.processing%3DDate.now()%7Dvar broadcast_queue%3D%5B%5D,is_casting%3Dfalse%3Bfunction queue_broadcast(data)%7Bbroadcast_queue.push(data)if(!is_casting)dequeue_broadcast()%3B%7Dfunction dequeue_broadcast()%7Bis_casting%3Dfalse if(broadcast_queue.length)%7Bbroadcast(broadcast_queue.shift())setTimeout(dequeue_broadcast,500)is_casting%3Dtrue%3B%7D%7Dfunction to_chunks(sY,image)%7Bvar num_chunks%3DMath.max(1,Math.ceil((image.height-image.params.chunk_overlap)/(image.params.chunk_size-image.params.chunk_overlap)))var base%3DMath.min(num_chunks-1,Math.floor(sY/(image.params.chunk_size-image.params.chunk_overlap)))var offset%3DsY-base*(image.params.chunk_size-image.params.chunk_overlap)%3Bif(base<%3D0)%7Breturn%5B0%5D%7Delse if(offset<image.params.chunk_overlap/2)%7Breturn%5Bbase-1,base%5D%7Delse if(offset<image.params.chunk_overlap)%7Breturn%5Bbase,base-1%5D%7Delse%7Breturn%5Bbase%5D%7D%7Dfunction valid_image(img)%7Bif(!(img%26%26img.tagName%3D%3D%27IMG%27%26%26img.complete%26%26img.width>150%26%26img.naturalWidth>150%26%26Math.min(img.width,img.naturalWidth)*Math.min(img.height,img.naturalHeight)>19000%26%26img.height>60%26%26img.naturalHeight>60%26%26img.naturalWidth<2000%26%26img.src.length<300%26%26img.ownerDocument.designMode!%3D%27on%27%26%26/%5E(https%3F%7Cfile):/i.test(img.src)))return false%3Bvar n%3Dimg,is_link%3Dfalse%3Bdo%7Bif(n.getAttribute(%27ocr%27)%3D%3D%27off%27)return false%3Bif(global_params.is_extension)%7Bif(n.getAttribute(%27ocr%27)%3D%3D%27custom%27)return false%3B%7Dif(n.tagName%3D%3D%27A%27)%7Bis_link%3Dtrue%3B%7D%7Dwhile((n%3Dn.parentNode)%26%26n.getAttribute)%3Bif(is_link%26%26img.__naptha_id)%7Bvar image%3Dim(img)%3Bvar regpad%3D10%3Bvar total_area%3D0%3Bimage.regions.forEach(function(region)%7Btotal_area%2B%3D(regpad*2%2Bregion.width)*(regpad*2%2Bregion.height)%7D)var frac_text%3Dtotal_area/(image.width*image.height)%3Bif(frac_text>0.85)%7Breturn false%3B%7D%7Dreturn true%7Dfunction image_layout(el)%7Bvar dim%3Del.getBoundingClientRect(),cmp%3Dwindow.getComputedStyle(el)%3Bfunction sty(prop)%7Breturn parseInt(cmp.getPropertyValue(prop),10)%7Dvar X%3Ddim.left%2Bsty(%27padding-left%27)%2Bsty(%27border-left-width%27),Y%3Ddim.top%2Bsty(%27padding-top%27)%2Bsty(%27border-top-width%27)%3Breturn%7Bwidth:el.width,height:el.height,X:X,Y:Y,left:pageXOffset%2BX,top:pageYOffset%2BY%7D%7Dfunction extract_region(ocr,start,end)%7Bvar col%3D(start%26%26start.region)%7C%7Cend.region%3Bif(ocr.error)%7Breturn%5Bocr.text%5D%3B%7Dif(!start%7C%7C!start.line)start%3D%7Bline:col.lines%5B0%5D,region:col%7D%3Bif(!end%7C%7C!end.line)end%3D%7Bline:col.lines%5Bcol.lines.length-1%5D,region:col%7D%3Bif(start.line.id%3D%3Dend.line.id)%7Breturn%5Bextract_line(ocr,start,end)%5D%7Delse%7Bvar within%3Dfalse%3Breturn col.lines.map(function(line)%7Bif(line.id%3D%3Dstart.line.id)%7Bwithin%3Dtrue%3Breturn extract_line(ocr,start,null)%7Delse if(line.id%3D%3Dend.line.id)%7Bwithin%3Dfalse%3Breturn extract_line(ocr,null,end)%7Delse if(within)%7Breturn extract_line(ocr,%7Bline:line,region:col%7D)%7Dreturn null%7D).filter(function(e)%7Breturn e%7D)%7D%7Dfunction extract_line(ocr,start,end)%7Bvar line%3D(start%26%26start.line)%7C%7Cend.line%3Bvar region%3D(start%26%26start.region)%7C%7Cend.region%3Bvar letters%3D%5B%5D.concat.apply(%5B%5D,line.words.map(function(word)%7Breturn word.letters%7D))if(region.virtual)%7Breturn line.words.map(function(word)%7Breturn word.letters.filter(function(letter)%7Bvar rcx%3Dletter.x0/2%2Bletter.x1/2%3Bvar in_range%3D(!(start%26%26start.letter)%7C%7Crcx>start.letter.x0)%26%26(!(end%26%26end.letter)%7C%7Crcx<end.letter.x1)%3Breturn in_range%7D).map(function(e)%7Breturn e._%7D).join(%27%27)%7D).join(%27 %27).trim()%7Dvar yr0%3DInfinity,yr1%3D-Infinity letters.forEach(function(letter)%7Bvar y_pred%3D(letter.cx-line.cx)*Math.tan(line.angle)%2Bline.cy yr0%3DMath.min(yr0,letter.y0-y_pred)yr1%3DMath.max(yr1,letter.y1-y_pred)%7D)var matches%3Docr.raw.filter(function(rec)%7Bvar rcx%3D(rec.x%2Brec.w/2)*region.scale,rcy%3D(rec.y%2Brec.h/2)*region.scale%3Bvar y_pred%3D(rcx-line.cx)*Math.tan(line.angle)%2Bline.cy var in_line%3D(rcy>y_pred%2Byr0%26%26rcy<y_pred%2Byr1)var in_range%3D(!(start%26%26start.letter)%7C%7Crcx>start.letter.x0)%26%26(!(end%26%26end.letter)%7C%7Crcx<end.letter.x1)%3Bvar is_rec%3Drec.matches.length>0%3Breturn in_line%26%26in_range%26%26is_rec%7D)%3Breturn matches.sort(function(a,b)%7Breturn(a.x%2Ba.w)-(b.x%2Bb.w)%7D).map(function(rec)%7Bif(rec.sw)%7Breturn %27 %27%2Brec.matches%5B0%5D%5B0%5D%7Dreturn rec.matches%5B0%5D%5B0%5D%7D).join(%27%27).trim()%7Dvar PROCESSING_PREAMBLE%3D%27<%5B TEXT RECOGNITION IN PROGRESS / MORE INFO: http://projectnaptha.com/process/ %27var PROCESSING_CONCLUDE%3D%27 / TEXT RECOGNITION IN PROGRESS %5D>%27function RegEsc(s)%7Breturn s.replace(/%5B-%5C/%5C%5C%5E%24*%2B%3F.()%7C%5B%5C%5D%7B%7D%5D/g,%27%5C%5C__136__%27)%3B%7D%3Bvar PROCESSING_MATCHER%3Dnew RegExp(RegEsc(PROCESSING_PREAMBLE)%2B%27(.*%3F)%27%2BRegEsc(PROCESSING_CONCLUDE),%27g%27)function extract_selection(sel,image)%7Bvar output%3Dget_selection(sel,image).map(function(pair)%7Bvar start%3Dpair%5B0%5D,end%3Dpair%5B1%5Dvar col%3D(start%26%26start.region)%7C%7Cend.region%3Bvar locator%3D%5Bcol.id,start%26%26start.line%26%26start.line.id,end%26%26end.line%26%26end.line.id,start%26%26start.letter%26%26start.letter.x0,end%26%26end.letter%26%26end.letter.x1%5D.map(function(e)%7Breturn e%7C%7C%27!%27%7D).join(%27%26%27)var variable%3D%27(IDX:%27%2Blocator%2B%27:XDI)%27 if(col.id in image.ocr%26%26image.ocr%5Bcol.id%5D.processing)%7Bvariable%2B%3D%27 / ELAPSED %27%2B((Date.now()-image.ocr%5Bcol.id%5D.processing)/1000).toFixed(2)%2B%27SEC%27%7Dvariable%2B%3D%27 / DATE %27%2B(new Date).toUTCString()return PROCESSING_PREAMBLE%2Bvariable%2BPROCESSING_CONCLUDE%3B%7D).join(%27%5Cn%5Cn%27)var incomplete%3D0%3Bvar has_ocrad%3Dfalse%3Bvar text%3Dsubstitute_recognition(output,function(region_id)%7Bif((region_id in image.ocr)%26%26image.ocr%5Bregion_id%5D.finished)%7Bvar region%3Dvirtualize_region(image,image.regions.filter(function(region)%7Breturn region.id%3D%3Dregion_id%7D)%5B0%5D)%3Bvar ocr%3Dimage.ocr%5Bregion_id%5D%3Bif(ocr%26%26ocr._engine%3D%3D%27ocrad%27%26%26ocr.engine%3D%3D"default")has_ocrad%3Dtrue%3Breturn%7Bregion:region,ocr:ocr%7D%7Dincomplete%2B%2Breturn null%3B%7D)%3Bvar bad_ocr%3Dtext.trim().split(/%5B%5Cs%5C-%5C:%5C%3B%5C%26%5Cif(word.length<%3D2)return false%3Bif(word.toUpperCase()%3D%3Dword)return false%3Breturn!/%5E%5BA-za-z%5D%5Ba-z%5D*%5Ba-z%5C.%5C%7D)%3Bif(bad_ocr%26%26has_ocrad%26%26get_setting(%27warn_ocrad%27))%7Btext%2B%3D%27%5Cn%5CnThis text was recognized by the built-in Ocrad engine. A better transcription may be attained by changing the OCR Engine (under the Language menu) to Tesseract. This message can be removed in the future by unchecking "OCR Disclaimer" (under the Options menu). More info: http://projectnaptha.com/ocrad%27%7Dreturn%7Btext:text,incomplete:incomplete%7D%7Dfunction substitute_recognition(text,interactor)%7Breturn text.replace(PROCESSING_MATCHER,function(all,interior)%7Bvar locmat%3Dinterior.match(/%5C(IDX:(.*%3F):XDI%5C)/)if(locmat%26%26locmat%5B1%5D)%7Bvar locator%3Dlocmat%5B1%5D%3Bvar loc%3Dlocator.split(%27%26%27).map(function(e)%7Breturn e%3D%3D%27!%27%3Fnull:e%7D)var dat%3Dinteractor(loc%5B0%5D)%3Bif(dat%26%26dat.ocr)%7Bvar region%3Ddat.region%3Bvar start%3D%7Bline:loc%5B1%5D%26%26region.lines.filter(function(line)%7Breturn line.id%3D%3Dloc%5B1%5D%7D)%5B0%5D,letter:loc%5B3%5D%26%26%7Bx0:loc%5B3%5D%7D,region:region%7Dvar end%3D%7Bline:loc%5B2%5D%26%26region.lines.filter(function(line)%7Breturn line.id%3D%3Dloc%5B2%5D%7D)%5B0%5D,letter:loc%5B4%5D%26%26%7Bx1:loc%5B4%5D%7D,region:region%7Dreturn extract_region(dat.ocr,start,end).join(%27%5Cn%27).trim()%7D%7Dreturn all%7D)%3B%7Dfunction parseTesseract(response)%7Bvar meta%3Dresponse.meta%3Bvar rotw%3D(meta.x1-meta.x0%2B1)/meta.sws*meta.cos%2Bmeta.xp*2,roth%3D(meta.y1-meta.y0%2B1)/meta.sws*meta.cos%2Bmeta.yp*2%3Bvar text%3Dresponse.text.trim()%3Bif(text.length%3D%3D0)return%5B%5D%3Bvar raw%3Dtext.split(%27%5Cn%27).map(function(e)%7Bvar first%3De.split(%27%5Ct%27)%5B0%5D%3Bvar d%3Dfirst.trim().split(%27 %27)%3Bvar x%3DparseInt(d%5B0%5D),y%3DparseInt(d%5B1%5D),w%3DparseInt(d%5B2%5D),h%3DparseInt(d%5B3%5D),conf%3DparseFloat(d%5B4%5D)%3Bvar cx%3Dx%2Bw/2-rotw/2,cy%3Dy%2Bh/2-roth/2%3Bvar rcx%3D(cx*Math.cos(meta.ang)-cy*Math.sin(meta.ang)%2Brotw/2)/meta.red,rcy%3D(cx*Math.sin(meta.ang)%2Bcy*Math.cos(meta.ang)%2Broth/2)/meta.red return%7Bx:(rcx-w/2/meta.red)/meta.cos%2Bmeta.x0/meta.sws-meta.xp,y:(rcy-h/2/meta.red)/meta.cos%2Bmeta.y0/meta.sws-meta.yp,w:w/meta.red/meta.cos,h:h/meta.red/meta.cos,sw:/SW%24/.test(first.trim()),matches:%5B%5Be.slice(first.length%2B1),conf%5D%5D%7D%7D)return raw%3B%7Dfunction parseOcrad(response)%7Bvar meta%3Dresponse.meta%3Bvar rotw%3D(meta.x1-meta.x0%2B1)/meta.sws*meta.cos%2Bmeta.xp*2,roth%3D(meta.y1-meta.y0%2B1)/meta.sws*meta.cos%2Bmeta.yp*2%3Bvar raw%3Dresponse.raw.map(function(e)%7Breturn e.match(/%5E%5Cs*(%5Cd%2B)%5Cs%2B(%5Cd%2B)%5Cs%2B(%5Cd%2B)%5Cs%2B(%5Cd%2B)%5Cs*%3B%5Cs*(%5Cd%2B)(%5C,%3F.%2B)%3F%24/)%7D).filter(function(e)%7Breturn e%7D).map(function(e)%7Bvar x%3DparseInt(e%5B1%5D),y%3DparseInt(e%5B2%5D),w%3DparseInt(e%5B3%5D),h%3DparseInt(e%5B4%5D),g%3DparseInt(e%5B5%5D)%3Bvar matches%3D%5B%5D%3Bif(g>0)%7Bvar etc%3De%5B6%5D.trim()%3Bwhile(etc%5B0%5D%3D%3D%27,%27%26%26etc%5B1%5D%3D%3D%27 %27)%7Betc%3Detc.slice(2)var m%3Detc.match(/%5E%5Cmatches.push(%5Bm%5B1%5D,parseInt(m%5B2%5D)%5D)etc%3Detc.slice(m%5B0%5D.length)%7D%7Dif(matches.length!%3Dg)%7Bconsole.error(%27recognition count mismatch%27,g,matches)%7Dvar cx%3Dx%2Bw/2-rotw/2,cy%3Dy%2Bh/2-roth/2%3Bvar rcx%3D(cx*Math.cos(meta.ang)-cy*Math.sin(meta.ang)%2Brotw/2)/meta.red,rcy%3D(cx*Math.sin(meta.ang)%2Bcy*Math.cos(meta.ang)%2Broth/2)/meta.red return%7Bx:(rcx-w/2/meta.red)/meta.cos%2Bmeta.x0/meta.sws-meta.xp,y:(rcy-h/2/meta.red)/meta.cos%2Bmeta.y0/meta.sws-meta.yp,w:w/meta.red/meta.cos,h:h/meta.red/meta.cos,matches:matches%7D%7D)%3Breturn raw%3B%7Dfunction UnionFind(count)%7Bthis.roots%3Dnew Array(count)%3Bthis.ranks%3Dnew Array(count)%3Bfor(var i%3D0%3Bi<count%3B%2B%2Bi)%7Bthis.roots%5Bi%5D%3Di%3Bthis.ranks%5Bi%5D%3D0%3B%7D%7DUnionFind.prototype.length%3Dfunction()%7Breturn this.roots.length%3B%7DUnionFind.prototype.makeSet%3Dfunction()%7Bvar n%3Dthis.roots.length%3Bthis.roots.push(n)%3Bthis.ranks.push(0)%3Breturn n%3B%7DUnionFind.prototype.find%3Dfunction(x)%7Bvar roots%3Dthis.roots%3Bwhile(roots%5Bx%5D!%3D%3Dx)%7Bvar y%3Droots%5Bx%5D%3Broots%5Bx%5D%3Droots%5By%5D%3Bx%3Dy%3B%7Dreturn x%3B%7DUnionFind.prototype.link%3Dfunction(x,y)%7Bvar xr%3Dthis.find(x),yr%3Dthis.find(y)%3Bif(xr%3D%3D%3Dyr)%7Breturn%3B%7Dvar ranks%3Dthis.ranks,roots%3Dthis.roots,xd%3Dranks%5Bxr%5D,yd%3Dranks%5Byr%5D%3Bif(xd<yd)%7Broots%5Bxr%5D%3Dyr%3B%7Delse if(yd<xd)%7Broots%5Byr%5D%3Dxr%3B%7Delse%7Broots%5Byr%5D%3Dxr%3B%2B%2Branks%5Bxr%5D%3B%7D%7Dfunction equivalence_classes(elements,is_equal)%7Bvar node%3D%5B%5Dfor(var i%3D0%3Bi<elements.length%3Bi%2B%2B)%7Bnode.push(%7Bparent:0,element:elements%5Bi%5D,rank:0%7D)%7Dfor(var i%3D0%3Bi<node.length%3Bi%2B%2B)%7Bvar root%3Dnode%5Bi%5Dwhile(root.parent)%7Broot%3Droot.parent%3B%7Dfor(var j%3D0%3Bj<node.length%3Bj%2B%2B)%7Bif(i%3D%3Dj)continue%3Bif(!is_equal(node%5Bi%5D.element,node%5Bj%5D.element))continue%3Bvar root2%3Dnode%5Bj%5D%3Bwhile(root2.parent)%7Broot2%3Droot2.parent%3B%7Dif(root2!%3Droot)%7Bif(root.rank>root2.rank)%7Broot2.parent%3Droot%3B%7Delse%7Broot.parent%3Droot2%3Bif(root.rank%3D%3Droot2.rank)%7Broot2.rank%2B%2B%7Droot%3Droot2%3B%7Dvar node2%3Dnode%5Bj%5D%3Bwhile(node2.parent)%7Bvar temp%3Dnode2%3Bnode2%3Dnode2.parent%3Btemp.parent%3Droot%3B%7Dvar node2%3Dnode%5Bi%5D%3Bwhile(node2.parent)%7Bvar temp%3Dnode2%3Bnode2%3Dnode2.parent%3Btemp.parent%3Droot%3B%7D%7D%7D%7Dvar index%3D0%3Bvar clusters%3D%5B%5D%3Bfor(var i%3D0%3Bi<node.length%3Bi%2B%2B)%7Bvar j%3D-1%3Bvar node1%3Dnode%5Bi%5Dwhile(node1.parent)%7Bnode1%3Dnode1.parent%7Dif(node1.rank>%3D0)%7Bnode1.rank%3D~index%2B%2B%3B%7Dj%3D~node1.rank%3Bif(clusters%5Bj%5D)%7Bclusters%5Bj%5D.push(elements%5Bi%5D)%7Delse%7Bclusters%5Bj%5D%3D%5Belements%5Bi%5D%5D%7D%7Dreturn clusters%3B%7Dvar sel%3D%7Bimg:null,stack:%5B%5D,start:null,end:null,deselect_time:0%7Dwindow.addEventListener(%27resize%27,handle_resize)document.addEventListener(%27copy%27,function(e)%7Bif(sel.start%7C%7Csel.stack.length)%7Bmodify_clipboard()var image%3Dim(sel.img)%3Bvar block%3Dextract_selection(sel,image)%3Bif(block.incomplete>0)%7Bbroadcast(%7Btype:%27clipwatch%27,id:image.id%7D)%7Dif(/%5C.(jpe%3Fg%7Cpng%7Cgif)%24/i.test(location.pathname))%7BsetTimeout(function()%7Bbroadcast(%7Btype:%27copy%27,id:image.id,text:block.text%7D)%7D,10)%7DsetTimeout(function()%7Bvar image%3Dim(sel.img)selected_regions().forEach(function(region)%7Bvar ocr%3Dimage.ocr%5Bregion.id%5D%3Bif(ocr._engine%3D%3D%27ocrad%27)%7B%7D%7D)%7D,10)%7D%7D)function collect_contexts()%7Bfor(var i in images)%7Bvar image%3Dimages%5Bi%5D%3Bif(image.el%26%26image.el.getBoundingClientRect)%7Bvar dim%3Dimage.el.getBoundingClientRect()if(dim.width%3D%3D0%7C%7Cdim.height%3D%3D0)%7Bdispose_overlay(image.el)delete images%5Bget_id(image.el)%5D%7D%7Dif(image.overlay%26%26image.overlay.childNodes.length%3D%3D0)%7Bdispose_overlay(image.el)%7D%7Dvar container%3Dget_container()%3Bif(container.childNodes.length%3D%3D0)%7Bif(container.parentNode)%7Bcontainer.parentNode.removeChild(container)%7D%7D%7Dfunction handle_resize(e)%7Bif(sel.img)%7Bvar image%3Dim(sel.img)%3Bupdate_selection()%7Dcollect_contexts()for(var id in images)%7Bupdate_overlay(images%5Bid%5D.el)%3B%7D%7Dvar text_layer%3Bfunction modify_clipboard()%7Bif(!text_layer)%7Btext_layer%3Ddocument.createElement(%27pre%27)%7Dtext_layer.className%3D%27project_naptha_text_layer%27 text_layer.innerHTML%3D%27%27 text_layer.style.top%3Dtext_layer.style.left%3D%27-100px%27 text_layer.style.width%3Dtext_layer.style.height%3D%2710px%27 text_layer.style.overflow%3D%27hidden%27 text_layer.style.position%3D%27absolute%27 document.body.appendChild(text_layer)var range%3Ddocument.createRange()%3Bvar text%3D%27<error extracting text %27%2B(new Date)%2B%27>%27 if(sel.img)%7Bvar image%3Dim(sel.img)%3Btext%3Dextract_selection(sel,image).text%7Dif(text)%7Btext_layer.textContent%3Dtext%3Btext_layer.focus()%3Brange.selectNodeContents(text_layer)var selection%3Dwindow.getSelection()selection.removeAllRanges()%3Bselection.addRange(range)%3B%7Delse%7Bremove_textlayer()%7D%7Dfunction push_sel(start,end)%7Bsel.stack.push(%5Bstart,end,Date.now()%5D)%7Dfunction clear_selection()%7Bif(sel.img)render_selection(sel.img,%5B%5D,%7B%7D)%3Bsel.stack%3D%5B%5D%3B%7Dfunction change_select()%7Bif(!check_selection())%7Bclear_selection()%3BsetTimeout(remove_textlayer,0)%7D%7Dfunction remove_textlayer()%7Bif(text_layer)%7Bif(text_layer.parentNode)text_layer.parentNode.removeChild(text_layer)%3B%7D%7Dfunction check_selection()%7Bvar selection%3Dwindow.getSelection()%3Bif(selection.rangeCount!%3D1)return false%3Bvar node%3Dselection.anchorNode%3Bvar container%3Dget_container()%3Bdo%7Bif(node%3D%3Dtext_layer%7C%7Cnode%3D%3Dcontainer)%7Breturn true%3B%7D%7Dwhile(node%3Dnode.parentNode)%3Breturn false%3B%7Dfunction do_lookup(image)%7Bimage.lookup%3D%7Bloading:Date.now()%7Dvar xhr%3Dnew XMLHttpRequest()%3Bif(window.XDomainRequest)%7Bxhr%3Dnew XDomainRequest()%7Dxhr.open(%27GET%27,global_params.apiroot%2B%27lookup%3Furl%3D%27%2BencodeURIComponent(image.src),true)xhr.send()xhr.onerror%3Dfunction()%7Bimage.lookup%3D%7Berror:true%7D%7Dxhr.onload%3Dfunction()%7Bimage.lookup%3DJSON.parse(xhr.responseText)image.lookup.finished%3DDate.now()%7D%7Dfunction update_selection()%7Bif(!sel%7C%7C!sel.img)return%3Bvar image%3Dim(sel.img)var selection%3Dget_selection(sel,image)%3Bif(!image.lookup%26%26navigator.onLine%26%26!session_params.no_lookup)%7Bdo_lookup(image)%7Drender_selection(sel.img,selection,image.params)if(selection.length>0)%7Bselection.forEach(function(pair)%7Bvar col%3D(pair%5B0%5D%26%26pair%5B0%5D.region)%7C%7Cpair%5B1%5D.region%3Bocr_region(image,col)%7D)%7Delse if(sel.start)%7Bvar col%3Dget_cursor(sel.start).region%3Bocr_region(image,col)%7D%7Dfunction fix_squig(start,end)%7Bvar col%3D(start%26%26start.region)%7C%7Cend.region%3Bif(!start%7C%7C!start.letter)start%3D%7Bletter:%7Bx0:col.x0%7D,line:col.lines%5B0%5D,region:col%7D%3Bif(!end%7C%7C!end.letter)end%3D%7Bletter:%7Bx1:col.x1%7D,line:col.lines%5Bcol.lines.length-1%5D,region:col%7D%3Breturn%5Bstart,end,col%5D%7Dfunction rectify_squig(start,end)%7Bvar _%3Dfix_squig(start,end),start%3D_%5B0%5D,end%3D_%5B1%5D,col%3D_%5B2%5D%3Bfunction sanity_check(box)%7Breturn box.x1>box.x0%26%26box.y1>box.y0%7Dif(start.line%26%26start.line%3D%3Dend.line)%7Breturn%5B%7Bx0:start.letter.x0,y0:start.line.y0,x1:end.letter.x1,y1:start.line.y1%7D%5D.filter(sanity_check)%7Delse%7Breturn%5B%7Bx0:start.letter.x0,y0:start.line.y0,x1:col.x1,y1:start.line.y1%7D,%7Bx0:col.x0,y0:start.line.y1,x1:col.x1,y1:end.line.y0%7D,%7Bx0:col.x0,y0:end.line.y0,x1:end.letter.x1,y1:end.line.y1%7D%5D.filter(sanity_check)%7D%7Dfunction box_intersect(a,b)%7Bvar width%3DMath.min(a.x1,b.x1)-Math.max(a.x0,b.x0),height%3DMath.min(a.y1,b.y1)-Math.max(a.y0,b.y0)%3Breturn(width>0%26%26height>0)%3Fwidth*height:0%3B%7Dfunction selected_regions()%7Bvar image%3Dim(sel.img)%3Breturn get_selection(sel,image).map(function(pair)%7Bvar col%3D(pair%5B0%5D%26%26pair%5B0%5D.region)%7C%7Cpair%5B1%5D.region%3Breturn image.regions.filter(function(region)%7Breturn region.id%3D%3Dcol.id%3B%7D)%5B0%5D%7D)%3B%7Dfunction get_selection(sel,image)%7Bvar squigs%3Dsel.stack.map(function(e)%7Bvar start%3De%5B0%5D,end%3De%5B1%5D%3Bif(start%26%26end)%7Bif(start.line%3D%3Dend.line)%7Bif(end.letter.x1<start.letter.x0)%7Breturn%5Bend,start%5D%7D%7Delse if(end.line.y1<start.line.y0)%7Breturn%5Bend,start%5D%7D%7Dreturn%5Bstart,end%5D%7D).concat(current_selection(sel,image)).filter(function(sel)%7Bvar start%3Dsel%5B0%5D,end%3Dsel%5B1%5Dvar sel_col%3D(start%26%26start.region)%7C%7Cend.region%3Bvar regions%3Dvirtualize_region(image,image.regions)var col_exists%3Dregions.some(function(col)%7Breturn sel_col%3D%3Dcol%7D)if(!col_exists%26%26sel_col)%7Bvar col_can%3Dregions.map(function(col)%7Breturn%5Bbox_intersect(sel_col,col),col%5D%7D).filter(function(n)%7Breturn n%5B0%5D>0%7D).sort(function(a,b)%7Breturn b%5B0%5D-a%5B0%5D%7D).map(function(n)%7Breturn n%5B1%5D%7D)%3Bif(col_can.length)%7Bvar candidate%3Dcol_can%5B0%5Dif(start%26%26start.region)start.region%3Dcandidate%3Bif(end%26%26end.region)end.region%3Dcandidate%3Breturn true%7D%7Dreturn col_exists%7D)%3Bfunction cursor_adjacent(a,b)%7Bif(a.line!%3Db.line)return false%3Bvar line%3Da.line%3Bvar lax%3D-1,lbx%3D-1%3Bvar wax%3D-1,wbx%3D-1%3Bline.words.forEach(function(word,wi)%7Bif(word%3D%3Da.letter)wax%3Dwi%3Bif(word%3D%3Db.letter)wbx%3Dwi%3Bword.letters.forEach(function(letter,li)%7Bif(letter%3D%3Da.letter)lax%3Dli%3Bif(letter%3D%3Db.letter)lbx%3Dli%3B%7D)%7D)if(lax!%3D-1%26%26lbx!%3D-1)%7Breturn(Math.abs(lbx-lax)%3D%3D1)%7Dif(wax!%3D-1%26%26wbx!%3D-1)%7Breturn(Math.abs(wbx-wax)%3D%3D1)%7Dreturn false%7Dvar merged%3Dequivalence_classes(squigs,function(a,b)%7Bvar a_col%3D(a%5B0%5D%26%26a%5B0%5D.region)%7C%7Ca%5B1%5D.region%3Bvar b_col%3D(b%5B0%5D%26%26b%5B0%5D.region)%7C%7Cb%5B1%5D.region%3Bif(a_col!%3Db_col)return false%3Bvar a_rects%3Drectify_squig(a%5B0%5D,a%5B1%5D)%3Bvar b_rects%3Drectify_squig(b%5B0%5D,b%5B1%5D)%3Bvar does_intersect%3Da_rects.some(function(a_rect)%7Breturn b_rects.some(function(b_rect)%7Breturn box_intersect(a_rect,b_rect)%7D)%7D)%3Breturn does_intersect%7D)return merged.map(function(ranges)%7Bif(ranges.length%3D%3D1)return ranges%5B0%5D%3Bvar sorted_starts%3Dranges.map(function(e)%7Breturn fix_squig(e%5B0%5D,e%5B1%5D)%5B0%5D%7D).sort(function(a_start,b_start)%7Bif(a_start.line.y0<b_start.line.y0)%7Breturn-1%7Delse if(a_start.line.y0%3D%3Db_start.line.y0)%7Bif(a_start.letter.x0<b_start.letter.x0)%7Breturn-1%7D%7Dreturn 1%7D)var sorted_ends%3Dranges.map(function(e)%7Breturn fix_squig(e%5B0%5D,e%5B1%5D)%5B1%5D%7D).sort(function(a_start,b_start)%7Bif(a_start.line.y1>b_start.line.y1)%7Breturn-1%7Delse if(a_start.line.y1%3D%3Db_start.line.y1)%7Bif(a_start.letter.x1>b_start.letter.x1)%7Breturn-1%7D%7Dreturn 1%7D)return%5Bsorted_starts%5B0%5D,sorted_ends%5B0%5D%5D%7D)%7Dfunction current_selection(sel,image)%7Bif(!image)return%5B%5D%3Bvar params%3Dimage.params%3Bvar start%3Dsel.start%26%26get_cursor(sel.start)var end%3Dsel.end%26%26get_cursor(sel.end)if(!start%7C%7C!end)return%5B%5D%3Bfunction swap()%7Bvar t%3Dstart%3Bstart%3Dend%3Bend%3Dt%3B%7Dvar selection%3D%5B%5D%3Bvar now%3DDate.now()var regions%3Dvirtualize_region(image,image.regions)if(start.region%3D%3Dend.region%26%26start.line%26%26end.line)%7Bif(start.line%3D%3Dend.line)%7Bif(start.letter.x0>%3Dend.letter.x1)swap()%3B%7Delse%7Bif(sel.start.Y>sel.end.Y)swap()%3B%7Dselection.push(%5Bstart,end,now%5D)%7Delse if((!start.region%7C%7C!end.region)%7C%7Cstart.region!%3Dend.region)%7Bif(end.region%26%26start.region)%7Bif(start.region.x0>end.region.x1)%7Bswap()%7Dif(start.region.y0>end.region.y1)%7Bswap()%3B%7D%7Delse if(sel.start.Y>sel.end.Y)%7Bswap()%7Dvar intersector%3D%7Bx0:Math.min(sel.start.X,sel.end.X)*params.scale-1,x1:Math.max(sel.start.X,sel.end.X)*params.scale%2B1,y0:Math.min(sel.start.Y,sel.end.Y)*params.scale-1,y1:Math.max(sel.start.Y,sel.end.Y)*params.scale%2B1,%7Dvar x0%3DInfinity,y0%3DInfinity,x1%3D0,y1%3D0%3Bregions.forEach(function(region)%7Bvar width%3DMath.min(intersector.x1,region.x1)-Math.max(intersector.x0,region.x0),height%3DMath.min(intersector.y1,region.y1)-Math.max(intersector.y0,region.y0)%3Bif(width>0%26%26height>0)%7Bx0%3DMath.min(x0,region.x0)y0%3DMath.min(y0,region.y0)x1%3DMath.max(x1,region.x1)y1%3DMath.max(y1,region.y1)%7D%7D)var path%3Dregions.filter(function(region)%7Bvar width%3DMath.min(x1,region.x1)-Math.max(x0,region.x0),height%3DMath.min(y1,region.y1)-Math.max(y0,region.y0)%3Breturn width>0%26%26height>0%7D)path.forEach(function(region)%7Bif(region%3D%3Dstart.region)%7Bselection.push(%5Bstart,null,now%5D)%7Delse if(region%3D%3Dend.region)%7Bselection.push(%5Bnull,end,now%5D)%7Delse%7Bselection.push(%5B%7Bregion:region%7D,null,now%5D)%7D%7D)%7Dreturn selection%3B%7Dfunction render_selection(img,selection,params)%7Bvar image%3Dim(img)if(selection.length%3D%3D0)%7Blayer_clear(img,%27highlight%27)if(image.flame)%7Bimage.flame.stopEmit%3Dtrue%3B%7Dreturn%7Dvar chunks%3D%5B%5D%3Bselection.forEach(function(pair)%7Bvar col%3D(pair%5B0%5D%26%26pair%5B0%5D.region)%7C%7Cpair%5B1%5D.region%3Bif(col.finished)return%3B%5B%5D.concat(to_chunks(col.y0,image),to_chunks(col.y1,image),to_chunks(col.y0-image.params.chunk_size,image),to_chunks(col.y1%2Bimage.params.chunk_size,image)).forEach(function(chunk)%7Bif(chunks.indexOf(chunk)%3D%3D-1%26%26image.chunks.indexOf(chunk)%3D%3D-1)chunks.push(chunk)%3B%7D)%7D)if(chunks.length>0)%7Bbroadcast(%7Btype:%27qchunk%27,id:image.id,time:Date.now(),chunks:chunks%7D)%7Dvar r%3D2%3Bvar lastX,lastY,_%3D%7B%7D,stack%3D%5B%5D%3Bfunction setRotation(angle,x,y)%7Bstack.push(%5B%27setrot%27,%5Bangle,x,y%5D%5D)%7Dfunction closePath()%7Bstack.push(%5B%27closePath%27%5D)%7Dfunction setFill(color)%7Bstack.push(%5B%27setfill%27,%5Bcolor%5D%5D)%7Dfunction setStroke(color)%7Bstack.push(%5B%27setstroke%27,%5Bcolor%5D%5D)%7Dfunction moveTo(x,y)%7Bstack.push(%5B%27moveTo%27,%5BlastX%3Dx,lastY%3Dy%5D%5D)%7Dfunction scale(x,y)%7Bstack.push(%5B%27scale%27,%5Bx,y%5D%5D)%7Dfunction setLineDash(dash)%7Bstack.push(%5B%27dash%27,dash%5D)%7Dfunction arcTo(x,y)%7Bstack.push(%5B%27arcTo%27,%5BlastX,lastY,lastX%3D(x%3D%3D_%3FlastX:x),lastY%3D(y%3D%3D_%3FlastY:y),r%5D%5D)%7Dfunction selectionSquig(start,end)%7Bvar region%3D(start%26%26start.region)%7C%7Cend.region%3Bif(!start%7C%7C!start.letter)%7Bstart%3D%7Bregion:region,letter:%7Bx0:region.x0%7D,line:region.lines%5B0%5D%7D%7Dif(!end%7C%7C!end.letter)%7Bend%3D%7Bregion:region,letter:%7Bx1:region.x1%7D,line:region.lines%5Bregion.lines.length-1%5D%7D%7Dvar xpad%3D1,ypad%3D3%3Bif(start.line!%3Dend.line)%7Bif((end.line.cy-end.line.lineheight/2-ypad)-(start.line.cy%2Bstart.line.lineheight/2%2Bypad)<1)%7Bif(end.letter.x1<%3Dstart.letter.x0)%7BselectionSquig(start,%7Bline:start.line,letter:%7Bx1:region.x1%7D%7D)selectionSquig(%7Bline:end.line,letter:%7Bx0:region.x0%7D%7D,end)return%3B%7Dif(end.line.x0>%3Dstart.line.x1)%7BselectionSquig(start,%7Bline:start.line,letter:%7Bx1:start.line.x1%7D%7D)selectionSquig(%7Bline:end.line,letter:%7Bx0:end.line.x0%7D%7D,end)return%3B%7D%7D%7Dvar angle%3Dregion.angle%3Bif(Math.abs(angle*region.width/params.scale)<2)angle%3D0%3BsetRotation(angle,region.cx,region.cy)scale(1/Math.cos(region.angle),1/Math.cos(region.angle))if(session_params.flame_on)%7BsetFill(%27rgba(255, 255, 0, 0.1)%27)%7Delse%7Bif(region.virtual)%7BsetFill(%27rgba(0, 173, 255, 0.3)%27)%7Delse%7BsetFill(%27rgba(0,100,255,0.4)%27)%7Dif(region.finished)%7BsetLineDash(%5B%5D)%7Delse%7BsetLineDash(%5B4,4%5D)%7D%7Dif(region.lines%5B0%5D.direction%3D%3D-1)%7BsetStroke(%27rgba(40,40,40,0.8)%27)%7Delse%7BsetStroke(%27rgba(211, 229, 255, 0.8)%27)%7DmoveTo(start.letter.x0%2Br*2,start.line.cy-start.line.lineheight/2-ypad)if(start.line!%3Dend.line)%7BarcTo(region.x1%2Bxpad,_)arcTo(_,end.line.cy-end.line.lineheight/2-ypad)%7Dif(Math.abs(region.x1-end.letter.x1)>3*r%7C%7Cstart.line%3D%3Dend.line)%7BarcTo(end.letter.x1%2Bxpad,_)%7DarcTo(_,end.line.cy%2Bend.line.lineheight/2%2Bypad)if(start.line!%3Dend.line)%7BarcTo(region.x0-xpad,_)arcTo(_,start.line.cy%2Bstart.line.lineheight/2%2Bypad)%7Dif(Math.abs(region.x0-start.letter.x0)>3*r%7C%7Cstart.line%3D%3Dend.line)%7BarcTo(start.letter.x0-xpad,_)%7DarcTo(_,start.line.cy-start.line.lineheight/2-ypad)if(start.line%3D%3Dend.line)%7BarcTo(end.letter.x1%2Bxpad,_)%3B%7Delse%7BarcTo(region.x1%2Bxpad,_)%3B%7DclosePath()%7Dselection.forEach(function(pair)%7BselectionSquig(pair%5B0%5D,pair%5B1%5D)%7D)var cx%3D0,cy%3D0,theta%3D0%3Bvar x0%3DInfinity,y0%3DInfinity,x1%3D0,y1%3D0%3Bvar ys_rot%3D1,xs_rot%3D1%3Bfunction trm(x,y)%7Bx-%3Dcx%3By-%3Dcy%3Bvar nx%3D((x*Math.cos(theta)-y*Math.sin(theta)))*xs_rot%2Bcx,ny%3D((x*Math.sin(theta)%2By*Math.cos(theta)))*ys_rot%2Bcy%3Bx0%3DMath.min(x0,nx)%3By0%3DMath.min(y0,ny)%3Bx1%3DMath.max(x1,nx)%3By1%3DMath.max(y1,ny)%3B%7Dstack.forEach(function(op)%7Bvar args%3Dop%5B1%5D%3Bop%3Dop%5B0%5D%3Bif(op%3D%3D%27setrot%27)%7Bcx%3Dargs%5B1%5D%3Bcy%3Dargs%5B2%5D%3Btheta%3Dargs%5B0%5D%3B%7Delse if(op%3D%3D%27moveTo%27)%7Btrm(args%5B0%5D,args%5B1%5D)%7Delse if(op%3D%3D%27arcTo%27)%7Btrm(args%5B0%5D,args%5B1%5D)%3Btrm(args%5B2%5D,args%5B3%5D)%7Delse if(op%3D%3D%27scale%27)%7Bxs_rot%3Dargs%5B0%5D%3Bys_rot%3Dargs%5B1%5D%7D%7D)var dpr%3Dwindow.devicePixelRatio%3Bvar ratio_x%3Dimg.width/img.naturalWidth/params.scale,ratio_y%3Dimg.height/img.naturalHeight/params.scale%3Bvar margin%3D5%3Bx0%3DMath.round(x0*ratio_x-margin)%3By0%3DMath.round(y0*ratio_y-margin)%3Bx1%3DMath.round(x1*ratio_x%2Bmargin)%3By1%3DMath.round(y1*ratio_y%2Bmargin)%3Bvar paper%3Dlayer(img,%27highlight%27,x0,y0)var sfx%3Dpaper.getContext(%272d%27)%3Bpaper.width%3DMath.round((x1-x0)*dpr)paper.height%3DMath.round((y1-y0)*dpr)paper.style.width%3D(x1-x0)%2B%27px%27 paper.style.height%3D(y1-y0)%2B%27px%27 sfx.translate(-dpr*x0,-dpr*y0)sfx.save()sfx.lineWidth%3D1%3Bsfx.beginPath()var cx%3D0,cy%3D0%3Bvar rx%3Dratio_x*dpr,ry%3Dratio_y*dpr%3Bvar ys_rot%3D1,xs_rot%3D1%3Bstack.forEach(function(op)%7Bvar args%3Dop%5B1%5D%3Bop%3Dop%5B0%5D%3Bif(op%3D%3D%27moveTo%27)%7Bsfx.moveTo((rx*args%5B0%5D-cx)*xs_rot,(ry*args%5B1%5D-cy)*ys_rot)%7Delse if(op%3D%3D%27arcTo%27)%7Bsfx.arcTo((rx*args%5B0%5D-cx)*xs_rot,(ry*args%5B1%5D-cy)*ys_rot,(rx*args%5B2%5D-cx)*xs_rot,(ry*args%5B3%5D-cy)*ys_rot,args%5B4%5D*dpr)%7Delse if(op%3D%3D%27closePath%27)%7Bsfx.closePath()sfx.fill()%3Bsfx.stroke()sfx.beginPath()sfx.restore()%3Bsfx.save()%7Delse if(op%3D%3D%27setrot%27)%7Bsfx.translate(cx%3Drx*args%5B1%5D,cy%3Dry*args%5B2%5D)%3Bsfx.rotate(args%5B0%5D)%7Delse if(op%3D%3D%27setfill%27)%7Bsfx.fillStyle%3Dargs%5B0%5D%7Delse if(op%3D%3D%27setstroke%27)%7Bsfx.strokeStyle%3Dargs%5B0%5D%7Delse if(op%3D%3D%27scale%27)%7Bxs_rot%3Dargs%5B0%5D%3Bys_rot%3Dargs%5B1%5D%7Delse if(op%3D%3D%27dash%27)%7Bif(sfx.setLineDash)sfx.setLineDash(args)%3B%7D%7D)if(window.CanvasFlame%26%26session_params.flame_on)%7Bvar flame_height%3D90%3Bif(!image.flame)%7Bvar flamecanvas%3Dlayer(img,%27pun%27,0,-flame_height)flamecanvas.width%3Dimg.width flamecanvas.height%3Dimg.height%2Bflame_height image.flame%3Dnew CanvasFlame(flamecanvas)%3B%7Dvar flame%3Dimage.flame%3Bflame.orig_context.clearRect(0,0,flame.width,flame.height)flame.orig_context.drawImage(paper,x0,y0%2Bflame_height,x1-x0,y1-y0)%3Bflame.updateEmbers()flame.stopEmit%3Dfalse%3Bflame.start(-1)%7D%7Dvar lastClientX%3D0,lastClientY%3D0%3Bvar lastPageX%3D0,lastPageY%3D0,lastPageT%3Bvar meanDX%3D0,meanDY%3D0%3Bvar lastPrediction%3D0%3Bdocument.addEventListener(%27mousemove%27,track_mouse,true)document.addEventListener(%27mousedown%27,begin_select,true)document.addEventListener(%27mouseup%27,finish_select,true)document.addEventListener(%27keydown%27,key_select,false)document.addEventListener(%27click%27,function(e)%7Bif(Date.now()-sel.deselect_time<100)%7Be.preventDefault()e.stopPropagation()e.stopImmediatePropagation()%7Dif(!valid_image(e.target))return%3Bif(e.button!%3D0)return%3Bvar mouse%3Dget_mouse(e)%3Bif(!mouse.img)return%3Bvar cursor%3Dget_cursor(mouse)%3Bif(cursor.letter)%7Be.preventDefault()e.stopPropagation()e.stopImmediatePropagation()%7D%7D,true)Element.prototype.has%3Dfunction(cls)%7Breturn this.className.split(" ").indexOf(cls)!%3D-1%7Dfunction begin_select(e)%7Bif(!menu_levels.some(function(menu)%7Breturn is_child(e.target,menu)%7D))%7Bcreate_menu(%5B%5D,0)%7Dif(e.target.has(%27contextmenu_overlay%27))%7Be.preventDefault()return%3B%7Dif(!valid_image(e.target))%7Bif(sel.start%7C%7Csel.stack.length>0)%7BrequestAnimationFrame(change_select)%3B%7Dreturn%3B%7Dvar mouse%3Dget_mouse(e)%3Bif(!mouse.img)return%3Bif(e.button!%3D0)return%3Bpredict_mouse()var img%3Dmouse.img%3Bif(sel.img!%3Dimg)%7Bclear_selection()%7Dsel.img%3Dimg%3Bvar image%3Dim(img)%3Bif(!image)return%3Bupdate_pointer(mouse,image)%3Bvar cursor%3Dget_cursor(mouse)%3Bif(e.detail%3D%3D1)%7Bif(cursor.letter)%7Bif(!e.shiftKey)clear_selection()%3Bsel.start%3Dmouse%3Bsel.end%3Dnull%3Be.preventDefault()e.stopPropagation()e.stopImmediatePropagation()%7Delse if(!e.shiftKey)%7Bclear_selection()%7D%7Delse if(e.detail>1)%7Bif(e.detail%3D%3D2%26%26cursor.word)%7Bif(!e.shiftKey)clear_selection()%3Bif(cursor.line.words.length%3D%3D1)%7Bpush_sel(cursor,cursor)%7Delse%7Bpush_sel(%7Bregion:cursor.region,line:cursor.line,letter:cursor.word.letters%5B0%5D%7D,%7Bregion:cursor.region,line:cursor.line,letter:cursor.word.letters%5Bcursor.word.letters.length-1%5D%7D)%7D%7Delse if(e.detail%3D%3D3%26%26cursor.region)%7Bif(!e.shiftKey)clear_selection()%3Bpush_sel(%7Bregion:cursor.region%7D)%7Delse%7Bif(e.detail%3D%3D10)session_params.flame_on%3Dtrue%3Bif(e.detail%3D%3D42)alert("Hi. My name is dug. I have just met you. I love you.")%3B%7D%7Dupdate_selection()requestAnimationFrame(modify_clipboard)%7Dfunction key_select(e)%7Bif(menu_levels.length)%7Bmenu_keyhandle(e)%7Dif(!sel.img)return%3Bvar image%3Dim(sel.img)%3Bif(sel.stack.length%3D%3D0)return%3Bvar end%3Dsel.stack%5Bsel.stack.length-1%5D%5B1%5Dfunction get_letters(line)%7Breturn%5B%5D.concat.apply(%5B%5D,line.words.map(function(e)%7Breturn e.letters%7D))%7Dif(e.keyIdentifier%3D%3D%27Down%27%26%26e.shiftKey)%7Bvar index%3Dend.region.lines.indexOf(end.line)var line%3Dend.region.lines%5Bindex%2B1%5D%3Bif(line)%7Bend.line%3Dline%3Bend.letter%3Dget_letters(line).sort(function(a,b)%7Breturn Math.abs(a.x0-end.letter.x0)-Math.abs(b.x0-end.letter.x0)%7D)%5B0%5D%7De.preventDefault()%7Delse if(e.keyIdentifier%3D%3D%27Up%27%26%26e.shiftKey)%7Bvar end%3Dsel.stack%5Bsel.stack.length-1%5D%5B1%5Dvar index%3Dend.region.lines.indexOf(end.line)var line%3Dend.region.lines%5Bindex-1%5D%3Bif(line)%7Bend.line%3Dline%3Bend.letter%3Dget_letters(line).sort(function(a,b)%7Breturn Math.abs(a.x0-end.letter.x0)-Math.abs(b.x0-end.letter.x0)%7D)%5B0%5D%7De.preventDefault()%7Delse if(e.keyIdentifier%3D%3D%27Right%27%26%26e.shiftKey)%7Bvar end%3Dsel.stack%5Bsel.stack.length-1%5D%5B1%5Dvar eletters%3Dget_letters(end.line)%3Bvar index%3Deletters.indexOf(end.letter)if(index<eletters.length-1%26%26eletters%5Bindex%2B1%5D)%7Bend.letter%3Deletters%5Bindex%2B1%5D%7De.preventDefault()%7Delse if(e.keyIdentifier%3D%3D%27Left%27%26%26e.shiftKey)%7Bvar end%3Dsel.stack%5Bsel.stack.length-1%5D%5B1%5Dvar eletters%3Dget_letters(end.line)%3Bvar index%3Deletters.indexOf(end.letter)if(index>0%26%26eletters%5Bindex-1%5D)%7Bend.letter%3Deletters%5Bindex-1%5D%7De.preventDefault()%7Delse if(String.fromCharCode(e.keyCode)%3D%3D%27Z%27%26%26(e.ctrlKey%7C%7Ce.metaKey))%7Bsel.stack.pop()%3Be.preventDefault()%7Delse if(String.fromCharCode(e.keyCode)%3D%3D%27A%27%26%26(e.ctrlKey%7C%7Ce.metaKey))%7Bimage.regions.forEach(function(col)%7Bpush_sel(%7Bregion:col%7D)%7D)e.preventDefault()%7Dupdate_selection()%7Dfunction finish_select(e)%7Bif(sel.start)%7Bvar img%3Dsel.img%3Bvar image%3Dim(img)%3Bif(valid_image(e.target))%7Bvar mouse%3Dget_mouse(e)%3Bupdate_pointer(mouse,image)%3B%7Dsel.stack%3Dsel.stack.concat(current_selection(sel,image))sel.start%3Dnull sel.deselect_time%3DDate.now()update_selection()requestAnimationFrame(modify_clipboard)setTimeout(collect_contexts,100)e.preventDefault()e.stopPropagation()e.stopImmediatePropagation()%7D%7Dfunction update_pointer(mouse,image)%7Bvar img%3Dmouse.img,imgX%3Dmouse.X,imgY%3Dmouse.Y%3Bif(image%26%26image.params)%7Bvar params%3Dimage.params%3Bvar sX%3DimgX*params.scale,sY%3DimgY*params.scale%3Bvar linpad%3D3%3Bvar regpad%3D10%3Bvar vreg%3Dvirtualize_region(image,image.regions)%3Bvar in_region%3Dvreg.some(function(region)%7Breturn(sX%2Bregpad>%3Dregion.x0%26%26sX-regpad<%3Dregion.x1%26%26sY%2Bregpad>%3Dregion.y0%26%26sY-regpad<%3Dregion.y1)%3B%7D)%3Bvar has_text%3Dvreg.some(function(region)%7Breturn region.lines.some(function(line)%7Breturn(sX%2Blinpad>%3Dline.x0%26%26sX-linpad<%3Dline.x1%26%26sY%2Blinpad>%3Dline.y0%26%26sY-linpad<%3Dline.y1)%3B%7D)%7D)if(has_text%26%26mouse.inside)%7Bimg.setAttribute(%27naptha_cursor%27,%27text%27)%7Delse if(in_region%26%26mouse.inside)%7Bimg.setAttribute(%27naptha_cursor%27,%27region%27)%7Delse%7Bimg.removeAttribute(%27naptha_cursor%27)%7D%7D%7Dfunction track_mouse(e)%7Bvar mouse%3Dget_mouse(e)var now%3DDate.now()if(now-lastPrediction>100)%7BrequestAnimationFrame(predict_mouse)%3BlastPrediction%3Dnow%3B%7Dif(mouse.img)%7Bvar image%3Dim(mouse.img)%3Bif(!image)return%3Bupdate_pointer(mouse,image)if(sel.start)%7Bsel.end%3Dmouse%3B%7D%7Dif(sel.start)%7Be.preventDefault()e.stopPropagation()e.stopImmediatePropagation()update_selection()%7Dmenu_handle(e)%7Dfunction predict_mouse()%7Bvar minVel%3D1%3Bvar tMax%3D500,tStep%3D100%3Bvar curVel%3DMath.sqrt(meanDX*meanDX%2BmeanDY*meanDY),velX%3DmeanDX/curVel*Math.max(curVel,minVel),velY%3DmeanDY/curVel*Math.max(curVel,minVel)%3Bvar t%3D0%3Bdo%7Bvar offsetX%3DlastClientX%2BvelX*t var offsetY%3DlastClientY%2BvelY*t if(offsetX<0%7C%7CoffsetY<0%7C%7CoffsetX>%3DinnerWidth%7C%7CoffsetY>%3DinnerHeight)break%3Bel%3Ddocument.elementFromPoint(offsetX,offsetY)t%2B%3DtStep%3B%7Dwhile(el%26%26t<tMax%26%26!valid_image(el))%3Bif(!valid_image(el))%7Bif(el)el.removeAttribute(%27naptha_cursor%27)%3Breturn%3B%7Dt-%3DtStep%3Bvar layout%3Dimage_layout(el)%3Bvar relX%3DlastClientX-layout.X,relY%3DlastClientY-layout.Y%3Bvar chunks%3D%5B%5D%3Bvar image%3Dim(el)%3Bvar expires%3DDate.now()-1000*10%3Bdo%7Bvar imgX%3DrelX%2BmeanDX/curVel*Math.max(curVel,minVel)*t,imgY%3DrelY%2BmeanDY/curVel*Math.max(curVel,minVel)*t%3Bvar natX%3DMath.max(0,Math.min(1,imgX/el.width))*el.naturalWidth,natY%3DMath.max(0,Math.min(1,imgY/el.height)*el.naturalHeight)%3Bto_chunks(natY*image.params.scale,image).forEach(function(chunk)%7Bif(chunks.indexOf(chunk)%3D%3D-1%26%26image.chunks.indexOf(chunk)%3D%3D-1)%7Bchunks.push(chunk)%7D%7D)t%2B%3DtStep%3B%7Dwhile(t<tMax%26%26imgX>0%26%26imgY>0%26%26imgX<el.width%26%26imgY<el.height)%3Bif(chunks.length>0)%7Bvar image%3Dim(el)broadcast(%7Btype:%27qchunk%27,id:image.id,time:Date.now(),chunks:chunks%7D)%7D%7Dfunction get_mouse(e)%7BlastClientX%3De.clientX%3BlastClientY%3De.clientY%3Bvar damp%3D0.5,now%3DDate.now(),dt%3Dnow-lastPageT,dx%3D(e.pageX-lastPageX)/dt,dy%3D(e.pageY-lastPageY)/dt%3Bif(dt!%3D0)%7BlastPageX%3De.pageX%3BlastPageY%3De.pageY%3BlastPageT%3Dnow%3BmeanDX%3DmeanDX*damp%2B(1-damp)*dx%3BmeanDY%3DmeanDY*damp%2B(1-damp)*dy%3Bif(!isFinite(meanDX)%7C%7C!isFinite(meanDY))meanDX%3DmeanDY%3D1%3B%7Dif(valid_image(e.target))%7Bvar img%3De.target%3Bvar layout%3Dimage_layout(img)var fracX%3D(e.clientX-layout.X)/img.width,fracY%3D(e.clientY-layout.Y)/img.height%3Breturn%7Bimg:img,sel:sel,inside:(fracX>0%26%26fracY>0%26%26fracX<1%26%26fracY<1),X:fracX*img.naturalWidth,Y:fracY*img.naturalHeight%7D%7Dreturn%7B%7D%7Dfunction point2rect(x,y,rect)%7Bvar ex%3DMath.max(Math.min(x,rect.x1),rect.x0),ey%3DMath.max(Math.min(y,rect.y1),rect.y0)%3Breturn Math.sqrt((x-ex)*(x-ex)%2B(y-ey)*(y-ey))%7Dfunction get_cursor(mouse)%7Bvar img%3Dmouse.img%3Bvar image%3Dimages%5Bget_id(img)%5D%3Bif(!image%7C%7C!image.regions)return%7B%7D%3Bvar params%3Dimage.params,sX%3Dmouse.X*params.scale,sY%3Dmouse.Y*params.scale%3Bfunction search_region(col_sel)%7Bvar line_sel%3Dnull,letter_sel%3Dnull,word_sel%3Dnull,min_dist%3DInfinity%3Bfor(var i%3D0%3Bi<col_sel.lines.length%3Bi%2B%2B)%7Bvar line%3Dcol_sel.lines%5Bi%5D%3Bfor(var j%3D0%3Bj<line.words.length%3Bj%2B%2B)%7Bvar word%3Dline.words%5Bj%5Dfor(var k%3D0%3Bk<word.letters.length%3Bk%2B%2B)%7Bvar letter%3Dword.letters%5Bk%5D,box%3Dletter%3Bif(i%3D%3Dcol_sel.lines.length-1%26%26j%3D%3Dline.words.length-1%26%26k%3D%3Dword.letters.length-1)%7Bbox%3D%7Bx0:letter.x0,y0:letter.y0,x1:col_sel.x1,y1:col_sel.y1%7D%7Dvar box_width%3Dbox.x1-box.x0%3Bvar d%3Dpoint2rect(sX,sY,box)%3Bif(d<min_dist)%7Bmin_dist%3Dd%3Bline_sel%3Dline%3Bword_sel%3Dword%3Bletter_sel%3Dletter%3B%7D%7D%7D%7Dreturn%7Bline:line_sel,letter:letter_sel,word:word_sel,dist:min_dist,region:col_sel%7D%7Dif(image.regions)%7Bvar regions%3Dvirtualize_region(image,image.regions)var region_candidates%3Dregions.map(function(col)%7Bvar col_dist%3Dpoint2rect(sX,sY,col)%3Bif(col_dist>10)return%3Bvar match%3Dsearch_region(col)return match%7D).filter(function(e)%7Breturn e%7D).sort(function(a,b)%7Breturn a.dist-b.dist%7D)%3Bvar result%3Dregion_candidates%5B0%5D%3B%7Dif(result)%7Bif(result.line)%7Bvar widths%3D%5B%5D,wordlen%3D%5B%5D%3Bresult.line.words.forEach(function(word)%7Bwordlen.push(word.letters.length)%3Bword.letters.forEach(function(letter)%7Bwidths.push(letter.x1-letter.x0)%7D)%7D)%3Bfunction S(a,b)%7Breturn a%2Bb%7Dvar meanlen%3Dwordlen.reduce(S)/wordlen.length%3Bvar mean%3Dwidths.reduce(S)/widths.length%3Bvar std%3DMath.sqrt(widths.map(function(e)%7Breturn(e-mean)*(e-mean)%7D).reduce(S)/widths.length)var max%3DMath.max.apply(Math,widths),min%3DMath.min.apply(Math,widths)%3Bif(std>10%26%26meanlen<%3D3%26%26result.region.xheight<40)%7Bresult.letter%3Dresult.word%3B%7D%7D%7Dreturn(result%7C%7C%7B%7D)%7Dfunction layer_clear(img,name)%7Bname%3Dname%7C%7C%27default%27%3Bvar image%3Dim(img)if(!(%27layers%27 in image))return%3Bif(name%3D%3D%27*%27)%7Bfor(var n in image.layers)layer_clear(img,n)%3Breturn%3B%7Dif(!(name in image.layers))return%3Bvar paper%3Dimage.layers%5Bname%5D%3Bif(paper.parentNode)%7Bpaper.parentNode.removeChild(paper)%7Ddelete image.layers%5Bname%5D%7Dfunction get_container()%7Bvar container_id%3D"project_naptha_container" var container%3Ddocument.getElementById(container_id)%3Bif(!container)%7Bcontainer%3Ddocument.createElement(%27div%27)container.id%3Dcontainer_id document.documentElement.appendChild(container)%7Dreturn container%7Dfunction dispose_overlay(img)%7Bvar image%3Dim(img)if(image.overlay%26%26image.overlay.parentNode)%7Bimage.overlay.parentNode.removeChild(image.overlay)%7Dimage.overlay%3Dnull delete image.overlay%3B%7Dfunction update_overlay(img)%7Bvar image%3Dim(img)if(!(%27overlay%27 in image))%7Bimage.overlay%3Ddocument.createElement(%27div%27)get_container().appendChild(image.overlay)%7Dvar overlay%3Dimage.overlay var layout%3Dimage_layout(img)overlay.setAttribute(%27data-imageid%27,image.id)overlay.style.position%3D%27absolute%27 overlay.style.userSelect%3D%27none%27 overlay.style.pointerEvents%3D%27none%27 overlay.style.margin%3D%270%27 overlay.style.border%3D%270%27 overlay.style.padding%3D%270%27 overlay.style.opacity%3D%271%27 overlay.style.left%3Dlayout.left%2B%27px%27 overlay.style.top%3Dlayout.top%2B%27px%27%7Dfunction layer(img,name,x0,y0)%7Bname%3Dname%7C%7C%27default%27%3Bx0%3Dx0%7C%7C0%3By0%3Dy0%7C%7C0%3Bvar image%3Dim(img)if(!(%27layers%27 in image))image.layers%3D%7B%7D%3Bif(!(name in image.layers))%7Bvar paper%3Dimage.layers%5Bname%5D%3Ddocument.createElement(%27canvas%27)%3Bupdate_overlay(img)%3Bimage.overlay.appendChild(paper)%7Dvar paper%3Dimage.layers%5Bname%5D%3Bpaper.setAttribute(%27data-layername%27,"project_naptha_layer_"%2Bname)paper.style.userSelect%3D%27none%27 paper.style.pointerEvents%3D%27none%27 paper.style.left%3Dx0%2B%27px%27 paper.style.top%3Dy0%2B%27px%27 init_layer(paper,name)return paper%7Dfunction depth(name)%7Bvar ordering%3D%5B%27plaster%27,%27translate%27,%27debug%27,%27highlight%27,%27pun%27,%27shimmer%27,%27error%27,%27overlay%27,%27menu%27%5D%3Bif(ordering.indexOf(name)%3D%3D-1)%7Bconsole.warn("Error: ",name,"not found in ordering schema")%7Dreturn 2147483645-ordering.length%2Bordering.indexOf(name)%3B%7Dfunction init_layer(el,name)%7Bel.style.zIndex%3Ddepth(name)%3Bel.style.position%3D%27absolute%27 el.style.margin%3D%270%27 el.style.border%3D%270%27 el.style.padding%3D%270%27 el.style.boxShadow%3D%27none%27%7Dfunction error_message(image,region,text)%7Bvar div%3Ddocument.createElement(%27div%27)if(text)%7Bdiv.innerHTML%3D"<b>Error</b> " div.appendChild(document.createTextNode(text.replace(/%5E%5Cs*Error:%3F%5Csi,%27%27)))%7Delse%7Bdiv.innerHTML%3D"<b>Error</b> something went wrong"%7Ddiv.innerHTML%2B%3D%27 <button type%3D"button" class%3D"close" data-dismiss%3D"alert" onclick%3D"this.parentNode.parentNode.removeChild(this.parentNode)">%26times%3B</button>%27 div.className%3D"alert-danger" div.style.zIndex%3Ddepth(%27error%27)update_overlay(image.el)var xpad%3D10,ypad%3D10%3Bvar sx%3D(image.el.width/image.el.naturalWidth/image.params.scale),sy%3D(image.el.height/image.el.naturalHeight/image.params.scale)%3Bvar layout%3Dimage_layout(image.el)div.style.position%3D"absolute" div.style.left%3D(layout.left%2Bsx*region.x0%2Bxpad)%2B%27px%27 div.style.top%3D(layout.top%2Bsy*region.y0%2Bypad)%2B%27px%27 div.style.width%3DMath.max(250,(sx*region.width-(35%2B14%2B1%2B1)-xpad*2))%2B%27px%27 get_container().appendChild(div)%7Dfunction update_translations(image)%7Bif(!image.translate)image.translate%3D%7B%7D%3Bimage.regions.forEach(function(region)%7Bif(!(region.id in image.translate))return%3Bvar translate%3Dimage.translate%5Bregion.id%5D%3Bfunction finish_translation()%7Btranslate.processing%3Dfalse translate.waiting%3Dfalse%3Btranslate.finished%3DDate.now()translate_region(image,region)draw_overlays(image)update_selection()%7Dif(!translate.language%7C%7Ctranslate.language%3D%3D%27erase%27)%7Bfinish_translation()%7Delse if(image.ocr%26%26translate.waiting%26%26region.id in image.ocr)%7Bvar ocr%3Dimage.ocr%5Bregion.id%5Dif(ocr.finished)%7Bvar text%3Dextract_region(ocr,%7Bregion:region%7D).join(%27%5Cn%27).trim().replace(/%5Cn/g,%27 %27).replace(/-%2B/g,%27%27).trim()%3Btranslate.waiting%3Dfalse%3Btranslate.processing%3DDate.now()if(translate.language%3D%3D%27esrever%27)%7Btranslate.text%3Dtext.split(%27%27).reverse().join(%27%27)finish_translation()%7Delse if(translate.language%3D%3D%27echo%27)%7Btranslate.text%3Dtext%3Bfinish_translation()%7Delse if(translate.language%3D%3D%27pig%27)%7Btranslate.text%3Dtext.replace(/%5Ba-z%5D%2B/ig,function(e)%7Bif(e.length<3)return e%3Bif(!/%5E%5Ba-z%5D*%24/i.test(e))return e%3Bvar proc%3D/%5Baeiou%5D/i.test(e%5B0%5D)%3F(e%2B%27way%27):(e.slice(1)%2Be%5B0%5D.toLowerCase()%2B%27ay%27)%3Bif(e%5B0%5D%3D%3De%5B0%5D.toUpperCase())proc%3Dproc%5B0%5D.toUpperCase()%2Bproc.slice(1)%3Bif(e%3D%3De.toUpperCase())proc%3Dproc.toUpperCase()%3Breturn proc%7D)finish_translation()%7Delse%7Bvar xhr%3Dnew XMLHttpRequest()%3Bxhr.open(%27POST%27,global_params.apiroot%2B%27translate%27,true)var formData%3Dnew FormData()%3Bvar lang%3Dtranslate.language%3BformData.append(%27target%27,lang)formData.append(%27url%27,image.src)formData.append(%27text%27,text)formData.append(%27user%27,global_params.user_id)xhr.send(formData)xhr.onerror%3Dfunction()%7Berror_message(image,region,"The translation server could not be reached")delete image.translate%5Bregion.id%5D%7Dxhr.onload%3Dfunction()%7Btry%7Bvar json%3DJSON.parse(xhr.responseText)%3Btranslate.text%3Djson.text finish_translation()%7Dcatch(err)%7Berror_message(image,region,xhr.responseText)delete image.translate%5Bregion.id%5D%7Dif(!((image.lookup%7C%7C%7B%7D).translations%7C%7C%5B%5D).some(function(chunk)%7Breturn chunk.target%3D%3Dlang%7D))%7B((image.lookup%7C%7C%7B%7D).translations%7C%7C%5B%5D).push(%7Btarget:lang%7D)%7D%7D%7D%7D%7Dif(!(region.id in image.plaster)%26%26translate.language)%7Bqueue_broadcast(%7Bsrc:image.src,type:%27qpaint%27,id:image.id,reg_id:region.id,region:region,swtscale:image.params.scale,swtwidth:image.width%7D)image.plaster%5Bregion.id%5D%3D%7Bprocessing:Date.now()%7Dregion.shimmer%3DDate.now()%3B%7D%7D)%7Dfunction draw_overlays(image)%7BsetTimeout(function()%7Bimage.regions.forEach(function(region)%7Bvar elapsed%3Dget_elapsed(image,region)var plaster%3Dimage.plaster%5Bregion.id%5Dvar translate%3Dimage.translate%5Bregion.id%5D%7C%7C%7B%7D%3Bif(elapsed>0)%7Bif(translate.language)%7Bif(plaster%26%26plaster.mask)plaster.mask.style.opacity%3D1%3Bif(translate.language!%3D%27erase%27)%7Bif(translate%26%26translate.paper)translate.paper.style.opacity%3D1%3B%7Delse%7Bif(translate%26%26translate.paper)translate.paper.style.opacity%3D0%3B%7D%7Delse%7Bif(plaster%26%26plaster.mask)plaster.mask.style.opacity%3D0%3Bif(translate%26%26translate.paper)translate.paper.style.opacity%3D0%3B%7D%7Delse%7Bif(plaster%26%26plaster.mask)plaster.mask.style.opacity%3D0%3Bif(translate%26%26translate.paper)translate.paper.style.opacity%3D0%3B%7D%7D)%7D,0)%7Dfunction translate_region(image,region)%7Bif(!image.ocr)return%3Bif(!image.plaster)image.plaster%3D%7B%7D%3Bif(!image.virtual)image.virtual%3D%7B%7D%3Bif(!image.translate)image.translate%3D%7B%7D%3Bif(!(region.id in image.translate))return%3Bvar translate%3Dimage.translate%5Bregion.id%5D%3Bif(!translate.paper)%7Btranslate.paper%3Ddocument.createElement(%27canvas%27)update_overlay(image.el)image.overlay.appendChild(translate.paper)translate.paper.style.transition%3D%27opacity 1s%27 translate.paper.style.opacity%3D%270%27%7Dvar img%3Dimage.el var paper%3Dtranslate.paper%3Binit_layer(paper,%27translate%27)paper.style.left%3D%270px%27 paper.style.top%3D%270px%27 paper.setAttribute(%27data-layername%27,"project_naptha_layer_translate")paper.width%3Dimg.naturalWidth paper.height%3Dimg.naturalHeight paper.style.width%3Dimg.width%2B%27px%27 paper.style.height%3Dimg.height%2B%27px%27 var ctx%3Dpaper.getContext(%272d%27)if(!translate.language)return%3Bif(translate.waiting%7C%7Ctranslate.processing)return%3Bif(!(region.id in image.ocr))return%3Bvar ocr%3Dimage.ocr%5Bregion.id%5D%3Bif(ocr.processing)return%3Bif(translate.language%3D%3D%27erase%27)return%3Bvar plaster%3Dimage.plaster%5Bregion.id%5D%3Bif(!plaster%7C%7Cplaster.processing)return%3Bfunction wrap(children,extend)%7Bextend%3Dextend%7C%7C%7B%7D%3Bextend.x0%3DInfinity%3Bextend.y0%3DInfinity%3Bextend.x1%3D0%3Bextend.y1%3D0%3Bchildren.forEach(function(child)%7Bextend.x0%3DMath.min(extend.x0,child.x0)%3Bextend.y0%3DMath.min(extend.y0,child.y0)%3Bextend.x1%3DMath.max(extend.x1,child.x1)%3Bextend.y1%3DMath.max(extend.y1,child.y1)%3B%7D)extend.cx%3Dextend.x1/2%2Bextend.x0/2%3Bextend.cy%3Dextend.y1/2%2Bextend.y0/2%3Breturn extend%3B%7Dfunction get_letters(region)%7Breturn%5B%5D.concat.apply(%5B%5D,region.lines.map(function letters_from_line(line)%7Breturn%5B%5D.concat.apply(%5B%5D,line.words.map(function(e)%7Breturn e.letters%7D))%7D))%7Dfunction html_decode_entities(text)%7Bvar x%3Ddocument.createElement(%27textarea%27)x.innerHTML%3Dtext%3Breturn x.value%7Dvar text%3Dhtml_decode_entities(translate.text)%3Bfunction align_mad(prop)%7Bvar met%3Dregion.lines.map(function(line)%7Breturn line%5Bprop%5D%7D)var mean%3Dmet.reduce(function(a,b)%7Breturn a%2Bb%7D)/region.lines.length%3Bvar mad%3Dmet .map(function(line_dim)%7Breturn Math.abs(line_dim-mean)/region.width%7D).sort(function(a,b)%7Breturn a-b%7D)%5BMath.floor(region.lines.length/2)%5Dreturn mad%7Dvar align%3D%27left%27 if((align_mad(%27cx%27)<align_mad(%27x0%27)%26%26align_mad(%27cx%27)<align_mad(%27x1%27))%7C%7Cregion.lines.length%3D%3D1)%7Balign%3D%27center%27%7Delse if(align_mad(%27x0%27)<align_mad(%27x1%27)%26%26align_mad(%27x0%27)<align_mad(%27cx%27))%7Balign%3D%27left%27%7Delse if(align_mad(%27x1%27)<align_mad(%27x0%27)%26%26align_mad(%27x1%27)<align_mad(%27cx%27))%7Balign%3D%27right%27%7Dvar letters%3Dget_letters(region)%3Bvar frac_up%3Dletters.filter(function(e)%7Breturn e.height/region.xheight>1.2%7D).length/letters.length%3Bif(frac_up<0.05)%7Bvar font_size%3DMath.round(1.0*region.xheight)%3Btext%3Dtext.toUpperCase()%7Delse%7Bvar font_size%3DMath.round(1.7*(region.xheight/image.params.scale)%2B0.1124)%3B%7Dctx.shadowBlur%3D0%3Bvar font_weight%3D400,font_name%3D%27%27%3Bif(frac_up<0.05%26%26region.lettersize/region.xheight>0.4)%7Bvar rgb%3Dplaster.colors%5B0%5D%5B1%5Dvar luma%3D0.299*rgb%5B0%5D%2B0.587*rgb%5B1%5D%2B0.114*rgb%5B2%5D%3Bif(luma>200)%7Bctx.shadowColor%3D"%23000" ctx.shadowBlur%3D5%3B%7Dif((region.lettersize/region.xheight)>0.7)%7Bfont_name%3D%27Impact%27%7Delse%7Bfont_name%3D%27xkcd%27%7D%7Delse%7Bvar font_weight_th%3D(38.85*region.thickness-33.65)/font_size var font_weight_ls%3D(13.97*region.lettersize-64.69)/font_size font_weight%3DMath.min(900,Math.max(100,Math.round(font_weight_th)*100))font_name%3D%27"Helvetica Neue"%27%7Dfunction measure(line)%7Breturn ctx.measureText(line).width%7Dwhile(true)%7Bctx.font%3Dfont_weight%2B%27 %27%2Bfont_size%2B%27px %27%2Bfont_name%3Bvar words%3DbreakWords(text.split(%27 %27),measure,region.width/image.params.scale)%3Bif(region.lines.length%3D%3D1)%7Bvar textlines%3D%5Bwords.join(%27 %27)%5D%7Delse%7Bvar textlines%3DbreakLineGreedy(words,measure,region.width/image.params.scale)%7Dvar min_height%3Dtextlines.length*font_size*image.params.scale%3Bif(min_height>region.height%26%26textlines.length>1)%7Bfont_size--continue%3B%7Dvar max_width%3DMath.max.apply(Math,textlines.map(measure))if(max_width>region.width/image.params.scale)%7Bfont_size--continue%3B%7Dbreak%3B%7Dconsole.log(textlines.join(%27%5Cn%27))ctx.save()var ox%3Dregion.cx/image.params.scale,oy%3Dregion.cy/image.params.scale%3Bctx.translate(ox,oy)ctx.rotate(region.angle)var lines%3Dtextlines.map(function(line,index)%7Bif(!line)return%3Bif(plaster.colors%26%26plaster.colors%5B0%5D%26%26plaster.colors%5B0%5D%5B1%5D)%7Bvar rgb%3Dplaster.colors%5B0%5D%5B1%5Dctx.fillStyle%3D%27rgb(%27%2Brgb.map(Math.round).join(%27,%27)%2B%27)%27%7Dvar vpad%3D5%3Bvar region_height%3D(region.lines%5Bregion.lines.length-1%5D.cy%2Bregion.lines%5Bregion.lines.length-1%5D.lineheight/2)-(region.lines%5B0%5D.cy-region.lines%5B0%5D.lineheight/2)var cy%3D(region.cy-region_height/2-vpad%2B(region_height%2Bvpad*2)*((0.5%2Bindex)/textlines.length))%3Bctx.textBaseline%3D%27middle%27%3Bif(align%3D%3D%27center%27)%7Bctx.textAlign%3D%27center%27 ctx.fillText(line,region.cx/image.params.scale-ox,cy/image.params.scale-oy)%7Delse if(align%3D%3D%27left%27)%7Bctx.textAlign%3D%27left%27 ctx.fillText(line,region.x0/image.params.scale-ox,cy/image.params.scale-oy)%7Delse if(align%3D%3D%27right%27)%7Bctx.textAlign%3D%27right%27 ctx.fillText(line,region.x1/image.params.scale-ox,cy/image.params.scale-oy)%7Dvar letters%3D%5B%5D%3Bvar h%3Dfont_size*image.params.scale%3Bfor(var i%3D0%3Bi<line.length%3Bi%2B%2B)%7Bvar w%3Dmeasure(line%5Bi%5D)*image.params.scale%3Bif(align%3D%3D%27center%27)%7Bvar x0%3Dregion.x0%2B(region.width-measure(line)*image.params.scale)/2%2Bmeasure(line.slice(0,i))*image.params.scale%7Delse if(align%3D%3D%27left%27)%7Bvar x0%3Dregion.x0%2Bmeasure(line.slice(0,i))*image.params.scale%7Delse if(align%3D%3D%27right%27)%7Bvar x0%3Dregion.x1-measure(line.slice(i))*image.params.scale%7Dletters.push(%7Bx0:x0,x1:x0%2Bw,y0:cy-h/2,y1:cy%2Bh/2,_:line%5Bi%5D%7D)%7Dvar words%3D%5B%5D,buf%3D%5B%5D%3Bfor(var i%3D0%3Bi<letters.length%3Bi%2B%2B)%7Bif(letters%5Bi%5D._%3D%3D%27 %27)%7Bwords.push(buf)buf%3D%5B%5D%7Delse buf.push(letters%5Bi%5D)%3B%7Dwords.push(buf)return wrap(letters,%7Blineheight:h,id:%27VLINE_%27%2Bindex,direction:region.direction,words:words.filter(function(e)%7Breturn e.length%7D).map(function(letters)%7Breturn wrap(letters,%7Bletters:letters%7D)%7D)%7D)%7D)%3Bctx.restore()image.virtual%5Bregion.id%5D%3Dwrap(lines,%7Bid:region.id,lines:lines,angle:region.angle,finished:true,virtual:true,direction:region.direction%7D)%7Dfunction breakWord(word,measure,L)%7Bvar i%3D0,j%3D0,words%3D%5B%5D%3Bwhile(j<word.length)%7Bvar j%3Di%2B1%3Bwhile(j<%3Dword.length%26%26measure(word.slice(i,j%2B1))<L)%7Bj%2B%2B%3B%7Dwords.push(word.slice(i,j))i%3Dj%3B%7Dreturn words%3B%7Dfunction breakWords(words,measure,L)%7Breturn%5B%5D.concat.apply(%5B%5D,words.map(function(word)%7Breturn breakWord(word,measure,L)%7D))%3B%7Dfunction breakLineGreedy(words,measure,L)%7Bvar i%3D0,j%3D0,lines%3D%5B%5D%3Bwhile(j<words.length)%7Bvar j%3Di%2B1%3Bwhile(j<%3Dwords.length%26%26measure(words.slice(i,j%2B1).join(%27 %27))<L)%7Bj%2B%2B%3B%7Dlines.push(words.slice(i,j).join(%27 %27))i%3Dj%3B%7Dreturn lines%3B%7Dfunction breakLineDP(words,measure,L)%7Bvar wl%3Dwords.map(measure),space%3Dmeasure(%27m%27)%3Bvar n%3Dwl.length%3Bvar m%3D%7B%270%27:0%7D%3Bvar s%3D%7B%7D%3Bfunction length(wordLengths,i,j)%7Bvar arr%3DwordLengths.slice(i-1,j)var sum%3Darr.length%3D%3D0%3F0:arr.reduce(function(a,b)%7Breturn a%2Bb%7D)%3Breturn sum%2Bj-i%2Bspace%7Dfor(var i%3D1%3Bi<n%2B1%3Bi%2B%2B)%7Bvar sums%3D%7B%7D%3Bvar k%3Di%3Bwhile(length(wl,k,i)<%3DL%26%26k>0)%7Bsums%5BMath.pow(L-length(wl,k,i),3)%2Bm%5Bk-1%5D%5D%3Dk k--%3B%7Dm%5Bi%5D%3DMath.min.apply(Math,Object.keys(sums))s%5Bi%5D%3Dsums%5Bm%5Bi%5D%5D%7Dvar line%3D1,lines%3D%5B%5D%3Bwhile(n>0)%7Blines.unshift(words.slice(s%5Bn%5D-1,n).join(%27 %27))n%3Ds%5Bn%5D-1 line%2B%2B%7Dreturn lines%7Dfunction draw_annotations(img,image)%7Bvar params%3Dsession_params%3Bif(!((params.show_contours%7C%7Cparams.show_letters%7C%7Cparams.show_lines%7C%7Cparams.show_regions%7C%7Cparams.show_chunks%7C%7Cparams.show_stitching)%26%26image))%7Blayer_clear(img,%27debug%27)return%3B%7Dvar layout%3Dimage_layout(img)%3Bvar paper%3Dlayer(img,%27debug%27,0,0)paper.width%3Dimage.width paper.height%3Dimage.height paper.style.width%3Dimg.width%2B%27px%27 paper.style.height%3Dimg.height%2B%27px%27 var ctx%3Dpaper.getContext(%272d%27)var num_chunks%3DMath.max(1,Math.ceil((image.height-image.params.chunk_overlap)/(image.params.chunk_size-image.params.chunk_overlap)))image.chunks.forEach(function(chunk)%7Bvar offset%3Dchunk*(image.params.chunk_size-image.params.chunk_overlap)%3Bvar chunk_color%3D(chunk%253>0)%3F((chunk%253>1)%3F%27red%27:%27green%27):"blue"%3Bif(params.show_chunks)%7Bctx.lineWidth%3D3%3Bctx.fillStyle%3Dctx.strokeStyle%3Dchunk_color if(ctx.setLineDash)ctx.setLineDash(%5B4,4%5D)%3Bctx.strokeRect(10*(chunk%252)%2B0.5%2Bctx.lineWidth,offset%2Bctx.lineWidth%2B0.5,image.width-2*10*(chunk%252)-2*ctx.lineWidth,image.params.chunk_size)ctx.fillText(%27Chunk %27%2Bchunk,20,offset%2B20)%7D%7D)if(params.show_stitching%26%26image.stitch_debug)%7Bimage.stitch_debug.forEach(function(line)%7Bif(ctx.setLineDash)ctx.setLineDash(%5B%5D)%3Bctx.lineWidth%3D1 if(line.type%3D%3D%27orphan%27)%7Bctx.strokeStyle%3D%27orange%27%7Delse if(line.type%3D%3D%27valid%27)%7Bctx.strokeStyle%3D%27green%27%7Dctx.strokeRect(line.x0%2B.5,line.y0%2B.5,line.width,line.height)%7D)%7Dimage.regions.forEach(function(col)%7Bif(params.show_regions)%7Bif(col.lines%5B0%5D.direction%3D%3D-1)%7Bctx.fillStyle%3D%27rgba(255,255,0,%27%2B(col.finished%3F0.3:0.1)%2B%27)%27 ctx.strokeStyle%3D%27black%27%7Delse%7Bctx.fillStyle%3D%27rgba(0,255,255,%27%2B(col.finished%3F0.3:0.1)%2B%27)%27 ctx.strokeStyle%3D%27white%27%7Dctx.lineWidth%3D2 if(col.finished)%7Bctx.lineWidth%3D3 if(ctx.setLineDash)ctx.setLineDash(%5B%5D)%3B%7Delse%7Bctx.lineWidth%3D3 if(ctx.setLineDash)ctx.setLineDash(%5B10,10%5D)%3B%7Dctx.save()%3Bctx.translate(col.cx,col.cy)ctx.rotate(col.angle)ctx.strokeRect(-col.width/2,-col.height/2,col.width,col.height)ctx.restore()%7Dctx.lineWidth%3D1 var strokecolors%3D%5B%27%23FF69B4%27,%27red%27,%27green%27,%27blue%27,%27purple%27%5Dcol.lines.forEach(function(line,index)%7Bvar chunk%3Dline.chunk%3Bvar chunk_color%3D(chunk%253>0)%3F((chunk%253>1)%3F%27red%27:%27green%27):"blue"%3Bctx.strokeStyle%3Dstrokecolors%5Bindex%25strokecolors.length%5Dctx.lineWidth%3D1 line.words.forEach(function(word)%7Bword.letters.forEach(function(letter)%7Bif(params.show_letters)%7Bif(ctx.setLineDash)ctx.setLineDash(%5B%5D)%3Bctx.strokeRect(letter.x0,letter.y0,letter.width,letter.height)%3B%7Dif(params.show_contours%26%26letter.shape)%7Bletter.shape.forEach(function(c)%7Bif(line.direction%3D%3D-1)%7Bctx.fillStyle%3D%27%23FF69B4%27%7Delse%7Bctx.fillStyle%3D%27yellow%27%7Dctx.fillRect(letter.x0%2B(c%25letter.width),letter.y0%2BMath.floor(c/letter.width),1,1)%7D)%7D%7D)if(params.show_words)%7Bctx.strokeRect(word.x0,word.y0,word.width,word.height)%7D%7D)if(params.show_lines)%7Bctx.beginPath()if(ctx.setLineDash)ctx.setLineDash(%5B%5D)%3Bctx.strokeStyle%3D%27purple%27 ctx.lineWidth%3D1%3Bif(ctx.setLineDash)ctx.setLineDash(%5B%5D)%3Bctx.strokeStyle%3Dstrokecolors%5Bindex%25strokecolors.length%5Dif(line.direction%3D%3D-1)%7Bctx.fillStyle%3D%27rgba(255,255,0,0.3)%27%7Delse%7Bctx.fillStyle%3D%27rgba(0,255,255,0.3)%27%7Dctx.save()%3Bctx.translate(line.cx,line.cy)ctx.rotate(line.angle)ctx.fillRect(-line.width/2,-line.lineheight/2,line.width,line.lineheight)ctx.restore()ctx.beginPath()ctx.lineWidth%3D3 line.words.forEach(function(word)%7Bword.letters.forEach(function(letter)%7Bctx.lineTo(letter.cx,letter.cy)%7D)%7D)ctx.stroke()%7D%7D)%7D)%7Dvar is_shimmering%3Dfalse%3Bfunction start_shimmer()%7Bif(!is_shimmering)render_shimmer()%3Breturn is_shimmering%7Dfunction get_elapsed(image,region)%7Bif(!image.ocr)image.ocr%3D%7B%7D%3Bif(!image.plaster)image.plaster%3D%7B%7D%3Bif(!image.translate)image.translate%3D%7B%7D%3Bvar elapsed%3D%5B%5Dif(region.id in image.ocr)%7Bif(image.ocr%5Bregion.id%5D.processing)%7Belapsed.push(-1)%7Delse%7Belapsed.push(Date.now()-image.ocr%5Bregion.id%5D.finished)%7D%7Dif(region.id in image.plaster)%7Bif(image.plaster%5Bregion.id%5D.processing)%7Belapsed.push(-1)%7Delse%7Belapsed.push(Date.now()-image.plaster%5Bregion.id%5D.finished)%7D%7Dif(region.id in image.translate%26%26image.translate%5Bregion.id%5D.language)%7Bif(image.translate%5Bregion.id%5D.waiting)%7Belapsed.push(-2)%7Delse if(image.translate%5Bregion.id%5D.processing)%7Belapsed.push(-1)%7Delse%7Belapsed.push(Date.now()-image.translate%5Bregion.id%5D.finished)%7D%7Dreturn Math.min.apply(Math,elapsed)%7Dfunction render_shimmer()%7Bis_shimmering%3Dfalse if(typeof sel%3D%3D%27undefined%27%7C%7C!sel%7C%7C!sel.img)return%3Bvar img%3Dsel.img var image%3Dim(img)%3Bvar cols%3Dimage.regions.filter(function(region)%7Bif(!region.shimmer)return false%3Bvar elapsed%3Dget_elapsed(image,region)if(elapsed>1000)return false%3Breturn true%3B%7D)%3Bif(cols.length%3D%3D0)%7Blayer_clear(img,%27shimmer%27)return%3B%7Dvar x0%3DInfinity,y0%3DInfinity,x1%3D0,y1%3D0%3Bcols.forEach(function(col)%7Bx0%3DMath.min(x0,col.x0)%3By0%3DMath.min(y0,col.y0)x1%3DMath.max(x1,col.x1)%3By1%3DMath.max(y1,col.y1)%7D)%3Bvar sx%3D(img.width/img.naturalWidth)/image.params.scale,sy%3D(img.height/img.naturalHeight)/image.params.scale%3Bvar paper%3Dlayer(img,%27shimmer%27,x0*sx,y0*sy)paper.width%3DMath.round((x1-x0%2B1)*sx)paper.height%3DMath.round((y1-y0%2B1)*sy)var ctx%3Dpaper.getContext(%272d%27)var dat%3Dctx.createImageData(paper.width,paper.height)cols.forEach(function(col)%7Bvar elapsed%3Dget_elapsed(image,col)var time%3D(Date.now()-col.shimmer)%3Bcol.lines.forEach(function(line)%7Bvar offset%3D(image.params.chunk_size-image.params.chunk_overlap)*line.chunk var seed%3Dline.y0%3Bvar seed2%3Dline.y1%3Bfor(var i%3D0%3Bi<17%3Bi%2B%2B)%7Bseed%3D((1664525*seed%2B1013904223)B94967296)%3Bseed2%3D((1664525*seed2%2B1013904223)B94967296)%3B%7Dseed/%3D4294967296%3Bseed2/%3D4294967296%3Bline.words.forEach(function(word)%7Bword.letters.forEach(function(letter)%7Bif(!letter.shape)return%3Bfor(var i%3D0%3Bi<letter.shape.length%3Bi%2B%2B)%7Bvar c%3Dletter.shape%5Bi%5D%3Bvar x%3Dletter.x0%2B(c%25letter.width),y%3D(letter.y0%2BMath.floor(c/letter.width))%3Bvar frac%3D2*seed%2Btime/(2000*(0.5%2Bseed2))-x/Math.min(800,image.width)%3Bvar interp%3DMath.pow(0.5-Math.cos(frac*Math.PI*2)/2,8)%3Bvar opacity%3DMath.max(0,interp-0.5)*Math.pow(Math.min(1,time/500),2)if(elapsed<1000)opacity*%3DMath.max(0,1-elapsed/300)%3Bopacity*%3D(line.direction>0%3F1:0.8)%3Bif(opacity>0)%7Bvar p%3D(dat.width*Math.round((y-y0)*sy)%2BMath.round((x-x0)*sx))*4%3Bdat.data%5Bp%5D%3Ddat.data%5Bp%2B1%5D%3Ddat.data%5Bp%2B2%5D%3D255%3Bdat.data%5Bp%2B3%5D%3DMath.floor(256*opacity)%7D%7D%7D)%7D)%7D)%7D)ctx.putImageData(dat,0,0)requestAnimationFrame(render_shimmer)is_shimmering%3Dtrue%7Dvar menu_levels%3D%5B%5D%3Bvar lastX,lastY%3Bvar menu_overlay%3Bvar typeCompletionTimeout%3Bvar typeBuffer%3D%27%27%3Bfunction menu_typedone()%7BclearTimeout(typeCompletionTimeout)%3BtypeBuffer%3D%27%27 var menu%3Dmenu_levels%5Bmenu_levels.length-1%5D%3Bif(menu%26%26menu.selectedIndex>0)%7B%5B%5D.forEach.call(menu.children,function(el)%7Bif(el.original)menu.replaceChild(el.original,el)%3B%7D)menu.change_selection(menu.selectedIndex,true)%7D%7Dfunction menu_keyhandle(e)%7Bif(e.shiftKey)return%3Bfunction invalid_el(el)%7Breturn el.tagName%3D%3D"HR"%7C%7Cel.has("disabled")%7Dif(e.keyIdentifier%3D%3D%27Down%27)%7Bvar menu%3Dmenu_levels%5Bmenu_levels.length-1%5D%3Bvar index%3Dmenu.selectedIndex%2B1%3Bvar cur_sel%3Bwhile(invalid_el(cur_sel%3Dmenu.children%5B(index%2Bmenu.children.length)%25menu.children.length%5D))%7Bindex%2B%2B%3B%7Dmenu.change_selection(cur_sel,true)e.preventDefault()%7Delse if(e.keyIdentifier%3D%3D%27Up%27)%7Bvar menu%3Dmenu_levels%5Bmenu_levels.length-1%5D%3Bvar index%3Dmenu.selectedIndex-1%3Bvar cur_sel%3Bwhile(invalid_el(cur_sel%3Dmenu.children%5B(index%2Bmenu.children.length)%25menu.children.length%5D))%7Bindex--%3B%7Dmenu.change_selection(cur_sel,true)e.preventDefault()%7Delse if(e.keyIdentifier%3D%3D%27Right%27)%7Bmenu_typedone()var menu%3Dmenu_levels%5Bmenu_levels.length-1%5D%3Bif(menu)%7Bvar index%3Dmenu.selectedIndex%3Bif(index%3D%3D-1)%7Bmenu.change_selection(0)%7Delse%7Bvar cur_sel%3Dmenu.children%5B(index%2Bmenu.children.length)%25menu.children.length%5Dmenu.change_selection(cur_sel)if(menu_levels%5Bmenu_levels.length-1%5D!%3Dmenu)%7Bmenu_levels%5Bmenu_levels.length-1%5D.change_selection(0,true)%7D%7D%7De.preventDefault()%7Delse if(e.keyIdentifier%3D%3D%27Left%27)%7Bif(menu_levels.length)%7Bcreate_menu(%5B%5D,menu_levels.length-1)e.preventDefault()%7D%7Delse if(e.keyIdentifier%3D%3D%27Enter%27%7C%7Ce.keyIdentifier%3D%3D%27U%2B0020%27)%7Bmenu_typedone()var menu%3Dmenu_levels%5Bmenu_levels.length-1%5D%3Bmenu.do_action(menu.selectedIndex)e.preventDefault()%7Delse if(e.keyIdentifier%3D%3D%27Esc%27%7C%7Ce.keyIdentifier%3D%3D%27U%2B001B%27)%7Bcreate_menu(%5B%5D,0)e.preventDefault()%7Dvar keycode%3De.keyCode%3Bvar valid%3D(keycode>64%26%26keycode<91)%3Bvar menu%3Dmenu_levels%5Bmenu_levels.length-1%5D%3Bif(valid%26%26menu)%7Bvar letter%3DString.fromCharCode(e.keyCode)%3BclearTimeout(typeCompletionTimeout)%3BtypeCompletionTimeout%3DsetTimeout(menu_typedone,500)%3BtypeBuffer%2B%3Dletter%3Bvar re%3Dnew RegExp(typeBuffer.split(%27%27).join(%27.*%3F%27),%27ig%27)%3Bvar has_match%3Dfalse%3B%5B%5D.forEach.call(menu.children,function(el)%7Bvar orig%3Del.original%7C%7Cel%3Borig.removeAttribute(%27selected%27)%3Bvar clone%3Dorig.cloneNode(true)%3Bclone.original%3Dorig%3Bvar temp%3Ddocument.createElement(%27div%27)%3Bvar text%3D%5B%5D.map.call(clone.childNodes,function(node)%7Breturn node.nodeType%3D%3D3%3Fnode.data:%27%27%7D).join(%27%27)re.lastIndex%3D0%3Bif(text.match(re))%7Bhas_match%3Dtrue%3Bclone.is_match%3Dtrue%3B%7D%3B%5B%5D.forEach.call(clone.childNodes,function(node)%7Bif(node.nodeType!%3D3)return%3Bre.lastIndex%3D0%3Btemp.innerHTML%3Dnode.data.replace(re,%27<u>__489__</u>%27)%3Bwhile(temp.firstChild)%7Bnode.parentNode.insertBefore(temp.firstChild,node)%7Dnode.parentNode.removeChild(node)%7D)menu.replaceChild(clone,el)%3B%7D)%3Bvar index%3Dmenu.selectedIndex%3Bif(has_match)%7Bvar cur_sel%3Bwhile(!(cur_sel%3Dmenu.children%5B(index%2Bmenu.children.length)%25menu.children.length%5D).is_match)%7Bindex%2B%2B%3B%7Dmenu.change_selection(cur_sel,true)%7Delse%7Bmenu.change_selection(index,true)%7D%7Delse%7Bmenu_typedone()%7D%7Dfunction menu_handle(e)%7Bif(is_child(e.target,menu_levels%5Bmenu_levels.length-1%5D))%7Bvar menu%3Dmenu_levels%5Bmenu_levels.length-1%5D%3Bmenu.change_selection(e.target)%7Delse if(is_child(e.target,menu_levels%5Bmenu_levels.length-2%5D)%7C%7Ce.target%3D%3Dmenu_levels%5Bmenu_levels.length-2%5D)%7Bvar child_rect%3Dmenu_levels%5Bmenu_levels.length-1%5D.getBoundingClientRect()%3Bfunction side(px,py,x1,y1,x2,y2)%7Breturn(y2-y1)*(px-x1)%2B(x1-x2)*(py-y1)%3B%7Dfunction PointInSector(px,py,x1,y1,x2,y2,x3,y3)%7Bvar cp1%3Dside(px,py,x1,y1,x2,y2)>0,cp3%3Dside(px,py,x3,y3,x1,y1)>0%3Breturn cp1%3D%3Dcp3%7Dvar dx%3D((child_rect.left/2%2Bchild_rect.right/2)-lastX)%3Bvar far_edge%3Ddx>0%3Fchild_rect.right:child_rect.left,close_edge%3Ddx>0%3Fchild_rect.left:child_rect.right%3Bif((e.clientX-lastX)/dx>0%26%26PointInSector(e.clientX,e.clientY,lastX,lastY,lastY>child_rect.bottom%3Ffar_edge:close_edge,child_rect.top,lastY<child_rect.top%3Ffar_edge:close_edge,child_rect.bottom))%7B%7Delse%7Bvar menu%3Dmenu_levels%5Bmenu_levels.length-2%5D%3Bmenu.change_selection(e.target)%7D%7Delse%7Bvar indices%3Dmenu_levels.map(function(menu,i)%7Breturn%5Bis_child(e.target,menu),i%5D%7D).filter(function(result)%7Breturn result%5B0%5D%7D).map(function(result)%7Breturn result%5B1%5D%7D)%3Bif(indices.length)%7Bcreate_menu(%5B%5D,indices%5B0%5D%2B1)%7Delse if(menu_levels%5Bmenu_levels.length-1%5D)%7Bmenu_levels%5Bmenu_levels.length-1%5D.change_selection(null)%7D%7Dvar cdamp%3D0.8%3BlastX%3De.clientX*cdamp%2BlastX*(1-cdamp)lastY%3De.clientY*cdamp%2BlastY*(1-cdamp)if(!isFinite(lastX))lastX%3De.clientX%3Bif(!isFinite(lastY))lastY%3De.clientY%3B%7Dfunction create_menu(items,level)%7Bif(menu_levels.length%3D%3D0%26%26items.length%3D%3D0)return%3Bfor(var i%3Dlevel%3Bi<menu_levels.length%3Bi%2B%2B)%7Bvar m%3Dmenu_levels%5Bi%5D%3Bif(m%26%26m.parentNode)%7Bm.parentNode.removeChild(m)%7Dmenu_levels%5Bi%5D%3Dnull%3B%7Dmenu_levels%3Dmenu_levels.filter(function(e)%7Breturn e%7D)%3Bif(level%3D%3D0%26%26items.length%3D%3D0)%7Bif(menu_overlay%26%26menu_overlay.parentNode)%7Bmenu_overlay.parentNode.removeChild(menu_overlay)%7D%7Dif(items.length%3D%3D0)%7Breturn%7Dvar menu%3Ddocument.createElement(%27menu%27)menu.style.fontFamily%3DsystemFonts%5BgetPlatform()%5D%3Bmenu_levels%5Blevel%5D%3Dmenu%3Bmenu.level%3Dlevel%3Bitems.forEach(function(item)%7Bif(item%3D%3D%27-%27)%7Bmenu.appendChild(document.createElement(%27hr%27))return%7Dvar btn%3Ddocument.createElement(%27button%27)btn.innerHTML%3Ditem.html if(item.checked)%7Bbtn.setAttribute(%27checked%27,%27checked%27)%7Dif(item.group)%7Bbtn.group%3Ditem.group%3B%7Dmenu.appendChild(btn)btn.action%3Ditem.action%3Bbtn.toggle%3Ditem.toggle%3Bif(item.items)%7Bbtn.className%2B%3D%27 dropdown%27 btn.items%3Ditem.items%3B%7Dif(item.disabled)%7Bbtn.className%2B%3D%27 disabled gray%27%7Delse%7Bif(item.gray)%7Bbtn.className%2B%3D%27 gray%27%7Dif(item.cls)%7Bbtn.className%2B%3D%27 %27%2Bitem.cls%7D%7D%7D)menu.addEventListener(%27mouseup%27,function(e)%7Bvar index%3Dmenu.change_selection(e.target)sel.deselect_time%3DDate.now()menu.do_action(index)%7D)menu.do_action%3Dfunction(index)%7Bif(index!%3D-1)%7Bvar btn%3Dmenu.children%5Bindex%5Dif(btn.group)%7B%5B%5D.forEach.call(menu.children,function(alt)%7Bif(alt.group%3D%3D%3Dbtn.group)%7Balt.removeAttribute(%27checked%27)%7D%7D)btn.setAttribute(%27checked%27,%27checked%27)%7Delse if(btn.toggle)%7Bif(btn.hasAttribute(%27checked%27))%7Bif(btn.toggle(false))create_menu(%5B%5D,0)%3Bbtn.removeAttribute(%27checked%27)%7Delse%7Bif(btn.toggle(true))create_menu(%5B%5D,0)%3Bbtn.setAttribute(%27checked%27,%27checked%27)%7D%7Delse if(btn.action%26%26!btn.has(%27disabled%27))%7Bbtn.action()create_menu(%5B%5D,0)%7D%7D%7Dmenu.selectedIndex%3D-1%3Bmenu.change_selection%3Dfunction(index,no_expand)%7Bif(typeof index!%3D%27number%27)%7Bfor(var i%3D0%3Bi<menu.children.length%3Bi%2B%2B)%7Bif(menu.children%5Bi%5D%3D%3Dindex%7C%7Cis_child(index,menu.children%5Bi%5D))%7Bindex%3Di%3Bbreak%3B%7D%7Dif(typeof index!%3D%27number%27)index%3D-1%3B%7Dvar old%3Dmenu.children%5Bmenu.selectedIndex%5D,select%3Dmenu.children%5Bindex%5D%3Bif(old)old.removeAttribute(%27selected%27)%3Bif(select%26%26!select.has(%27disabled%27))%7Bselect.setAttribute(%27selected%27,true)%3Bmenu.selectedIndex%3Dindex%3B%7Delse%7Bmenu.selectedIndex%3Dnull%3B%7Dif(select%26%26select.items)%7Bif(!no_expand)%7Bmenu_typedone()%3Bvar sub%3Dtypeof select.items%3D%3D%27function%27%3Fselect.items():select.items%3Bsub_menu(sub,menu.level%2B1,select.getBoundingClientRect())%7D%7Delse%7Bcreate_menu(%5B%5D,menu.level%2B1)%7Dreturn index%7Dmenu.style.position%3D%27fixed%27 menu.style.top%3D%27-1000px%27%3Bmenu.style.left%3D%27-1000px%27%3Bmenu.style.zIndex%3Ddepth(%27menu%27)if(!(menu_overlay%26%26menu_overlay.parentNode))%7Bmenu_overlay%3Ddocument.createElement(%27div%27)menu_overlay.className%3D"contextmenu_overlay" menu_overlay.style.zIndex%3Ddepth(%27overlay%27)get_container().appendChild(menu_overlay)menu_overlay.addEventListener("mousewheel",function(e)%7Be.preventDefault()%3Be.stopPropagation()%3Breturn false%3B%7D,true)%3B%7Dmenu_overlay.appendChild(menu)return menu%7Dfunction sub_menu(items,level,rect)%7Bvar menu%3Dcreate_menu(items,level)%3Bvar pad%3D10%3Bif(rect.top%2Bmenu.offsetHeight%2Bpad>innerHeight)%7Bmenu.style.top%3D(innerHeight-menu.offsetHeight-pad)%2B%27px%27%3B%7Delse%7Bmenu.style.top%3Drect.top%2B%27px%27%3B%7Dif(rect.right%2Bmenu.offsetWidth>innerWidth)%7Bmenu.style.left%3D(rect.left-menu.offsetWidth)%2B%27px%27%3B%7Delse%7Bmenu.style.left%3Drect.right%2B%27px%27%3B%7D%7Dfunction context_menu(items,x,y)%7Bvar menu%3Dcreate_menu(items,0)if(y%2Bmenu.offsetHeight>innerHeight)%7Bif(y-menu.offsetHeight<0)%7Bmenu.style.top%3D(innerHeight-menu.offsetHeight)%2B%27px%27%7Delse%7Bmenu.style.top%3D(y-menu.offsetHeight)%2B%27px%27%7D%7Delse%7Bmenu.style.top%3Dy%2B%27px%27%7Dif(x%2Bmenu.offsetWidth>innerWidth)%7Bif(x-menu.offsetWidth<0)%7Bmenu.style.left%3D(innerWidth-menu.offsetWidth)%2B%27px%27%7Delse%7Bmenu.style.left%3D(x-menu.offsetWidth)%2B%27px%27%7D%7Delse%7Bmenu.style.left%3Dx%2B%27px%27%7Dmenu.focus()%7Dfunction is_child(el,parent)%7Bif(!parent)return null%3Bwhile(el%26%26el.parentNode!%3Dparent)el%3Del.parentNode%3Breturn el%7DsystemFonts%3D%7Bcros:%27Noto Sans UI, sans-serif%27,linux:%27Ubuntu, sans-serif%27,mac:%27Lucida Grande, sans-serif%27,win:%27Segoe UI, Tahoma, sans-serif%27,unknown:%27sans-serif%27%7D%3Bfunction getPlatform()%7Bvar ua%3Dwindow.navigator.appVersion%3Bif(ua.indexOf(%27Win%27)!%3D-1)return %27win%27%3Bif(ua.indexOf(%27Mac%27)!%3D-1)return %27mac%27%3Bif(ua.indexOf(%27Linux%27)!%3D-1)return %27linux%27%3Bif(ua.indexOf(%27CrOS%27)!%3D-1)return %27cros%27%3Breturn %27unknown%27%3B%7D%3Bdocument.addEventListener(%27contextmenu%27,right_click,true)function get_letters(region)%7Breturn%5B%5D.concat.apply(%5B%5D,region.lines.map(function letters_from_line(line)%7Breturn%5B%5D.concat.apply(%5B%5D,line.words.map(function(e)%7Breturn e.letters%7D))%7D))%7Dfunction guess_engine()%7Bvar image%3Dim(sel.img)var cols%3D(!sel.start%26%26sel.stack.length%3D%3D0)%3Fimage.regions:selected_regions()%3Bvar popularity%3D%7B%27tess:eng%27:0.1%7D%3B%3B((image.lookup%7C%7C%7B%7D).chunks%7C%7C%5B%5D).forEach(function(e)%7Bpopularity%5Be.engine%5D%3D(popularity%5Be.engine%5D%7C%7C0)%2B1%7D)%3Bvar most_popular%3DObject.keys(popularity).sort(function(b,a)%7Breturn popularity%5Ba%5D-popularity%5Bb%5D%7D)%5B0%5D%3Bif(cols.filter(function(region)%7Bvar letters%3Dget_letters(region)%3Bvar frac_up%3Dletters.filter(function(e)%7Breturn e.height/region.xheight>1.2%7D).length/letters.length%3Bif(frac_up<0.05%26%26(region.lettersize/region.xheight)>0.7)%7Breturn true%7Dreturn false%7D).length>0.8*cols.length)%7Breturn "tess:joh"%7Delse%7Breturn most_popular%7D%7Dfunction right_click(e)%7Bvar mouse%3Dget_mouse(e)%3Bif(!mouse.img)return%3Bvar cursor%3Dget_cursor(mouse)%3Bif(!cursor.region)return%3Be.preventDefault()e.stopImmediatePropagation()e.stopPropagation()if(menu_levels.some(function(menu)%7Breturn is_child(e.target,menu)%7D))return%3Bvar items%3D%5B%5D%3Bvar image%3Dim(mouse.img)%3Bvar params%3Dimage.params%3Bfunction check_sel()%7Bif(sel.img!%3Dmouse.img)%7Bclear_selection()sel.img%3Dmouse.img%7D%7Dcheck_sel()var min_dist%3DInfinity%3Bif(sel.stack.length>0%7C%7Csel.start)%7Bvar image%3Dim(sel.img)%3Bvar layout%3Dimage_layout(sel.img)var sx%3Dsel.img.width/sel.img.naturalWidth/image.params.scale,sy%3Dsel.img.height/sel.img.naturalHeight/image.params.scale%3Bvar boxes%3D%5B%5D.concat.apply(%5B%5D,get_selection(sel,image).map(function(pair)%7Breturn rectify_squig(pair%5B0%5D,pair%5B1%5D)%7D)).map(function(box)%7Breturn%7Bx0:box.x0*sx%2Blayout.X,y0:box.y0*sy%2Blayout.Y,x1:box.x1*sx%2Blayout.X,y1:box.y1*sy%2Blayout.Y%7D%7D)for(var i%3D0%3Bi<boxes.length%26%26min_dist>0%3Bi%2B%2B)%7Bvar dist%3Dpoint2rect(e.clientX,e.clientY,boxes%5Bi%5D)if(dist<min_dist)%7Bmin_dist%3Ddist%3B%7D%7D%7Dif(min_dist>30)%7Bclear_selection()if(cursor.line.words.length%3D%3D1)%7Bpush_sel(cursor,cursor)%7Delse%7Bpush_sel(%7Bregion:cursor.region,line:cursor.line,letter:cursor.word.letters%5B0%5D%7D,%7Bregion:cursor.region,line:cursor.line,letter:cursor.word.letters%5Bcursor.word.letters.length-1%5D%7D)%7Dupdate_selection()requestAnimationFrame(modify_clipboard)%7Dvar selection%3Dget_selection(sel,image)%3Bitems.push(%7Bhtml:"Copy Text",disabled:selection.length%3D%3D0%7C%7Cglobal_params.demo_mode,action:function()%7Bbroadcast(%7Btype:%27copy%27,text:extract_selection(sel,image).text,id:image.id%7D)%7D%7D)if(virtualize_region(image,selected_regions()).some(function(region)%7Breturn region.virtual%7D))%7Bitems.push(%7Bhtml:"Modify Text <small>(BETA)</small>",action:function()%7Bselected_regions().filter(function(region)%7Breturn virtualize_region(image,region).virtual%3D%3Dtrue%7D).forEach(function(region)%7Bvar translate%3Dimage.translate%5Bregion.id%5Dtranslate.text%3Dprompt("Enter new text for region",translate.text)translate.language%3D%27custom%27 translate_region(image,region)update_selection()%7D)%7D%7D)%7Ditems.push(%7Bhtml:"Select All",disabled:image.regions.length%3D%3Dselection.length,action:function()%7Bcheck_sel()image.regions.forEach(function(col)%7Bpush_sel(%7Bregion:col%7D)%7D)update_selection()%7D%7D)items.push(%7Bhtml:"Open in New Tab",action:function()%7Bopen(image.el.src)%7D%7D)items.push(%27-%27)function engine(id,html)%7Bvar has_lookup%3Dselected_regions().some(function(region)%7Breturn get_lookup_chunks(image,region).some(function(chunk)%7Breturn chunk.engine%3D%3Did%7D)%7D)%3Bvar is_ideal%3Dguess_engine()%3D%3Did%3Bvar is_local%3D(id%3D%3D%27ocrad%27)%3Breturn%7Bhtml:html,checked:selected_regions().some(function(region)%7Breturn(image.ocr%5Bregion.id%5D%7C%7C%7B%7D)._engine%3D%3Did%7D),disabled:(global_params.demo_mode%26%26!has_lookup)%7C%7C(location.protocol%3D%3D%27file:%27%26%26!is_local),cls:%5Bis_ideal%3F%27ideal%27:%27%27,(has_lookup%7C%7Cis_local)%3F%27%27:%27halfgray%27%5D.join(%27 %27),action:function()%7Bcheck_sel()if(!image.ocr)image.ocr%3D%7B%7D%3Bselected_regions().forEach(function(region)%7Bvar ocr%3Dimage.ocr%5Bregion.id%5D%3Bif(ocr%26%26ocr.engine%3D%3Did)return%3Bimage.ocr%5Bregion.id%5D%3D%7Bengine:id,waiting:Date.now()%7Dif(image.translate%26%26image.translate%5Bregion.id%5D)%7Bvar translate%3Dimage.translate%5Bregion.id%5Dtranslate.waiting%3DDate.now()delete translate.finished delete translate.processing%7Dregion.shimmer%3DDate.now()%7D)update_translations(image)update_selection()draw_overlays(image)start_shimmer()%7D%7D%7Dfunction escape_html(text)%7Bvar d%3Ddocument.createElement(%27div%27)%3Bd.appendChild(document.createTextNode(text))%3Breturn d.innerHTML%7Dfunction language_items()%7Bvar items%3D%5Bengine(%27ocrad%27,"English <small>Ocrad.js</small>"),%5Dif(/https%3F%5C:/.test(location.protocol))%7Bitems%3Ditems.concat(%5B%27-%27,engine("tess:eng","English <small>Tesseract</small>"),engine("tess:joh","Internet Meme"),engine("tess:rus","Russian"),engine("tess:deu","German"),engine("tess:spa","Spanish"),engine("tess:chi_sim","Chinese Simplified"),engine("tess:chi_tra","Chinese Traditional"),engine("tess:fra","French"),engine("tess:jpn","Japanese"),%5D)%7Delse%7Bitems.push(%7Bhtml:"<small>Remote OCR only for http(s) URLs</small>",disabled:true%7D)%7Dreturn items%3B%7Ditems.push(%7Bhtml:"Language",items:language_items%7D)if(!image.plaster)image.plaster%3D%7B%7D%3Bfunction translate(id,html)%7Bvar unplastered%3Dselected_regions().some(function(region)%7Breturn!(region.id in image.plaster)%7D)var checked%3Dselected_regions().some(function(region)%7Breturn(!image.translate%7C%7Cimage.translate%5Bregion.id%5D%7C%7C%7B%7D).language%3D%3Did%7D)var has_lookup%3D((image.lookup%7C%7C%7B%7D).translations%7C%7C%5B%5D).some(function(e)%7Breturn e.target%3D%3Did%7D)%3Bvar is_local%3D!id%7C%7C(%5B%27erase%27,%27echo%27,%27esrever%27,%27pig%27%5D.indexOf(id)!%3D-1)return%7Bhtml:html,cls:(has_lookup%7C%7Cis_local%7C%7Cchecked)%3F%27%27:%27halfgray%27,disabled:(location.protocol%3D%3D%27file:%27%26%26!is_local),checked:checked,action:function()%7Bcheck_sel()if(!image.translate)image.translate%3D%7B%7D%3Bvar cols%3D(!sel.start%26%26sel.stack.length%3D%3D0)%3Fimage.regions:selected_regions()%3Bconsole.log(cols,sel.start,sel.stack)if(id!%3D%27erase%27)%7Bselected_regions().forEach(function(region)%7Bif(!image.ocr%5Bregion.id%5D%7C%7Cimage.ocr%5Bregion.id%5D.engine%3D%3D%27default%27)%7Bimage.ocr%5Bregion.id%5D%3D%7Bengine:get_ocr_engine(image,region,guess_engine()),waiting:Date.now()%7D%7D%7D)%7Dclear_selection()cols.forEach(function(col)%7Bpush_sel(%7Bregion:col%7D)%7D)cols.forEach(function(col)%7Bif(image.translate%5Bcol.id%5D)%7Bif(image.translate%5Bcol.id%5D.language%3D%3Did)return%3Bvar el%3Dimage.translate%5Bcol.id%5D.paper el.style.opacity%3D0%3BsetTimeout(function()%7Bif(el.parentNode)el.parentNode.removeChild(el)%3B%7D,2000)%7Dimage.translate%5Bcol.id%5D%3D%7Blanguage:id,waiting:Date.now()%7D%7D)update_translations(image)start_shimmer()draw_overlays(image)update_selection()%7D%7D%7Dfunction translate_items()%7Bvar items%3D%5B%5D%3Bitems%3Ditems.concat(%5Btranslate(null,%27No Translation%27),translate(%27erase%27,%27Erase Text%27),translate(%27echo%27,%27Reprint Text%27),%5D)if(selected_regions().some(function(region)%7Breturn(!image.translate%7C%7Cimage.translate%5Bregion.id%5D%7C%7C%7B%7D).language%3D%3D%27custom%27%7D))%7Bitems.push(translate(%27custom%27,%27Custom Text%27))%7Dif(/https%3F%5C:/.test(location.protocol))%7Bitems%3Ditems.concat(%5B%27-%27,translate(%27en%27,%27English <small>Google Translate</small>%27),translate(%27es%27,%27Spanish <small>Microsoft Translate</small>%27),translate(%27ru%27,%27Russian <small>Yandex Translate</small>%27),translate(%27zh-CN%27,%27Chinese Simplified%27),translate(%27zh-TW%27,%27Chinese Traditional%27),translate(%27ja%27,%27Japanese%27),translate(%27de%27,%27German%27),translate(%27fr%27,%27French%27),%5D)%7Delse%7Bitems%3Ditems.concat(%5B%27-%27,translate(%27esrever%27,%27Reverse Text%27),translate(%27pig%27,%27Pig Latin%27),%7Bhtml:"<small>Remote OCR only for http(s) URLs</small>",disabled:true%7D%5D)%7Dreturn items%3B%7Ditems.push(%7Bhtml:"Translate <small>(BETA)</small>",items:translate_items%7D)items.push(%27-%27)items.push(%7Bhtml:"Options",items:%5B%7Bhtml:%27Show OCR Disclaimer%27,disabled:global_params.demo_mode,checked:get_setting(%27warn_ocrad%27),toggle:function(val)%7Bput_setting(%27warn_ocrad%27,val)%7D%7D,%7Bhtml:%27Disable lookup%27,disabled:global_params.demo_mode,checked:get_setting(%27no_lookup%27),toggle:function(val)%7Bput_setting(%27no_lookup%27,val)%7D%7D,%27-%27,%7Bhtml:%27Disable for this image%27,disabled:true%7D,%7Bhtml:%27Disable on this page%27,disabled:true%7D,%7Bhtml:%27Disable on <i>%27%2Bescape_html(location.hostname)%2B%27</i>%27,disabled:true%7D,%27-%27,%7Bhtml:%27Add Translate Key%27,disabled:global_params.demo_mode,action:function()%7Bopen(global_params.apiroot%2B%27enter_key/%27%2Bglobal_params.user_id)%7D%7D,%7Bhtml:%27Report Issue%27,action:function()%7Bopen("https://docs.google.com/forms/d/1WLitLvYOPefYOd9S2SeCcYCWb-fsfbyDCd8_moIR0C4/viewform%3Fentry.467368810%3D"%2BencodeURIComponent(image.src)%2B"%26entry.149654426%3D"%2BencodeURIComponent(location.href))%7D%7D%5D%7D)items.push(%7Bhtml:"Advanced",items:%5B%7Bhtml:"Show Regions",checked:session_params.show_regions,toggle:function(val)%7Bcheck_sel()session_params.show_regions%3Dval draw_annotations(sel.img,image)%7D%7D,%7Bhtml:"Show Lines",checked:session_params.show_lines,toggle:function(val)%7Bcheck_sel()session_params.show_lines%3Dval%3Bdraw_annotations(sel.img,image)%7D%7D,%7Bhtml:"Show Stitching",checked:session_params.show_stitching,disabled:!image.stitch_debug,toggle:function(val)%7Bcheck_sel()session_params.show_stitching%3Dval%3Bdraw_annotations(sel.img,image)%7D%7D,%7Bhtml:"Show Letters",checked:session_params.show_letters,toggle:function(val)%7Bcheck_sel()session_params.show_letters%3Dval%3Bdraw_annotations(sel.img,image)%7D%7D,%7Bhtml:"Show Contours",checked:session_params.show_contours,disabled:global_params.demo_mode,toggle:function(val)%7Bcheck_sel()session_params.show_contours%3Dval%3Bdraw_annotations(sel.img,image)%7D%7D,%7Bhtml:"Show Chunks",checked:session_params.show_chunks,toggle:function(val)%7Bcheck_sel()session_params.show_chunks%3Dval%3Bdraw_annotations(sel.img,image)%7D%7D,%27-%27,%7Bhtml:"Clear State",disabled:global_params.demo_mode,action:function(val)%7Bcheck_sel()layer_clear(sel.img,%27*%27)delete images%5Bget_id(sel.img)%5D%3B%7D%7D,%7Bhtml:"Debug Mode",disabled:global_params.demo_mode,checked:image.params.debug,toggle:function(val)%7Bcheck_sel()layer_clear(sel.img,%27*%27)delete images%5Bget_id(sel.img)%5D%3Bim(sel.img).params.debug%3Dval%3Breturn true%7D%7D%5D%7D)var button%3D""%3Bif(!(image.lookup%7C%7C%7B%7D).finished%26%26(session_params.no_lookup%7C%7C!navigator.onLine))%7Bbutton%3D"<span class%3D%27credits-button offline%27>offline</span>"%7Delse if(global_params.demo_mode)%7Bbutton%3D"<span class%3D%27credits-button low%27>demo</span>"%7Delse%7B%7Ditems.push(%7Bhtml:"naptha <small>0.7.5</small> "%2Bbutton,gray:true,action:function()%7Bopen("http://projectnaptha.com/%23"%2Bglobal_params.user_id)%7D%7D)context_menu(items,e.clientX,e.clientY)%7Dfunction CanvasFlame(canvas)%7Bthis.orig_canvas%3Ddocument.createElement("canvas")%3Bthis.orig_context%3Dthis.orig_canvas.getContext("2d")%3Bthis.canvas%3Dcanvas%3Bthis.width%3Dthis.orig_canvas.width%3Dthis.canvas.width%3Bthis.height%3Dthis.orig_canvas.height%3Dthis.canvas.height%3Bthis.context%3Dthis.canvas.getContext("2d")%3Bthis.context.fillStyle%3D"rgba(0, 0, 0, 0)" this.context.fillRect(0,0,this.width,this.height)%3Bthis.image%3Dthis.context.getImageData(0,0,this.width,this.height)%3Bthis.data%3Dthis.image.data%3Bthis.palette%3Dnew Array(256)%3Bfor(var i%3D0%3Bi<%3D64%3Bi%2B%2B)%7Bvar alpha%3DMath.floor(Math.pow(Math.random(),0.1)*256)this.palette%5Bi%5D%3D%5Bi*4,0,0,alpha%5D%3Bthis.palette%5Bi%2B64%5D%3D%5B255,i*4,0,alpha%5D%3Bthis.palette%5Bi%2B128%5D%3D%5B255,255,i*4,alpha%5D%3Bthis.palette%5Bi%2B192%5D%3D%5B255,255,255,alpha%5D%3B%7Dthis.updateEmbers()this.flames%3Dnew Uint8Array(this.width*this.height)%3Bfor(var i%3D0%3Bi<this.width*this.height%3Bi%2B%2B)%7Bthis.flames%5Bi%5D%3D0%3B%7Dthis.started%3Dfalse%3B%7DCanvasFlame.prototype.updateEmbers%3Dfunction()%7Bthis.orig_image%3Dthis.orig_context.getImageData(0,0,this.orig_canvas.width,this.orig_canvas.height)%3Bthis.orig_data%3Dthis.orig_image.data%3Bthis.emits%3Dnew Uint8Array(this.width*this.height)%3Bfor(var i%3D0%3Bi<this.emits.length%3Bi%2B%2B)%7Bthis.emits%5Bi%5D%3D(this.orig_data%5Bi*4%5D%2Bthis.orig_data%5Bi*4%2B1%5D%2Bthis.orig_data%5Bi*4%2B2%5D)/3>70%3B%7D%7DCanvasFlame.prototype.start%3Dfunction(effect_duration,emit_duration)%7Bif(this.started)return%3Bthis.started%3Dtrue%3Bthis.stopEmit%3Dfalse%3Bvar myself%3Dthis%3Bvar effect_time%3Deffect_duration%7C%7C6.0%3Bvar emit_time%3Demit_duration%7C%7C(effect_time-2.0)%3Bif(emit_time>effect_time%7C%7Cemit_time<0)emit_time%3Deffect_time/2.0%3Bmyself.loop()%7D%3BCanvasFlame.prototype.stop%3Dfunction()%7Bthis.started%3Dfalse%3B%7D%3BCanvasFlame.prototype.loop%3Dfunction()%7Bvar x,y,i%3Bvar run_count%3D0%3Bfor(y%3Dthis.height-2%3By>-1%3By--)%7Bfor(x%3D0%3Bx<this.width%3Bx%2B%2B)%7Bi%3Dy*this.width%2Bx%3Bthis.flames%5Bi%5D%3D(!this.stopEmit%26%26this.emits%5Bi%5D)%3F(255-Math.round(Math.random()*70)):Math.round((%2B(x>0%3Fthis.flames%5Bi-1%5D:0)%2Bthis.flames%5Bi%5D%2B(x<this.width-1%3Fthis.flames%5Bi%2B1%5D:0)%2B(x>1%3Fthis.flames%5Bi-2%2Bthis.width%5D:0)%2B(x>0%3Fthis.flames%5Bi-1%2Bthis.width%5D:0)%2Bthis.flames%5Bi%2Bthis.width%5D*2%2B(x<this.width-1%3Fthis.flames%5Bi%2B1%2Bthis.width%5D:0)%2B(x<this.width-2%3Fthis.flames%5Bi%2B2%2Bthis.width%5D:0))*(0.97%2B((Math.random()-0.5)*0.3))/9.0)%3Bif(this.flames%5Bi%5D>10%26%26this.flames%5Bi%5D<170)%7Bfor(j%3D0%3Bj<4%3Bj%2B%2B)%7Bthis.data%5Bi*4%2Bj%5D%3Dthis.palette%5Bthis.flames%5Bi%5D>255%3F255:this.flames%5Bi%5D%5D%5Bj%5D%3B%7Drun_count%2B%2B%7Delse%7Bthis.data%5Bi*4%2B3%5D%3D0%3B%7D%7D%7Dif(run_count<5)%7Bthis.stop()%3B%7Dthis.context.putImageData(this.image,0,0)%3Bif(this.started)%7Bvar myself%3Dthis%3BrequestAnimationFrame(function()%7Bmyself.loop()%7D)%7D%7D%3B%7D)()%3B
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment