+
+
+These are some handy documents which address some of the more obscure
+features of the asdf.us color code tool:
+
+* tips.txt - Tips on using the keyboard
+* irssi.txt - Instructions on using IRSSI to make color codes.
+* nopaint.txt - A guide to "No Paint"
+
+Documents on Shaders
+
+* shadetut.txt - A brief tutorial on ASCII shaders.
+* shaders/brush.txt - Shaders designed to work on the brush
+* shaders/canvas.txt - Shaders designed to work on the canvas
+* shaders/util.txt - Miscellaneous utilities / snippets
+
+For more information on IRC, Color Codes, and much more, visit the
+documentation sitemap, part of the Jollo IRC Network.
+
+asdf.us/ascii
+
diff --git a/doc/irssi.txt b/doc/irssi.txt
new file mode 100644
index 0000000..baded1c
--- /dev/null
+++ b/doc/irssi.txt
@@ -0,0 +1,153 @@
+ __________________________________________________________________________
+ ____ ____ _____ ____ ___ ___ ____
+ /_____ _____/ / ___ \ / ____/ / ____/ /_____ _____/
+ / / / / \ \ / / / / / /
+ / / / /____/ / \ \__ \ \__ / /
+ / / / ___ __/ \__ \ \__ \ / /
+ / / / / \ \ \ \ \ \ / /
+ _____/ /_____ / / \ \ ____/ / ____/ /____/ /_____
+ ___/ /__/ /_______\ \__/ /___/ // /____
+ __________________________________________________________________________
+
+
+OPTIMIZE YOUR TERMINAL FOR COLOR CODES ON OSX
+=============================================
+
+You can use terminal, some nerds seem to prefer iterm2, but it's up to you
+... http://iterm2.com/
+
+To see color codes correctly, make sure your term type is xterm-256color --
+
+If you use iterm: https://s3.amazonaws.com/luckyplop/a1b0f0e3d6eae746c82194876f2ccd8b200bc3bb.png
+If you use terminal: https://s3.amazonaws.com/luckyplop/6a2270b58ea1cfac587607215e1b829f41d47355.png
+
+Restart iterm after changing this setting.
+
+The default iterm colors are kind of ugly for color codes, so you may want to change them
+to something like this..
+
+https://s3.amazonaws.com/luckyplop/c5f3a1f2b8e2f8a745fa2638c21af7d26117b91b.png
+
+You can download this iTerm color preset here:
+
+http://asdf.us/ascii/doc/bamboo.itermcolors
+
+
+INSTALLING IRSSI ON OSX
+=======================
+
+For me the easiest thing is to install homebrew >> http://brew.sh/
+
+Follow les instructions and then..
+
+brew install irssi
+
+Then you run irssi from a terminal by typing the magic word..
+
+irssi
+
+
+SETTING UP IRSSI FOR COLOR CODES
+================================
+
+Use these commands for proper unicode support --
+
+/set term_charset utf-8
+/set recode_autodetect_utf8 ON
+/set recode_fallback ISO-8859-15
+/set recode ON
+
+Use these commands to dump color codes quickly and efficiently --
+
+/set cmd_queue_speed 0msec
+/set cmds_max_at_once 1
+/set flood_max_msgs 0
+/set flood_timecheck 0
+
+To make your log go back very far --
+
+/set scrollback_lines 20000
+/set scrollback_time 10day
+
+Remember to type /save after doing a /set to save your changes!
+
+/save
+
+Your irssi configuration will be stored in your home directory in..
+
+.irssi/config
+
+
+NORMAL IRSSI OPERATION
+======================
+
+If you do not want to do the autojoin thing these are the commands you'd normally use to connect:
+
+/server -ssl irc.asdf.us 7777
+/join #ascii
+
+
+SETTING UP IRSSI TO AUTOJOIN #ASCII
+===================================
+
+First run irssi, then paste in these commands.
+Please change YOUR_NICK_HERE to your preferred username!
+
+/network add -nick YOUR_NICK_HERE -user YOUR_NICK_HERE -realname "YOUR NAME HERE" asdf
+/server add -network asdf -auto -ssl irc.asdf.us 7777
+/channel add -auto #ascii asdf
+/save
+/quit
+
+Now run irssi again.. it should autoconnect to the channels and stuff.
+If you want it to move you into #ascii by default, you can do ctrl-N and then
+
+/layout save
+/save
+
+
+IRC TIPS
+========
+
+/join #ascii -- join a channel :)
+/part #ascii -- leave a channel ;(
+/quit blabla -- quit irc (with the quit message 'blabla')
+/list -- list channels
+/nick booboo -- change your nick to booboo
+/who #ascii -- show complete list of people on #ascii
+/names -- show quick list of names
+/msg nick blabla -- send someone a private message ;)
+
+
+IRSSI TIPS
+==========
+
+Ctrl-N -- move to next window
+Ctrl-P -- move to previous window
+/window close -- close a window
+Fn-up arrow -- page up (if you don't have a pageup key)
+Fn-down arrow -- page down (if you don't have a pagedown key)
+
+
+IRSSI SCRIPTING
+===============
+
+This is its own can of worms. May we suggest:
+
+http://scripts.irssi.org/scripts/noticelogic.pl
+http://scripts.irssi.org/scripts/nickcolor.pl
+
+To make it run on startup, copy it into ~/.irssi/scripts/autorun/ (and restart irssi)
+
+Another fun IRC thing is running an XDCC server for sharing files..
+-- for more info see http://asdf.us/xdcc/
+
+
+AND REMEMBER...
+===============
+
+Have fun and be safe online!
+
+
+
+
diff --git a/doc/nopaint.txt b/doc/nopaint.txt
new file mode 100644
index 0000000..5428cab
--- /dev/null
+++ b/doc/nopaint.txt
@@ -0,0 +1,58 @@
+
+ "" 88
+ 9,88m, ,8888, 9,88m, ,888, mm 9,88m, 8888
+ 88 88 86 98 88 88 ,mm88 88 88 88 88
+ 88 88 '8888' 88888' "nn89 88 88 88 "8m
+ 88 a brief tutorial :)
+
+Last month or so I encountered Jeffrey Scudder's tool 'No Paint' -
+
+https://www.nopaint.org/
+
+- an automatic drawing tool with a minimal interface: you control it using
+just two buttons. The No Paint tool provided much entertainment on #sally,
+so during some downtime I added similar functionality to the asdf.us/ascii
+tool. Under the brush, you should see two buttons - to kick it off, click
+'paint' and it will begin drawing.
+
+If you don't like what it's doing, click 'no' -
+- this will remove the current line and start drawing a new line.
+
+If you like what it's doing, click 'paint' -
+- the line will be applied to the canvas, and it will start drawing anew.
+
+While it's going, you can also click 'pause' and it will stop, so you can
+save it or draw on it yourself.
+
+
+Keyboard shortcuts -
+
+left arrow - 'no'
+right arrow - 'paint'
+down arrow - 'pause'
+
+
+Right-click toggles -
+
+If you RIGHT-CLICK on "Paint" it will switch tools automatically.
+
+If you RIGHT-CLICK on "No" it will engage TURBO MODE.
+
+
+Some tools currently implemented -
+
+- solid brush
+- erase brush
+- color-changing brush
+- hue brush
+- letter brush
+- clone tool
+- smear tool
+- fill tool
+- stars brush
+- canvas slide
+- canvas scale
+- canvas rotate
+- canvas colorcycle
+
+
diff --git a/doc/shaders/brush.txt b/doc/shaders/brush.txt
new file mode 100644
index 0000000..8a5f672
--- /dev/null
+++ b/doc/shaders/brush.txt
@@ -0,0 +1,126 @@
+BRUSH SHADERS
+=============
+
+Unless noted, these shaders were written to work on the brush itself.
+Make sure "brush" is selected and "animate" is checked.
+
+
+>> distressed texture brush
+
+Sample use of the "choice" function to get a random color.
+
+var char = choice(" abcdef ")
+lex.bg = +choice("0124")
+lex.fg = +choice("01234")
+lex.char = char
+lex.opacity = char == " " ? 0 : 1
+
+
+
+>> foggy terrain brush
+
+var char = choice(" abcdef ")
+lex.bg = choice([14,15])
+lex.fg = choice("367")
+lex.char = char
+lex.opacity = char == " " ? 0 : 1
+
+
+
+>> mirror brush (left-right)
+
+NOTE: Animate this on the canvas, then draw:
+
+if (x > w/2) {
+ lex.assign( canvas.aa[y][w-x] )
+}
+
+
+
+>> mirror brush (up-down)
+
+NOTE: Animate this on the canvas, then draw:
+
+if (x > h/2) {
+ lex.assign( canvas.aa[h-y][x] )
+}
+
+
+
+>> rainbow stardust brush
+
+Uncheck BG and animate this to brush:
+
+lex.fg = hue(t)
+lex.char = choice(" ,'.,.','****** ")
+
+
+
+>> noise brushes, works on a black background:
+
+lex.bg = max(5, yellow(randint(t)))
+lex.opacity = lex.bg == colors.black ? 0 : 1
+
+
+
+>> simple rainbow:
+
+if (lex.bg != 1) lex.bg = randint(t)
+lex.opacity = lex.bg == colors.black ? 0 : 1
+
+
+
+>> self-erasing:
+
+if (lex.bg != 1) lex.bg = yellow(randint(t))
+lex.opacity = lex.bg == colors.black ? 0 : 1
+
+
+
+>> cycling rainbow brush
+
+if (lex.bg != 1) lex.bg = hue( all_color_hue_order.indexOf( color_names[ lex.bg ] ) + 1 )
+lex.opacity = lex.bg == colors.black ? 0 : 1
+
+
+
+>> "stars" brush.. set your brush to paint just the character "#"
+
+if (lex.char == "#") {
+ lex.fg = hue(randint(15))
+ lex.char = random() > 0.1 ? " " : "+@*.,\"+'*-"[randint(10)]
+}
+
+
+
+>> use fg char to mask mask what you're drawing on the bg
+
+if (lex.char != "/") { lex.bg = 1 }
+
+
+
+>> sharded glitch brush
+
+Example: http://asdf.us/z/kksnvs.png
+
+Use on a brush:
+
+lex.bg = t/y/x
+lex.opacity = lex.bg % 1 ? 0 : 1
+
+
+
+>> incremental brush
+
+Set your brush to be the ^ character, square, about 10x10
+Draw "char" only
+Then animate this shader on the canvas:
+
+if (lex.char=="^") {
+ lex.bg += 1
+ lex.char = " "
+}
+lex.bg += 1
+
+
+
diff --git a/doc/shaders/canvas.txt b/doc/shaders/canvas.txt
new file mode 100644
index 0000000..3e036bd
--- /dev/null
+++ b/doc/shaders/canvas.txt
@@ -0,0 +1,237 @@
+CANVAS SHADERS
+==============
+
+These shaders were written to work on areas of canvas.
+Make sure "canvas" is selected and "animate" is checked.
+
+
+>> original shader..
+
+lex.bg = hue((x+y*y+t/10)/20)
+lex.fg = (x+y)%16
+lex.char = (y%2) ? ":" : "%"
+
+
+
+>> energy ball ascii shader
+
+d = dist(x/2+w/4, y, w/2, h/2)
+an = angle(x/2+w/4, y, w/2,h/2)+t/4200
+r=10.2
+
+if (d < r) lex.bg = randint(r)
+
+ll=abs(an|0)+""
+lex.char=ll[ll.length-1]
+
+if (d > r) {
+ lex.bg = randint(d)
+ lex.fg = randint(d)
+ lex.char = ll[ll.length-2]
+}
+
+
+
+>> drifting fire
+
+t += sin(x/1000)*100000
+pos = y/h*6 + sin(x*3) - cos(y*t/10000-10)
+pos = clamp(pos, 0, 6)
+lex.bg = hue(pos)
+
+
+
+>> the "bJoel56" shader
+
+yy=y
+x-=w/2
+y-=h/2
+
+lex.bg = blue(yy/h+random())
+lex.fg = green(yy/h*4 + sin(x/100+random()/2)) // hue(t/1000)|0;
+
+var abcd=".'~:;!>+=icjtJYSGSXDQKHNWM";
+function chara (aa,n) { return aa[clamp(n*aa.length, 0, aa.length)|0] }
+lex.char = chara(abcd, y/h*(5/3 + tan(x/100)+random()/1))
+
+
+
+>> frog shader v2
+
+t/=-100
+d = sinp( (dist(x/2+w/4, y, w/2, h/2) + t)/2 ) * 10
+
+lex.char=',./>"ASE$#'[(floor(d))]
+lex.fg = [1,3,9][floor(d*3)%3]
+lex.bg=1
+
+
+
+>> frog shader v3
+
+// set period to like 0.2 for a normal circle
+period = y/10
+
+t/=-100
+d = sinp( (dist(x/2+w/4, y, w/2, h/2) + t) * period )
+dd = d * 10.5
+d3 = dd < 8 ? 0 : 1
+
+lex.char=' .,"+/>OXEN'[(floor(dd))]
+lex.fg = [3,9][floor(d3)]
+lex.bg=1
+
+
+
+>> spaceships
+
+many cool shaders are possible with this technique.. changing the char
+gradient (lex.char=...) etc. i love how the dots move on v4.
+
+this is a variation that looks like a bunch of ships flying across the screen.
+has a really cool 3d look to it cuz the rows move at different speeds.
+
+period = sin(y)
+
+t/=-100
+d = sinp( (dist(x/2+w/4, y, w/2, h/2) + t) * period )
+dd = d * 10.5
+d3 = dd < 8 ? 0 : 1
+
+lex.char=' .,"+/>\^+'[(floor(dd))]
+lex.fg = [3,9][floor(d3)]
+lex.bg=1
+
+
+
+>> concentric circles with a wavy "sunburst" pattern going around them
+
+x -= w/2
+y -= h/2
+
+x /= h
+y /= h/2 + 2
+
+r = dist(x,y,0,0)
+ang = angle(x,y,0,0)
+
+if (r < 0.6) {
+ if (abs(mod(sin((r*t)/100000000000) + ang*18,TWO_PI)) < 2)
+ lex.bg = 12
+ else
+ lex.bg = 5
+}
+else if (r < 0.65)
+ lex.bg = 4
+else if (abs(mod(sin((r*t)/100000000000) + ang*18,TWO_PI)) < 2)
+ lex.bg = 7
+else
+ lex.bg = 8
+
+
+
+>> slash-based interference patterns
+
+if (x > h*2) x=h-x
+y-=h/2
+t/=2000
+
+if (sin(x-y*t) > 0) {
+ lex.bg=1
+ lex.fg=4
+ lex.char= Math.floor(x-y*10001+t)%2 ? '\/' : '\\'
+}
+else {
+ lex.bg=1
+ lex.fg=9
+ lex.char= Math.floor(3*x+y+t)%2 ? '\\' : ' '
+}
+
+
+
+>> sparkling stars
+
+if (lex.char != " ") {
+ lex.fg =floor( Math.random()*10 )
+ var az="Xx+*"
+ lex.char=az[floor(az.indexOf(lex.char)+ t/10000000 +Math.random())%az.length]
+}
+
+
+
+>> coogi x/y doodle
+
+xx=x
+t/=1000
+x/=w/2
+y/=h/2
+y-=1
+x-=1
+x*=x-sin(y/t)
+y*=1
+
+lex.bg = 1 // gray( sin(x/(y/3-1)+t) + sin(y/4+t) )
+lex.fg = hue( sin((y/5)+t) - cos(x*t) *5 )
+lex.char = " _.,:;\"~| "[Math.round(xx*(y+1+(x+t/102)/4)*13)%13]
+
+
+
+>> glitch shader - produces odd combinations of fg/bg
+
+lex.char=String.fromCharCode(lex.char.charCodeAt(0)+1)
+lex.bg+=7
+lex.fg+=5
+
+
+
+>> dots / lines shader
+
+xx = ((t/10*x)*y/10)%8
+lex.bg = colors.black
+lex.fg = green(x*3+y*5)
+lex.char = ((xx%1) !== 0) ? " " : " .,;=+!@"[xx]
+
+
+
+>> munching squares horizon
+
+t/=100
+y+=10
+x-=w/2
+x/=y/10
+lex.bg=hue((x^y)+t)
+
+
+
+>> grayscale vertical interlacing
+
+First, make a canvas that's totally white.
+
+Run this shader:
+
+if (lex.bg == 0) {
+ lex.bg = ((x)%2) ? 15 : 14
+}
+
+Then set your brush to a white square.
+
+Run this shader w/ animate:
+
+if (lex.bg == 0) {
+ lex.bg = ((x)%2) ? 0 : 1
+}
+
+
+
+>> nice purple/orange texture
+
+lex.bg=colors.purple
+lex.fg=colors.orange
+x/=3
+x=floor(x+y/2.1) // <- this is cool number to change
+if (x+10*sin(x)+10*cos(y/(t%100)) < y/3) {
+ lex.char="abcdefghijklmnopqrstuvwxyz"[x%26]
+} else {
+ lex.char="abcdefghijklmnopqrstuvwxyz".toUpperCase()[x%26]
+}
+
diff --git a/doc/shaders/util.txt b/doc/shaders/util.txt
new file mode 100644
index 0000000..a455eb3
--- /dev/null
+++ b/doc/shaders/util.txt
@@ -0,0 +1,38 @@
+SHADER UTILITIES
+================
+
+These are little snippets which may be useful in writing your own shaders.
+
+
+
+>> basic way to slow the frame rate of a shader.
+
+window.zz=window.zz||0
+if(!(x+y)) zz++
+if (lex.bg != 1 && !(zz % 4)) {
+ ...
+}
+
+
+>> handy for brushes - use color to mask brush shape
+
+lex.opacity = lex.bg == colors.black ? 0 : 1
+
+
+Tip: Set to "animate brush", then use option+shift (alt+shift) to
+copy color from the canvas. Brush will have the "shape" of the
+copied color only. Can be a cool effect when used with fg/bg only.
+
+
+
+>> copy color from canvas at x/y
+
+lex.assign( canvas.get(x,y) )
+
+
+
+>> animate canvas up and to the left..
+
+lex.assign( canvas.get(x+1,y+1) )
+
+
diff --git a/doc/shadetut.txt b/doc/shadetut.txt
new file mode 100644
index 0000000..681b421
--- /dev/null
+++ b/doc/shadetut.txt
@@ -0,0 +1,156 @@
+ASCII SHADER TUTORIAL
+=====================
+
+In the asdf.us/ascii shaders, you write a little math function that executes on every
+pixel on the selected area. The shaders can affect either the brush, the selected region,
+or the whole canvas.
+
+Shaders can also be animated, so they update live. With a shader applied to the brush,
+the brush changes continuously as you draw.
+
+
+THE LEX OBJECT
+==============
+
+Essentially you are writing a Javascript function that modifies this "lex" object, which
+has four properties
+
+1) lex.bg = this is the background color
+2) lex.fg = this is the foreground color (text color)
+3) lex.char = this is the letter that you see in the space
+4) lex.opacity = this is whether the pixel actually draws or not
+ - so like a circular brush is opacity 1 in the middle and opacity 0 on the corners
+
+
+THE COLOR CODE NUMBERS
+======================
+
+With lex.bg and lex.fg, the goal is to have a number between 0 and 15, corresponding to
+the color code values from mIRC.
+
+If you shift-click on the color palette, you can cycle it around to the one which shows
+the actual order of the mIRC colors.
+
+The mIRC colors are the ones that go white, black, dark blue, green, red, dark red ...
+and these correspond to the numbers 0, 1, 2, 3, 4 ...
+
+
+COLOR CYCLING
+=============
+
+Additionally there are some color functions that might help -
+These functions make it easier to cycle through colors in a way that makes sense logically
+(since the mIRC colors are in a weird order)
+
+- hue(...) = this creates a cycle of colors in terms of their hue or color name,
+ so you get a rainbow that goes from dark red through yellow, green, blue,
+ purple, and back
+- gray(...) = cycles through grayscale
+- red(...) yellow(...) green(...) blue(...) purple(...) = use smaller palettes
+- inv_hue(...) fire(...) dark_gray(...) = these are oddities i made for fun
+
+
+VARIABLES
+=========
+
+Variables you have at your disposal are similar to the asdf.us/shader tool -
+
+- x, y = the coordinates of the pixel
+- mouse.x, mouse.y = the coordinate of the mouse as it hovers over the canvas
+- t = the current time, in milliseconds
+
+TIP: The time will increase very quickly - it's good to add t /= 1000 at the top of
+your shader so it goes slowly (and won't cause a seizure).
+
+
+FUNCTIONS
+=========
+
+Remember, this is Javascript. You have the basic operators:
+
++ - / *
+
+And the bitwise operators:
+
+& | ^ ~
+
+You can do if statements with the standard comparison operators:
+
+< > == <= >=
+
+You also have access to all the functions on the Math object:
+
+floor, ceil, round
+abs, sign, mod(n,m), xor
+pow, exp, sqrt
+cos, sin, tan
+acos, asin, atan, atan2
+random() rand(n) randint(n) randrange(a,b)
+E, PI, PHI
+
+And some utility functions which might help:
+
+clamp(n,min,max)
+mix(n,a,b) (lerp)
+step(a,b)
+smoothstep(min,max,n)
+avg(m,n,a)
+cosp, sinp (mapped to [0,1])
+pixel(x,y) == 4*(y*w+h)
+dist(x,y,a,b)
+angle(x,y,a,b)
+choice(array)
+deg(radians), rad(degrees)
+
+
+BEYOND BASIC COLORS
+===================
+
+Other weird effects are possible if you combine these color functions.
+
+For instance, if you do hue(x+y) you'll get a rainbow. But remember, this is just
+outputting a number between 0 and 15. So you can do hue(x+y) + 1 and get a different
+cycle which does not really have anything to do with the rainbow, but looks cool.
+
+
+HOW DRAWING WORKS IN THE ASCII TOOL
+===================================
+
+When you click and drag to draw a line, your mouse produces a series of points which
+describe the line you tried to draw. But these points do not necessarily make a
+continuous line - more like a series of dots, which it then draw lines between to make
+a "line" or "brush stroke".
+
+A line between two points is made by stamping the brush at regular intervals between the
+points which, with these brushes, ends up filling the space in between so it looks like
+you drew a continuous line.
+
+This is why when you draw a line with a big brush, it smears the outer edges.. The stamps
+happen right next to each other, so you wind up seeing mostly brush edges.
+
+You can visualize this effect with the following shader:
+
+lex.bg = mouse.x + mouse.y
+
+Drawing strokes quickly, or slowly.
+Make sure to make it animate to brush.
+Results could look like this:
+
+http://i.asdf.us/im/f9/1458658781640-ascii-bamboo.png
+
+
+SAMPLE SHADERS
+==============
+
+You can see a list of example shaders here:
+
+http://asdf.us/ascii/doc/shaderz.txt
+
+If you make a cool shader and want to see it on the list, please get in touch!
+You can find me on irc.jollo.org:9999 (ssl) in #sally, making color codes with my friends.
+
+Thanks and have fun!
+
+~ Bamboo, 22 Marzo 2016
+
+
diff --git a/doc/tips.txt b/doc/tips.txt
new file mode 100644
index 0000000..d014197
--- /dev/null
+++ b/doc/tips.txt
@@ -0,0 +1,16 @@
+asdf.us/ascii tips
+==================
+
+These keyboard commands work in brush mode (square, circle, cross):
+
+[ brush smaller
+] brush bigger
+ctrl~click on brush erase cell
+ctrl~click on canvas draw with bg color
+shift~click on canvas draw line from last position
+alt~click on canvas fill brush with sampled color
+alt~shift~click on canvas copy canvas to brush
+rightclick on palette set bg color (when drawing with a letter)
+
+h/t timb for guide
+
diff --git a/fonts/fsex300-webfont.eot b/fonts/fsex300-webfont.eot
new file mode 100644
index 0000000..e2e493b
Binary files /dev/null and b/fonts/fsex300-webfont.eot differ
diff --git a/fonts/fsex300-webfont.eot? b/fonts/fsex300-webfont.eot?
new file mode 100644
index 0000000..e2e493b
Binary files /dev/null and b/fonts/fsex300-webfont.eot? differ
diff --git a/fonts/fsex300-webfont.svg b/fonts/fsex300-webfont.svg
new file mode 100644
index 0000000..a99c895
--- /dev/null
+++ b/fonts/fsex300-webfont.svg
@@ -0,0 +1,6006 @@
+
+
+
\ No newline at end of file
diff --git a/fonts/fsex300-webfont.ttf b/fonts/fsex300-webfont.ttf
new file mode 100644
index 0000000..d53e501
Binary files /dev/null and b/fonts/fsex300-webfont.ttf differ
diff --git a/fonts/fsex300-webfont.woff b/fonts/fsex300-webfont.woff
new file mode 100644
index 0000000..a2225f9
Binary files /dev/null and b/fonts/fsex300-webfont.woff differ
diff --git a/img/bucket.png b/img/bucket.png
new file mode 100644
index 0000000..cfaab30
Binary files /dev/null and b/img/bucket.png differ
diff --git a/img/dropper.gif b/img/dropper.gif
new file mode 100644
index 0000000..7d9eee1
Binary files /dev/null and b/img/dropper.gif differ
diff --git a/img/gray-dither.gif b/img/gray-dither.gif
new file mode 100644
index 0000000..8809b7a
Binary files /dev/null and b/img/gray-dither.gif differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..8f42f4f
--- /dev/null
+++ b/index.html
@@ -0,0 +1,166 @@
+
+
+
+
+asciiblaster
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .
+
+
+
+
+
+
+
+
+
+ x fg
+ x bg
+ x char
+
+ + add
+ _ mirror x
+ _ mirror y
+
+ undo
+ redo
+
+
+ square
+ circle
+ cross
+ text
+ fill
+ select
+
+ rotate
+ scale
+ translate
+ slice
+
+ _ grid
+
+ x vertical
+
+
+
+
+
+ new
+ save
+ load
+
+ shader
+ webcam
+ doc
+ gallery
+
+ _ advanced
+
+ > send to IRC
+
+
+
+ no
+ paint
+ pause
+
+
+ brush: 5 x 5
+ canvas: 100 x 30
+
+
+
+ ascii *irssi mirc ansi
+
+
+
+
+
+
+
+
+
+
+
colorcode is too wide for irc and is cutoff
+
+
+
+
+ _ animate
+ to *canvas brush selection
+
+
+
+
+
+
+ x
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/app.js b/js/app.js
new file mode 100644
index 0000000..6afbbcc
--- /dev/null
+++ b/js/app.js
@@ -0,0 +1,100 @@
+
+var dragging = false
+var drawing = false
+var erasing = false
+var selecting = false
+var filling = false
+var changed = false
+var transforming = false
+var mirror_x = false
+var mirror_y = false
+var focused
+
+var canvas, tools, palette, controls, brush, mode
+var current_tool, current_filetool, current_canvas
+var mouse = { x: 0, y: 0 }
+
+function init () {
+ build()
+ bind()
+ clipboard.load_from_location()
+}
+function build () {
+ shader.init()
+// shader.run(canvas)
+ shader.animate()
+
+ canvas.append(canvas_rapper)
+ brush.append(brush_rapper)
+ palette.append(palette_rapper)
+ letters.append(letters_rapper)
+ letters.repaint("Basic Latin")
+
+ controls.circle.focus()
+// controls.shader.focus()
+
+ brush.bg = colors.red
+ brush.generate()
+ brush.build()
+
+ // controls.grid.use()
+ canvas.resize_rapper()
+}
+function bind () {
+ canvas.bind()
+ palette.bind()
+ letters.bind()
+ brush.bind()
+ controls.bind()
+ keys.bind()
+ clipboard.bind()
+
+ window.addEventListener('mouseup', function(e){
+ dragging = erasing = false
+ // if (current_filetool.name != 'shader' && current_filetool.name != 'load' && current_filetool.name != 'save' && is_desktop) {
+ // cursor_input.focus()
+ // }
+
+ var ae = document.activeElement
+
+ if (ae !== shader_textarea && ae !== import_textarea && ae !== username_input && ae !== upload_input) {
+ if (is_desktop) cursor_input.focus()
+ }
+
+ if (selecting) {
+ selection.up(e)
+ }
+ else if (transforming) {
+ transform.up(e)
+ }
+ })
+ window.addEventListener("touchend", function(){
+ if (current_tool.name === "text") {
+ if (is_desktop) cursor_input.focus()
+ }
+ dragging = false
+ })
+
+ window.addEventListener('mousedown', function(e){
+ // if (current_filetool.name != 'shader' && is_desktop) { cursor_input.focus() }
+ })
+
+ document.addEventListener('DOMContentLoaded', function(){
+ if (is_desktop) { cursor_input.focus() }
+ document.body.classList.remove('loading')
+ })
+
+ window.onbeforeunload = function() {
+ // if (changed && !in_iframe()) return "You have edited this drawing."
+ }
+
+ function in_iframe () {
+ try {
+ return window.self !== window.top;
+ } catch (e) {
+ return true;
+ }
+ }
+}
+
+init()
diff --git a/js/blit.js b/js/blit.js
new file mode 100644
index 0000000..2dce3e5
--- /dev/null
+++ b/js/blit.js
@@ -0,0 +1,105 @@
+var blit = (function(){
+ var blit = {}
+ blit.and = blit.atop = function(A, B, x, y){
+ x = x || 0 ; y = y || 0
+ B.forEach(function(lex, u, v){
+ var cell = A.getCell(u+x, v+y)
+ if (cell && lex.opacity > 0) {
+ cell.assign(lex)
+ }
+ })
+ }
+ blit.or = blit.under = function(A, B, x, y){
+ x = x || 0 ; y = y || 0
+ B.forEach(function(lex, u, v){
+ var cell = A.getCell(u+x, v+y)
+ if (cell && cell.opacity == 0) {
+ cell.assign(lex)
+ }
+ })
+ }
+ // copy the region of A beginning at x,y into B
+ blit.copy_from = function(A, B, x, y){
+ x = x || 0 ; y = y || 0
+ B.forEach(function(lex, u, v){
+ var cell = A.getCell(u+x, v+y)
+ if (cell) {
+ lex.assign(cell)
+ }
+ })
+ }
+ blit.copy_toroidal_from = function(A, B, x, y){
+ x = x || 0 ; y = y || 0
+ B.forEach(function(lex, u, v){
+ var cell = A.get(u+x, v+y)
+ if (cell) {
+ lex.assign(cell)
+ }
+ })
+ }
+ blit.copy_to = function(A, B, x, y){
+ x = x || 0 ; y = y || 0
+ B.forEach(function(lex, u, v){
+ var cell = A.getCell(u+x, v+y)
+ if (cell) {
+ cell.assign(lex)
+ }
+ })
+ }
+ blit.invert = function(A, B, x, y){
+ x = x || 0 ; y = y || 0
+ B.forEach(function(lex, u, v){
+ var cell = A.getCell(u+x, v+y)
+ if (cell && lex.opacity > 0) {
+ cell.fg = get_inverse(cell.fg)
+ cell.bg = get_inverse(cell.bg)
+ }
+ })
+ }
+ var distance_rect = function(x, y, ratio){
+ return Math.sqrt((Math.pow(y * ratio, 2)) + Math.pow(x, 2))
+ }
+ var distance_square = function(x, y, ratio){
+ return Math.sqrt((Math.pow(y * ratio, 2)) + Math.pow(x * ratio, 2))
+ }
+ blit.circle = function(A, lex){
+ var hw = brush.w/2, hh = brush.h/2
+ var ratio, distance
+
+ if (brush.w === brush.h){
+ distance = distance_square
+ ratio = hw / hh * (brush.w === 3 || brush.w === 5 ? 1.2 : 1.05)
+ } else {
+ distance = distance_rect
+ ratio = hw / hh
+ }
+
+ A.forEach(function(lex,x,y) {
+ if (distance(x - hw + 0.5, y - hh + 0.5, ratio) > hw){
+ lex.clear()
+ }
+ })
+ }
+ blit.cross = function(A, lex){
+ A.forEach(function(lex,x,y) {
+ if ((x+y)%2) {
+ lex.clear()
+ }
+ })
+ }
+ blit.inverted_cross = function(A, lex){
+ // 1x1 brush should still draw something
+ if (A.w == 1 && A.h == 1) {
+ return
+ }
+ A.forEach(function(lex,x,y) {
+ if (!((x+y)%2)) {
+ lex.clear()
+ }
+ })
+ }
+ blit.square = function(A, lex){
+ // i.e. no transparency
+ }
+ return blit
+})()
diff --git a/js/clipboard.js b/js/clipboard.js
new file mode 100644
index 0000000..f636af9
--- /dev/null
+++ b/js/clipboard.js
@@ -0,0 +1,330 @@
+var clipboard = (function () {
+
+ var exports = {
+ format: "irssi",
+ importing: false,
+ visible: false,
+ canvas: document.createElement("canvas"),
+ canvas_r: document.createElement("canvas"),
+
+ bind: function () {
+// import_ascii.addEventListener("change", exports.setFormat("ascii"))
+// import_irssi.addEventListener("change", exports.setFormat("irssi"))
+// import_mirc.addEventListener("change", exports.setFormat("mirc"))
+ import_button.addEventListener("click", exports.import_colorcode)
+ export_button.addEventListener("click", exports.export_data)
+ save_button.addEventListener("click", exports.save_png)
+ upload_button.addEventListener("click", exports.upload_png)
+ import_textarea.addEventListener("focus", exports.focus)
+ import_textarea.addEventListener("blur", exports.blur)
+ import_textarea.addEventListener('paste', exports.paste)
+// import_irssi.setAttribute("checked", true)
+ },
+ setFormat: function (name) {
+ return function () {
+ clipboard.format = name
+ if (! clipboard.importing) { clipboard.export_data() }
+ }
+ },
+ show: function () { import_rapper.style.display = "block"; clipboard.visible = true; changed = false },
+ hide: function () { import_rapper.style.display = "none"; clipboard.visible = false },
+ focus: function () {
+ if (! clipboard.importing) {
+ import_textarea.focus()
+ import_textarea.select()
+ }
+ },
+ blur: function () {
+ },
+
+ import_mode: function () {
+ focus()
+ clipboard.importing = true
+ gallery_rapper.style.display = 'none'
+ format_el.style.display = 'none'
+ cutoff_warning_el.style.display = 'none'
+ import_buttons.style.display = "inline"
+ import_textarea.value = ""
+ },
+ export_mode: function () {
+ focus()
+ clipboard.importing = false
+ import_buttons.style.display = "none"
+ format_el.style.display = 'inline'
+ cutoff_warning_el.style.display = 'none'
+ gallery_rapper.style.display = 'inline'
+ clipboard.export_data()
+ },
+
+ paste: function (e) {
+ e.preventDefault()
+ // images will come through as files
+ var types = toArray(e.clipboardData.types)
+ import_textarea.value = ""
+ types.forEach(function(type, i){
+ console.log(type)
+ // this can be text/plain or text/html..
+ if (type.match('text/plain')) {
+ import_textarea.value = e.clipboardData.getData(type)
+ }
+ else {
+ console.error("unknown type!", item.type)
+ }
+ })
+ },
+
+ import_colorcode: function (data, no_undo) {
+ if (data && data.preventDefault) {
+ data = import_textarea.value
+ }
+ else {
+ data = data || import_textarea.value
+ }
+
+ var irssi_style_regex = /^\s*\/exec -out printf ("%b" )?"/;
+
+ // turn irssi style into mirc style
+ if (data.match(irssi_style_regex)){
+ data = data.replace(/\\x03/gm, '\x03')
+ .replace(/(\\x..)+/gm, unicode.unescapeFromEscapedBytes)
+ .replace(/\\x5C/g, '\\')
+ .replace(/\\n/gm, '\n')
+ .replace(/\\`/gm, '`')
+ .replace(/\\"/gm, '"')
+ .replace(/\\\$/gm, '$')
+ .replace(irssi_style_regex, '')
+ .replace(/"\s*$/, '')
+ }
+
+ // not a colorcode
+ if (!data.match(/\x03/))
+ return exports.import_text();
+
+ var json = colorcode.to_json(data, {fg:0, bg:1})
+
+ if (!no_undo) undo.new()
+ if (!no_undo) undo.save_rect(0,0, canvas.w, canvas.h)
+ if (json.w !== canvas.w || json.h !== canvas.h){
+ if (!no_undo) undo.save_size(canvas.w, canvas.h)
+ canvas.resize(json.w, json.h, true)
+ }
+ canvas.clear()
+
+ for (var y = 0, line; line = json.lines[y]; y++){
+ var row = canvas.aa[y]
+ for (var x = 0, char; char = line[x]; x++){
+ var lex = row[x]
+ lex.char = String.fromCharCode(char.value)
+ lex.fg = char.fg
+ lex.bg = char.bg
+ lex.opacity = 1
+ lex.build()
+ }
+ }
+
+ current_filetool && current_filetool.blur()
+ },
+
+ import_text: function () {
+ var data = import_textarea.value
+ var lines = data.split("\n")
+ var width = lines.reduce(function(a,b){ console.log(a,b); return Math.max(a, b.length) }, 0)
+ var height = lines.length
+ if (width > canvas.max) {
+ return alert("input too wide")
+ }
+ if (height > canvas.max) {
+ return alert("input too tall")
+ }
+ undo.new()
+ undo.save_rect(0,0, canvas.w, canvas.h)
+ canvas.clear()
+ lines.forEach(function(line, y){
+ var row = canvas.aa[y]
+ if (! row) return
+ for (var x = 0; x < line.length; x++) {
+ var lex = row[x]
+ if (! lex) return
+ lex.char = line[x]
+ lex.fg = brush.bg
+ lex.opacity = 1
+ lex.build()
+ }
+ })
+ // TODO: some notion of a "selected" region which cuts/clones the underlying region
+
+// var pasted_region = new Matrix (width, height, function(x,y){
+// var lex = new Lex (x,y)
+// lex.char = lines[y][x] || " "
+// lex.build()
+// return lex
+// })
+ },
+ export_data: function () {
+ var output
+ // switch (clipboard.format) {
+ switch (controls.save_format.value) {
+ case 'ascii':
+ output = canvas.ascii()
+ break
+ case 'mirc':
+ output = canvas.mirc({cutoff: 400})
+ break
+ case 'irssi':
+ output = canvas.irssi({cutoff: 400})
+ break
+ case 'ansi':
+ output = canvas.ansi()
+ break
+ }
+ if (output.cutoff){
+ cutoff_warning_el.style.display = 'block'
+ } else {
+ cutoff_warning_el.style.display = 'none'
+ }
+ import_textarea.value = output
+ clipboard.focus()
+ return output
+ },
+
+ rotate_canvas: function(){
+ var cr = clipboard.canvas_r, c = clipboard.canvas
+ cr.width = c.height
+ cr.height = c.width
+ var ctx = cr.getContext('2d')
+ ctx.resetTransform()
+ ctx.translate(0, cr.height)
+ ctx.rotate(-Math.PI / 2)
+ ctx.drawImage(c, 0, 0)
+ return cr
+ },
+
+ export_canvas: function (done_fn) {
+ var opts = {
+ palette: 'mirc',
+ font: canvas.pixels ? 'fixedsys_8x8' : 'fixedsys_8x15',
+ fg: 0,
+ bg: 1,
+ canvas: clipboard.canvas
+ }
+ opts.done = function(){
+ var c = canvas.rotated ? clipboard.rotate_canvas() : clipboard.canvas
+ if (done_fn) done_fn(c)
+ }
+
+ var start = Date.now();
+ colorcode.to_canvas(canvas.mirc(), opts)
+ var total = Date.now() - start;
+ console.log("took " + total)
+ },
+
+ filename: function () {
+ return [ +new Date, "ascii", user.username ].join("-")
+ },
+
+ save_png: function () {
+ var save_fn = function(canvas_out){
+ var filename = clipboard.filename() + ".png"
+ var blob = PNG.canvas_to_blob_with_colorcode(canvas_out, canvas.mirc())
+ saveAs(blob, filename);
+ }
+ clipboard.export_canvas(save_fn)
+ },
+
+ upload_png: function () {
+ var upload_fn = function(canvas_out){
+ var blob = PNG.canvas_to_blob_with_colorcode(canvas_out, canvas.mirc())
+ var filename = clipboard.filename()
+ var tag = 'ascii'
+ upload(blob, filename, tag, canvas.mirc())
+ }
+ clipboard.export_canvas(upload_fn)
+ }
+
+ }
+
+ // http...?a=1&b=2&b=3 -> {a: '1', b: ['2', '3']}
+ function parse_url_search_params(url){
+ var params = {}
+ url = url.split('?')
+ if (url.length < 2) return params
+
+ var search = url[1].split('&')
+ for (var i = 0, pair; pair = search[i]; i++){
+ pair = pair.split('=')
+ if (pair.length < 2) continue
+ var key = pair[0]
+ var val = pair[1]
+ if (key in params){
+ if (typeof params[key] === 'string'){
+ params[key] = [params[key], val]
+ }
+ else params[key].push(val)
+ }
+ else params[key] = val
+ }
+ return params
+ }
+
+ function get_filetype(txt){
+ txt = txt.split('.')
+ return txt[txt.length - 1].toLowerCase()
+ }
+
+ function fetch_url(url, f, type){
+ type = type || 'arraybuffer'
+ url = "/cgi-bin/proxy?" + url
+ //url = "http://198.199.72.134/cors/" + url
+ var xhr = new XMLHttpRequest()
+ xhr.open('GET', url, true)
+ xhr.responseType = type
+ xhr.addEventListener('load', function(){ f(xhr.response) })
+ xhr.send()
+ }
+
+ function load_text(txt){
+ clipboard.import_colorcode(txt, true)
+ }
+
+ function load_png(buf){
+ var chunks = PNG.decode(buf)
+ if (!chunks) return
+ var itxt_chunks = []
+ for (var i=0, c; c=chunks[i]; i++){
+ if (c.type !== 'iTXt') continue
+ var itxt = PNG.decode_itxt_chunk(c)
+ if (!itxt.keyword || itxt.keyword !== 'colorcode') continue
+ clipboard.import_colorcode(itxt.data, true)
+ }
+ }
+
+ function sally_url_convert(url){
+ var png_regex = /^https?:\/\/jollo\.org\/den\/sallies\/([0-9]+)\/([^.]+)\.png$/
+ var matches = url.match(png_regex)
+ if (!matches) return url
+ return 'http://jollo.org/den/sallies/' + matches[1] + '/raw-' + matches[2] + '?.txt'
+ // txt suffix to force asdf proxy
+ }
+
+ exports.load_from_location = function(){
+ var params = parse_url_search_params(window.location + '')
+ if (!params.url) return
+ var url = params.url
+ url = sally_url_convert(url)
+ var type = get_filetype(url)
+ switch (type){
+ case 'txt':
+ fetch_url(url, load_text, 'text')
+ break
+ case 'png':
+ fetch_url(url, load_png)
+ break
+ }
+
+ }
+
+ return exports
+
+})()
+
+
diff --git a/js/color.js b/js/color.js
new file mode 100644
index 0000000..c518327
--- /dev/null
+++ b/js/color.js
@@ -0,0 +1,106 @@
+
+var fillColor = 1 // black
+
+var color_names = ("white black dark-blue green red dark-red purple orange " +
+ "yellow lime teal cyan blue magenta dark-gray light-gray").split(" ");
+
+var all_color_hue_order = "dark-red red orange yellow lime green teal cyan blue dark-blue purple magenta black dark-gray light-gray white".split(" ");
+var all_color_inv_order = "cyan teal blue dark-blue purple magenta dark-red red orange yellow lime green white light-gray dark-gray black".split(" ");
+var color_hue_order = "dark-red red orange yellow lime cyan teal blue dark-blue purple magenta".split(" ");
+var color_inv_order = "cyan teal blue dark-blue purple magenta dark-red red orange yellow lime green".split(" ");
+var gray_names = ("black dark-gray light-gray white").split(" ")
+
+var fire_names = ("black dark-red red orange yellow white cyan").split(" ")
+var red_names = ("black dark-red red").split(" ")
+var yellow_names = ("black orange yellow white").split(" ")
+var green_names = ("teal green lime").split(" ")
+var blue_names = ("black dark-blue blue").split(" ")
+var purple_names = ("dark-blue purple magenta red").split(" ")
+var dark_gray_names = ("black dark-blue teal dark-gray light-gray white").split(" ")
+var color_alphabet = "abcdefghijklmnop";
+var colors = {}
+color_names.forEach(function(name, i){
+ colors[name.replace("-", "")] = i
+ colors[name] = i
+})
+colors.brown = 5
+
+function get_inverse (n) { return colors[all_color_inv_order.indexOf(color_names[n])] }
+
+function mirc_color (n) { return mod(n, 16)|0 }
+function mirc_color_reverse (n) { return mod(-(n+1), 16)|0 }
+function all_hue (n) { return colors[all_color_hue_order[mod(n, 16)|0]] }
+function all_inv_hue (n) { return colors[all_color_inv_order[mod(n, 16)|0]] }
+function hue (n) { return colors[color_hue_order[mod(n, 11)|0]] }
+function rand_hue () { return colors[color_hue_order[randint(11)]] }
+function rand_gray () { return colors[gray_names[randint(4)]] }
+function inv_hue (n) { return colors[color_inv_order[mod(n, 11)|0]] }
+function gray (n) { return colors[gray_names[mod(n, 4)|0]] }
+function fire (n) { return colors[fire_names[mod(n, 7)|0]] }
+function red (n) { return colors[red_names[mod(n, 3)|0]] }
+function yellow (n) { return colors[yellow_names[mod(n, 4)|0]] }
+function green (n) { return colors[green_names[mod(n, 3)|0]] }
+function blue (n) { return colors[blue_names[mod(n, 3)|0]] }
+function purple (n) { return colors[purple_names[mod(n, 4)|0]] }
+function dark_gray (n) { return colors[dark_gray_names[mod(n, 4)|0]] }
+
+var css_lookup = {
+ 'rgb(255, 255, 255)': 'A',
+ 'rgb(0, 0, 0)': 'B',
+ 'rgb(0, 0, 127)': 'C',
+ 'rgb(0, 147, 0)': 'D',
+ 'red': 'E',
+ 'rgb(127, 0, 0)': 'F',
+ 'rgb(156, 0, 156)': 'G',
+ 'rgb(252, 127, 0)': 'H',
+ 'rgb(255, 255, 0)': 'I',
+ 'rgb(0, 252, 0)': 'J',
+ 'rgb(0, 147, 147)': 'K',
+ 'rgb(0, 255, 255)': 'L',
+ 'rgb(0, 0, 252)': 'M',
+ 'rgb(255, 0, 255)': 'N',
+ 'rgb(127, 127, 127)': 'O',
+ 'rgb(210, 210, 210)': 'P',
+};
+var css_reverse_lookup = {}
+Object.keys(css_lookup).forEach(function(color){
+ css_reverse_lookup[ css_lookup[color].charCodeAt(0) - 65 ] = color
+})
+
+var ansi_fg = [
+ 97, // white
+ 30, // black
+ 34, // dark blue
+ 32, // green
+ 91, // light red
+ 31, // dark red
+ 35, // purple
+ 33, // "dark yellow" (orange?)
+ 93, // "light yellow"
+ 92, // light green
+ 36, // cyan (teal?)
+ 96, // light cyan
+ 94, // light blue
+ 95, // light magenta
+ 90, // dark gray
+ 37, // light gray
+]
+
+var ansi_bg = [
+ 107, // white
+ 40, // black
+ 44, // dark blue
+ 42, // green
+ 101, // light red
+ 41, // dark red
+ 45, // purple
+ 43, // yellow (orange)
+ 103, // light yellow
+ 102, // light green
+ 46, // cyan (teal?)
+ 106, // light cyan
+ 104, // light blue
+ 105, // light magenta
+ 100, // dark gray
+ 47, // light gray
+]
diff --git a/js/dither.js b/js/dither.js
new file mode 100644
index 0000000..f5c86c9
--- /dev/null
+++ b/js/dither.js
@@ -0,0 +1,10 @@
+var dither = {
+ aa: '▓▒░ ',
+ a: '▓',
+ b: '▒',
+ c: '░',
+ d: ' ',
+ p: function(n){
+ return dither.aa[Math.floor(Math.abs(n) % 4)]
+ }
+}
diff --git a/js/draw.js b/js/draw.js
new file mode 100644
index 0000000..9b0e3b9
--- /dev/null
+++ b/js/draw.js
@@ -0,0 +1,221 @@
+
+var draw = (function(){
+
+ var last_point = [0,0]
+
+ function down (e, lex, point) {
+ var w = canvas.w, h = canvas.h
+ erasing = (e.which == "3" || e.ctrlKey)
+ changed = true
+ if (e.shiftKey) {
+ line (lex, last_point, point, erasing)
+ if (mirror_x) {
+ line(lex, [w-last_point[0], last_point[1]], [w-point[0], point[1]], erasing)
+ }
+ if (mirror_y) {
+ line(lex, [last_point[0], h-last_point[1]], [point[0], h-point[1]], erasing)
+ }
+ if (mirror_x && mirror_y) {
+ line(lex, [w-last_point[0], h-last_point[1]], [w-point[0], h-point[1]], erasing)
+ }
+ }
+ else {
+ stamp (canvas, brush, point[0], point[1], erasing)
+ if (mirror_x) {
+ stamp (canvas, brush, w-point[0], point[1], erasing)
+ }
+ if (mirror_y) {
+ stamp (canvas, brush, point[0], h-point[1], erasing)
+ }
+ if (mirror_x && mirror_y) {
+ stamp (canvas, brush, w-point[0], h-point[1], erasing)
+ }
+ }
+ last_point[0] = point[0]
+ last_point[1] = point[1]
+ }
+
+ function set_last_point (e, point) {
+ last_point[0] = point[0]
+ last_point[1] = point[1]
+ }
+
+ function move (e, lex, point) {
+ var w = canvas.w, h = canvas.h
+ line(lex, last_point, point, erasing)
+ if (mirror_x) {
+ line(lex, [w-last_point[0], last_point[1]], [w-point[0], point[1]], erasing)
+ }
+ if (mirror_y) {
+ line(lex, [last_point[0], h-last_point[1]], [point[0], h-point[1]], erasing)
+ }
+ if (mirror_x && mirror_y) {
+ line(lex, [w-last_point[0], h-last_point[1]], [w-point[0], h-point[1]], erasing)
+ }
+
+ last_point[0] = point[0]
+ last_point[1] = point[1]
+ }
+
+ function move_toroidal (e, lex, point) {
+ var w = canvas.w, h = canvas.h
+ var src_x_quantile = quantile( last_point[0], w )
+ var src_y_quantile = quantile( last_point[1], h )
+ var dst_x_quantile = quantile( point[0], w )
+ var dst_y_quantile = quantile( point[1], h )
+ var src_x_mod = mod( last_point[0], w )
+ var src_y_mod = mod( last_point[1], h )
+ var dst_x_mod = mod( point[0], w )
+ var dst_y_mod = mod( point[1], h )
+ // if we've moved across the edge of the board, draw two lines
+ if (src_x_quantile != dst_x_quantile || src_y_quantile != dst_y_quantile) {
+ var xa, ya
+ if (src_x_quantile < dst_x_quantile) {
+ xa = [
+ [src_x_mod, dst_x_mod + w],
+ [src_x_mod-w, dst_x_mod],
+ ]
+ }
+ else if (src_x_quantile == dst_x_quantile) {
+ xa = [
+ [src_x_mod, dst_x_mod],
+ [src_x_mod, dst_x_mod],
+ ]
+ }
+ else {
+ xa = [
+ [src_x_mod, dst_x_mod-w],
+ [src_x_mod+w, dst_x_mod],
+ ]
+ }
+
+ if (src_y_quantile < dst_y_quantile) {
+ ya = [
+ [src_y_mod, dst_y_mod + h],
+ [src_y_mod-h, dst_y_mod],
+ ]
+ }
+ else if (src_y_quantile == dst_y_quantile) {
+ ya = [
+ [src_y_mod, dst_y_mod],
+ [src_y_mod, dst_y_mod],
+ ]
+ }
+ else {
+ ya = [
+ [src_y_mod, dst_y_mod-h],
+ [src_y_mod+h, dst_y_mod],
+ ]
+ }
+ line(lex, [ xa[0][0], ya[0][0] ], [ xa[0][1], ya[0][1] ], erasing)
+ line(lex, [ xa[1][0], ya[1][0] ], [ xa[1][1], ya[1][1] ], erasing)
+ }
+ else {
+ var x_a = mod( last_point[0], w )
+ var y_a = mod( last_point[1], h )
+ var x_b = mod( point[0], w )
+ var y_b = mod( point[1], h )
+ var last_point_mod = [x_b, y_b], point_mod = [x_a, y_a]
+ line(lex, last_point_mod, point_mod, erasing)
+ // if (mirror_x) {
+ // line(lex, [w-last_point_mod[0], last_point_mod[1]], [w-point_mod[0], point_mod[1]], erasing)
+ // }
+ // if (mirror_y) {
+ // line(lex, [last_point_mod[0], h-last_point_mod[1]], [point_mod[0], h-point_mod[1]], erasing)
+ // }
+ }
+ last_point[0] = point[0]
+ last_point[1] = point[1]
+ // y = point.y
+ }
+
+ function point (lex, x, y, erasing) {
+ stamp (canvas, brush, x, y, erasing)
+ }
+
+ function line (lex, a, b, erasing) {
+ var len = dist(a[0], a[1], b[0], b[1])
+ var bw = 1
+ var x, y, i;
+ for (var i = 0; i <= len; i += bw) {
+ x = lerp(i / len, a[0], b[0])
+ y = lerp(i / len, a[1], b[1])
+ stamp (canvas, brush, x, y, erasing)
+ }
+ }
+
+ function stamp (canvas, brush, x, y, erasing) {
+ var hh = brush.w/2|0
+ brush.forEach(function(lex, s, t){
+ s = round( s + x-hh )
+ t = round( t + y-hh )
+ if (s >= 0 && s < canvas.w && t >= 0 && t < canvas.h) {
+ if (lex.opacity === 0 && lex.char === ' ') return;
+ var aa = canvas.aa[t][s]
+ undo.save_lex(s, t, aa)
+ if (erasing) {
+ aa.erase(lex)
+ }
+ else {
+ aa.stamp(lex, brush)
+ }
+ }
+ })
+ }
+
+ function fill (lex, x, y) {
+ var q = [ [x,y] ]
+ var aa = canvas.aa
+ var target = aa[y][x].clone()
+ var n, w = 0, e = 0, j = 0
+ var kk = 0
+ // gets into a weird infinite loop if we don't break here.. :\
+ if (target.eq(lex)) { return }
+ LOOP: while (q.length) {
+ n = q.shift()
+ if (aa[n[1]][n[0]].ne(target)) {
+ continue LOOP
+ }
+ w = e = n[0]
+ j = n[1]
+ WEST: while (w > 0) {
+ if (aa[j][w-1].eq(target)) {
+ w = w-1
+ }
+ else {
+ break WEST
+ }
+ }
+ EAST: while (e < canvas.w-1) {
+ if (aa[j][e+1].eq(target)) {
+ e = e+1
+ }
+ else {
+ break EAST
+ }
+ }
+ for (var i = w; i <= e; i++) {
+ undo.save_lex(i, j, aa[j][i])
+ aa[j][i].assign(lex)
+ if (j > 0 && aa[j-1][i].eq(target)) {
+ q.push([ i, j-1 ])
+ }
+ if (j < canvas.h-1 && aa[j+1][i].eq(target)) {
+ q.push([ i, j+1 ])
+ }
+ }
+ }
+ }
+
+ var draw = {}
+ draw.down = down
+ draw.set_last_point = set_last_point
+ draw.move = move
+ draw.move_toroidal = move_toroidal
+ draw.stamp = stamp
+ draw.line = line
+ draw.point = point
+ draw.fill = fill
+ return draw
+
+})()
diff --git a/js/lex.js b/js/lex.js
new file mode 100644
index 0000000..b2f54dc
--- /dev/null
+++ b/js/lex.js
@@ -0,0 +1,138 @@
+function Lex (x,y) {
+ if (typeof x == "number") {
+ this.y = y
+ this.x = x
+ this.span = document.createElement("span")
+ }
+ else {
+ this.span = x
+ }
+ this.fg = colors.white
+ this.bg = colors.black
+ this.char = " "
+ this.opacity = 1
+ this.focused = false
+}
+Lex.prototype.build = function(){
+ if (isNaN(this.bg) || this.bg == Infinity || this.bg == -Infinity) this.bg = colors.black
+ if (isNaN(this.fg) || this.fg == Infinity || this.fg == -Infinity) this.fg = colors.black
+ this.span.className = this.css()
+ this.span.innerHTML = this.html()
+}
+Lex.prototype.css = function(){
+ return (
+ this.focused ?
+ "focused " : ""
+ ) + (
+ this.opacity === 0 ?
+ "transparent f" + color_alphabet[modi(this.fg,16)] :
+ "f" + color_alphabet[modi(this.fg,16)] + " b" + color_alphabet[modi(this.bg,16)]
+ )
+}
+Lex.prototype.html = function(){
+ return this.char == " " ? " " : this.char || " "
+}
+Lex.prototype.read = function(){
+ this.char = this.span.innerHTML
+ return this.char
+}
+Lex.prototype.ascii = function(){
+ return this.char || " "
+}
+Lex.prototype.sanitize = function(){
+ switch (this.char) {
+// case "%": return "%"
+ case undefined:
+ case "": return " "
+ default: return this.char
+ }
+}
+var fgOnly = false
+Lex.prototype.mirc = function(){
+ var char = this.char || " "
+ if (fgOnly) {
+ return "\x03" + (this.fg&15) + char
+ }
+ if ((this.bg&15) < 10 && ! isNaN(parseInt(char))) {
+ return "\x03" + (this.fg&15) + ",0" + (this.bg&15) + char
+ }
+ else {
+ return "\x03" + (this.fg&15) + "," + (this.bg&15) + char
+ }
+}
+Lex.prototype.ansi = function(){
+ var fg = ansi_fg[ this.fg&15 ]
+ var bg = ansi_bg[ this.bg&15 ]
+ var c = this.sanitize()
+ if (c == "\\") c = "\\\\"
+ if (c == '"') c = '\\"'
+ return "\\e[" + fg + ";" + bg + "m" + c
+}
+Lex.prototype.assign = function (lex){
+ this.fg = lex.fg
+ this.bg = lex.bg
+ this.char = lex.char
+ this.opacity = lex.opacity
+ this.build()
+}
+Lex.prototype.stamp = function (lex, brush){
+ if (brush.draw_fg) this.fg = lex.fg
+ if (brush.draw_bg && lex.opacity > 0) this.bg = lex.bg
+ if (brush.draw_char) this.char = lex.char
+ this.opacity = 1
+ this.build()
+}
+Lex.prototype.clone = function () {
+ var lex = new Lex (0,0)
+ lex.assign(this)
+ return lex
+}
+Lex.prototype.erase = function (){
+ this.fg = fillColor
+ this.bg = fillColor
+ this.char = " "
+ this.opacity = 1
+ this.build()
+}
+Lex.prototype.eq = function(lex){
+ return lex && this.fg == lex.fg && this.bg == lex.bg && this.char == lex.char
+}
+Lex.prototype.eqColor = function(lex){
+ return lex && this.fg == lex.fg && this.bg == lex.bg
+}
+Lex.prototype.ne = function(lex){
+ return ! this.eq(lex)
+}
+Lex.prototype.clear = function(){
+ this.bg = colors.black
+ this.fg = 0
+ this.char = " "
+ this.opacity = 0
+ this.build()
+}
+Lex.prototype.isClear = function(){
+ return this.bg == 1 && this.fg == 0 && this.char == " "
+}
+Lex.prototype.focus = function(){
+ if (focused) focused.blur()
+ this.span.classList.add('focused')
+ this.focused = true
+ focused = this
+}
+Lex.prototype.blur = function(){
+ focused = null
+ this.span && this.span.classList.remove('focused')
+ this.focused = false
+ this.onBlur && this.onBlur()
+}
+Lex.prototype.demolish = function(){
+ if (this.span.parentNode) { this.span.parentNode.removeChild(this.span) }
+ this.span = null
+}
+Lex.prototype.key = function(char, keyCode) {
+ if (! char) { return }
+ this.char = char
+ this.fg = brush.fg
+ this.build()
+ return true
+}
diff --git a/js/matrix.js b/js/matrix.js
new file mode 100644
index 0000000..dbc76c7
--- /dev/null
+++ b/js/matrix.js
@@ -0,0 +1,321 @@
+function Matrix (w,h,f){
+ this.x = 0
+ this.y = 0
+ this.w = w
+ this.h = h
+ this.f = f
+ this.focus_x = 0
+ this.focus_y = 0
+ this.initialize()
+}
+Matrix.prototype.initialize = function(f){
+ var w = this.w || 1, h = this.h || 1, f = f || this.f
+ var aa = new Array (h)
+ for (var y = 0; y < h; y++) {
+ aa[y] = new Array (w)
+ for (var x = 0; x < w; x++) {
+ aa[y][x] = f(x,y)
+ }
+ }
+ this.aa = aa
+}
+Matrix.prototype.rebuild = function (){
+ this.demolish()
+ this.initialize()
+ this.append()
+ this.bind()
+ this.generate && this.generate()
+ this.focus_clamp()
+ check_if_lost_focus()
+}
+Matrix.prototype.clone = function () {
+ var base = this
+ var clone = new Matrix(this.w, this.h, function(x,y){
+ return base.getCell(x,y).clone()
+ })
+ clone.f = this.f
+ return clone
+}
+Matrix.prototype.assign = function (mat) {
+ var base = this
+ this.demolish()
+ this.w = mat.w
+ this.h = mat.h
+// this.f = function(){}
+ this.initialize(function(x,y){
+ var el = mat.getCell(x,y).clone()
+ el.build()
+ return el
+ })
+ this.append()
+ this.bind()
+ check_if_lost_focus()
+ return this
+}
+
+Matrix.prototype.bind = function () {}
+Matrix.prototype.demolish = function (){
+ this.forEach(function(lex){
+ lex.demolish()
+ })
+ while (this.rapper && this.rapper.firstChild) {
+ this.rapper.removeChild(this.rapper.firstChild);
+ }
+ this.aa.forEach(function(row){
+ row.length = 0
+ })
+ this.aa.length = 0
+}
+Matrix.prototype.forEach = function(f){
+ this.aa.forEach(function(row, y){
+ row.forEach(function(lex, x){
+ f(lex, x, y)
+ })
+ })
+}
+Matrix.prototype.focus_clamp = function(){
+ this.focus_x = clamp(this.focus_x, 0, this.w - 1)
+ this.focus_y = clamp(this.focus_y, 0, this.h - 1)
+}
+Matrix.prototype.focus_add = function(x, y){
+ this.focus(this.focus_x + x, this.focus_y + y)
+}
+Matrix.prototype.focus = function(x, y){
+ if (x === undefined) x = this.focus_x
+ if (y === undefined) y = this.focus_y
+ x = mod(x, this.w)
+ y = mod(y, this.h)
+ this.focus_x = x
+ this.focus_y = y
+
+ //focused_input = this
+ this.aa[y][x].focus()
+}
+Matrix.prototype.focusLex = function(y,x){
+ if (x < 0) {
+ y -= 1
+ }
+ if (x > this.aa[0].length) {
+ y += 1
+ }
+ this.aa[mod(y,this.h)][mod(x,this.w)].focus()
+}
+Matrix.prototype.clear = function(){
+ this.forEach(function(lex,x,y){ lex.clear() })
+}
+Matrix.prototype.erase = function(){
+ this.forEach(function(lex,x,y){ lex.erase() })
+}
+Matrix.prototype.fill = function(lex){
+ this.fg = lex.fg
+ this.bg = lex.bg
+ this.char = lex.char
+ this.opacity = lex.opacity
+ this.forEach(function(el,x,y){
+ el.assign(lex)
+ el.build()
+ })
+}
+
+Matrix.prototype.build = function(){
+ this.forEach(function(lex,x,y){
+ lex.build()
+ })
+}
+Matrix.prototype.append = function(rapper){
+ rapper = this.rapper = rapper || this.rapper
+ if (! this.rapper) return
+ this.aa.forEach(function(row, y){
+ var div = document.createElement("div")
+ row.forEach(function(lex, x) {
+ div.appendChild(lex.span)
+ })
+ rapper.appendChild( div )
+ })
+}
+Matrix.prototype.region = function(w,h,x,y) {
+ w = w || 1
+ h = h || 1
+ x = x || 0
+ y = y || 0
+ var parent = this
+ var mat = new Matrix(w, h, function(x,y){
+ return parent.aa[y][x]
+ })
+ mat.f = this.f
+ return mat
+}
+Matrix.prototype.setCell = function(lex,x,y){
+ this.aa[y] && this.aa[y][x] && this.aa[y][x].assign(lex)
+}
+Matrix.prototype.getCell = function(x,y){
+ if (this.aa[y] && this.aa[y][x]) return this.aa[y][x]
+ else return null
+}
+Matrix.prototype.get = function(x,y){
+ y = floor(mod(y || 0, this.h))
+ x = floor(mod(x || 0, this.w))
+ if (this.aa[y] && this.aa[y][x]) return this.aa[y][x]
+ else return null
+}
+
+Matrix.prototype.resize = function(w,h){
+ w = w || canvas.w
+ h = h || canvas.h
+ var div, row, lex
+ var f = this.f, old_h = this.aa.length, old_w = this.aa[0].length
+ var rapper = this.rapper
+ w = max(w, 1)
+ h = max(h, 1)
+ if (h < old_h) {
+ for (var y = old_h; y > h; y--) {
+ row = this.aa.pop()
+ div = row[0].span.parentNode
+ row.forEach(function(lex, x){
+ lex.demolish()
+ })
+ div.parentNode.removeChild(div)
+ }
+ }
+ else if (h > old_h) {
+ for (var y = old_h; y < h; y++) {
+ div = document.createElement("div")
+ rapper.appendChild( div )
+ this.aa[y] = new Array (w)
+ for (var x = 0; x < w; x++) {
+ lex = this.aa[y][x] = f(x,y)
+ div.appendChild(lex.span)
+ }
+ }
+ }
+
+ if (w < old_w) {
+ this.aa.forEach(function(row, y){
+ while (row.length > w) {
+ lex = row.pop()
+ lex.demolish()
+ }
+ })
+ }
+ else if (w > old_w) {
+ this.aa.forEach(function(row, y){
+ div = row[0].span.parentNode
+ for (var x = row.length; x < w; x++) {
+ lex = row[x] = f(x,y)
+ div.appendChild(lex.span)
+ }
+ })
+ }
+
+ this.w = w
+ this.h = h
+ this.bind && this.bind()
+ this.focus_clamp()
+ if (this.rapper && this.rapper.parentNode != document.body) {
+ this.resize_rapper()
+ }
+}
+Matrix.prototype.resize_rapper = function(){
+ var cell = canvas.aa[0][0].span
+ var cw = cell.offsetWidth
+ var ch = cell.offsetHeight
+// if (canvas.grid) { ch++ }
+ var width = cw * this.aa[0].length
+ var height = ch * this.aa.length
+ if (canvas.grid) { width++; height++ }
+ if (this.rotated) {
+ this.rapper.parentNode.classList.add("rotated")
+ this.rapper.parentNode.style.height = (width) + "px"
+ this.rapper.parentNode.style.width = (height) + "px"
+ this.rapper.style.top = (width/2) + "px"
+ // this.rapper.style.left = ((canvas_rapper.offsetHeight+20)/2) + "px"
+ }
+ else {
+ this.rapper.parentNode.classList.remove("rotated")
+ this.rapper.parentNode.style.height = ""
+ this.rapper.style.width =
+ this.rapper.parentNode.style.width = (width) + "px"
+ this.rapper.style.top = ""
+ // canvas_rapper.style.left = "auto"
+ }
+}
+Matrix.prototype.ascii = function () {
+ var lines = this.aa.map(function(row, y){
+ var last, line = ""
+ row.forEach(function(lex, x) {
+ line += lex.ascii()
+ })
+ return line // .replace(/\s+$/,"")
+ })
+ var txt = lines.join("\n")
+ return txt
+}
+Matrix.prototype.ansi = function (opts) {
+ var lines = this.aa.map(function(row, y){
+ var last, line = ""
+ row.forEach(function(lex, x) {
+ if (lex.eqColor(last)) {
+ line += lex.sanitize()
+ }
+ else {
+ line += lex.ansi()
+ last = lex
+ }
+ })
+ return line
+ })
+ var txt = lines.filter(function(line){ return line.length > 0 }).join('\\e[0m\\n') + "\\e[0m"
+ return 'echo -e "' + txt + '"'
+}
+Matrix.prototype.mirc = function (opts) {
+ var cutoff = false
+ var lines = this.aa.map(function(row, y){
+ var last, line = ""
+ row.forEach(function(lex, x) {
+ if (lex.eqColor(last)) {
+ line += lex.sanitize()
+ }
+ else {
+ line += lex.mirc()
+ last = lex
+ }
+ })
+ if (opts && opts.cutoff && line.length > opts.cutoff) {
+ cutoff = true
+ return line.substr(0, opts.cutoff)
+ }
+ return line
+ })
+
+ var txt = lines.filter(function(line){ return line.length > 0 }).join('\n')
+
+ if (cutoff) {
+ txt = new String(txt)
+ txt.cutoff = true
+ }
+ return txt
+}
+Matrix.prototype.irssi = function(opts){
+ var mirc = this.mirc(opts)
+ var txt = mirc
+ // .replace(/\%/g, '%%')
+ .replace(/\\/g, '\\x5C')
+ .replace(/\"/g, '\\\"')
+ // .replace(/\'/g, '\\\'')
+ .replace(/\`/g, '\\\`')
+ .replace(/\$/g, '\\$')
+ // .replace(/\n\s+/g, '\n')
+ // .replace(/\s+$/g, '\n')
+ // .replace(/^\n+/, '')
+ .replace(/\n/g, '\\n')
+ .replace(/\x02/g, '\\x02')
+ .replace(/\x03/g, '\\x03')
+
+ txt = unicode.escapeToEscapedBytes(txt)
+ txt = '/exec -out printf "%b" "' + txt + '"\n'
+ if (mirc.cutoff){
+ txt = new String(txt)
+ txt.cutoff = true
+ }
+ return txt
+}
diff --git a/js/png.js b/js/png.js
new file mode 100644
index 0000000..e9b326e
--- /dev/null
+++ b/js/png.js
@@ -0,0 +1,226 @@
+var PNG = (function(){
+
+var crc32 = function(u8){
+ var table = new Uint32Array([
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+ 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+ 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+ 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+ 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+ 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+ 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+ 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+ 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+ 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+ 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+ 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+ 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+ 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+ 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+ 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+ 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+ 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+ 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+ 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+ 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+ 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+ ])
+
+ var crc = 0 ^ (-1)
+
+ for(var i = 0; i < u8.length; i++){
+ crc = (crc >>> 8) ^ table[(crc ^ u8[i]) & 0xFF]
+ }
+
+ //return (crc ^ (-1)) // signed
+ return (crc ^ (-1)) >>> 0
+}
+
+var signature = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10])
+var te, td
+
+// decodes chunks in png
+// see http://www.w3.org/TR/PNG/#5Chunk-layout
+// returns something like
+// [{length: Number,
+// type: String[4],
+// crc: Number,
+// data: Uint8Array[] // optional
+// }, ...]
+var decode = function(buf, err){
+ var u8a = new Uint8Array(buf)
+ var dv = new DataView(buf)
+ td = td || new TextDecoder('utf-8')
+ err = err || function(msg){ throw new Error(msg) }
+
+ var out = []
+ var pos = 0
+
+ if (u8a.length < signature.length) return err("not a valid png")
+ for (var i=0; i u8a.length) return err("unexpected end of file")
+ chunk.length = dv.getInt32(pos, false)
+ pos += 4
+
+ if (pos + 4 > u8a.length) return err("unexpected end of file")
+ chunk.type = td.decode(new DataView(buf, pos, 4))
+ pos += 4
+
+ if (chunk.length){
+ if (pos + chunk.length > u8a.length) return err('unexpected end of file')
+ chunk.data = new Uint8Array(buf, pos, chunk.length)
+ pos += chunk.length
+ }
+
+ if (pos + 4 > u8a.length) return err("unexpected end of file")
+ //chunk.crc = new Uint8Array(buf, pos, 4)
+ chunk.crc = dv.getUint32(pos, false)
+ pos += 4
+
+
+ out.push(chunk)
+ //done = true
+ //console.log(pos.length, u8a.length)
+ if (pos === u8a.length) done = true
+ }
+
+
+ return out
+}
+
+var encode = function(chunks){
+ te = te || new TextEncoder('utf-8')
+
+ var size = 8 // inital png signature
+ for (var i=0, c; c=chunks[i]; i++){
+ size += 4 // length
+ size += 4 // type
+ size += c.length // data
+ size += 4 // crc32
+ }
+
+ var buf = new ArrayBuffer(size)
+ var u8 = new Uint8Array(buf)
+ var dv = new DataView(buf)
+ var pos = 0
+
+ u8.set(signature, 0)
+ pos += 8
+
+ for (var i=0, c; c=chunks[i]; i++){
+ dv.setInt32(pos, c.length, false) // length
+ pos += 4
+ var chunk_type_u8 = te.encode(c.type) // type
+ u8.set(chunk_type_u8, pos)
+ pos += 4
+ if (c.length){
+ u8.set(c.data, pos) // data
+ pos += c.length
+ }
+ //u8.set(c.crc, pos) // crc32
+ dv.setUint32(pos, c.crc, false) // crc32
+ pos += 4
+ }
+
+ return u8
+}
+
+
+var make_itxt_chunk = function(keyword, txt){
+ te = te || new TextEncoder('utf-8')
+ var keyword_u8 = te.encode(keyword)
+ var txt_u8 = te.encode(txt)
+ var header_u8 = new Uint8Array(keyword_u8.length + 5)
+ header_u8.set(keyword_u8, 0)
+ // header has keyword, then a null byte and some additional fields
+ // see http://www.w3.org/TR/PNG/#11iTXt
+ var chunk = {type: 'iTXt'}
+ chunk.length = header_u8.length + txt_u8.length
+ var u8 = new Uint8Array(4 + chunk.length)
+ // put type and data on the same u8 array so we can calculate crc
+ u8.set(te.encode(chunk.type), 0)
+ u8.set(header_u8, 4)
+ u8.set(txt_u8, header_u8.length + 4)
+ chunk.crc = crc32(u8)
+ chunk.data = new Uint8Array(u8.buffer, 4)
+ return chunk
+}
+
+var read_cstring = function(u8, pos){
+ var str = ""
+ while (pos < u8.length){
+ if (u8[pos] === 0) return str
+ str += String.fromCharCode(u8[pos])
+ pos++
+ }
+ return str
+}
+
+var decode_itxt_chunk = function(chunk){
+ td = td || new TextDecoder('utf-8')
+ var data = {}
+ var pos = 0
+ data.keyword = read_cstring(chunk.data, 0)
+ pos += data.keyword.length + 1
+ data.compression = chunk.data[pos]
+ pos += 1
+ data.compression_method = chunk.data[pos]
+ pos += 1
+ data.language = read_cstring(chunk.data, pos)
+ pos += data.language.length + 1
+ data.translated_keyword = read_cstring(chunk.data, pos)
+ pos += data.translated_keyword.length + 1
+ var data_u8 = chunk.data.subarray(pos)
+ data.data = td.decode(data_u8)
+ return data
+}
+
+var canvas_to_blob_with_colorcode = function(canvas, cc){
+ var u8 = dataUriToUint8Array(canvas.toDataURL())
+ var chunks = decode(u8.buffer)
+ var itxt_chunk = make_itxt_chunk('colorcode', cc)
+ // assume we wanna insert the chunk very last, just in front of the end
+ chunks.splice(chunks.length - 1, 0, itxt_chunk)
+ var blob = new Blob([encode(chunks)], {type: 'image/png'})
+ return blob
+}
+
+var exports = {}
+exports.crc32 = crc32
+exports.decode = decode
+exports.encode = encode
+exports.make_itxt_chunk = make_itxt_chunk
+exports.decode_itxt_chunk = decode_itxt_chunk
+exports.canvas_to_blob_with_colorcode = canvas_to_blob_with_colorcode
+return exports
+
+})()
diff --git a/js/shader.js b/js/shader.js
new file mode 100644
index 0000000..f0ed548
--- /dev/null
+++ b/js/shader.js
@@ -0,0 +1,58 @@
+var shader = (function(){
+ var fn_str, fn, lex
+ var exports = {}
+ var animating = false
+
+ exports.init = function(){
+ lex = new Lex (0, 0)
+ exports.build(demo_shader.innerHTML)
+ }
+ exports.build = function (fn_str){
+ try {
+ new_fn = new Function('lex', 'x', 'y', 'w', 'h', 't', fn_str)
+ new_fn(lex, 0, 0, 1, 1, 0)
+ }
+ catch (e) {
+ throw 'Shader execution error'
+ }
+ exports.fn = fn = new_fn
+ return fn
+ }
+ exports.run = function(canvas){
+ var t = +new Date
+ shader.canvas = shader.canvas || canvas
+ var w = shader.canvas.w, h = shader.canvas.h
+ shader.canvas.forEach(function(lex, x, y){
+ fn(lex, x, y, w, h, t)
+ lex.build()
+ })
+ }
+ exports.toggle = function(state){
+ animating = typeof state == "boolean" ? state : ! animating
+ shader_fps_el.classList.toggle('hidden')
+ return animating
+ }
+ exports.pause = function(){
+ animating = false
+ shader_fps_el.classList.add('hidden')
+ shader.fps_time = 0
+ }
+ exports.play = function(){
+ animating = true
+ shader_fps_el.classList.remove('hidden')
+ }
+ exports.animate = function (t){
+ requestAnimationFrame(exports.animate)
+ if (! animating) { return }
+ if (shader.fps_time){
+ var ms = Date.now() - shader.fps_time
+ fps = 1000 / ms
+ shader_fps_el.innerHTML = (fps | 0) + ' fps'
+ }
+ shader.fps_time = Date.now()
+ exports.run(canvas)
+ }
+
+ return exports
+
+})()
diff --git a/js/tool.js b/js/tool.js
new file mode 100644
index 0000000..d0053a6
--- /dev/null
+++ b/js/tool.js
@@ -0,0 +1,170 @@
+var Tool = Model({
+ init: function (el) {
+ this.el = el
+ this.lex = new Lex (el)
+ this.name = el.innerHTML
+ },
+ bind: function(){
+ var tool = this
+ tool.el.addEventListener('mousedown', function(e){
+ tool.focus()
+ })
+ tool.el.addEventListener('contextmenu', function(e){
+ tool.context(e)
+ })
+ if (tool.memorable) {
+ // console.log(tool.name, localStorage.getItem("ascii.tools." + tool.name) )
+ tool.use( localStorage.getItem("ascii.tools." + tool.name) == "true" )
+ }
+ },
+ use: function(){},
+ context: function(e){},
+ done: function(){},
+ focus: function(){
+ // focused && focused.blur()
+ current_tool && current_tool.blur()
+ current_tool = this
+ this.el.classList.add('focused')
+ this.use()
+ if (this.name != 'shader' && is_desktop) { cursor_input.focus() }
+ },
+ blur: function(){
+ current_tool = null
+ this.el.classList.remove('focused')
+ this.done()
+ }
+})
+
+var FileTool = Tool.extend({
+ focus: function(){
+ if (current_filetool === this) {
+ this.blur()
+ return
+ }
+ current_filetool && current_filetool.blur()
+ current_filetool = this
+ this.el.classList.add('focused')
+ this.use()
+ if (this.name != 'shader' && is_desktop) { cursor_input.focus() }
+ },
+ blur: function(){
+ current_filetool = null
+ this.el.classList.remove('focused')
+ this.done()
+ }
+})
+
+var RadioItem = Tool.extend({
+ init: function(group, el){
+ this.group = group
+ this.el = el
+ },
+ focus: function(){
+ this.el.classList.add('focused')
+ },
+ blur: function(){
+ this.el.classList.remove('focused')
+ },
+ bind: function(){
+ var control = this
+ this.el.addEventListener('mousedown', function(){
+ control.group.use(control)
+ })
+ }
+})
+
+var RadioGroup = Tool.extend({
+ init: function(el){
+ this.el = el
+ this.controls = {}
+ var names = el.innerHTML.split(' ')
+ el.innerHTML = ''
+ var group = this
+ names.forEach(function(value){
+ var el = document.createElement('span')
+ el.classList.add('radio','tool')
+ var control = new RadioItem(group, el)
+ if (value.substr(0,1) === '*') {
+ control.value = value = value.substr(1)
+ group.use(control)
+ }
+ control.value = el.innerHTML = value
+ group.controls[value] = control
+ group.el.appendChild(el)
+ })
+ },
+ use: function(control){
+ if (typeof control === 'string') {
+ control = this.controls[control]
+ }
+ this.selected_control && this.selected_control.blur()
+ this.value = control.value
+ this.selected_control = control
+ control.focus()
+ control.use()
+ if (this.memorable){
+ localStorage.setItem("ascii.tools." + this.name, this.value)
+ }
+ },
+ bind: function(){
+ var tool = this
+ for (var n in this.controls){
+ this.controls[n].bind()
+ }
+ if (tool.memorable) {
+ var value = localStorage.getItem("ascii.tools." + tool.name)
+ if (value) tool.use(value)
+ }
+ }
+})
+
+var Checkbox = Tool.extend({
+ init: function (el){
+ this.__init(el)
+ var name = this.name.replace(/^[x_] /,"")
+ var state = localStorage.getItem("ascii.tools." + name) == "true" || this.name[0] == "x"
+ this.name = name
+ this.update(state)
+ },
+ update: function(state){
+ if (state) this.el.innerHTML = "x " + this.name
+ else this.el.innerHTML = "_ " + this.name
+ if (this.memorable) { localStorage.setItem("ascii.tools." + this.name, !! state) }
+ }
+})
+
+var BlurredCheckbox = Checkbox.extend({
+ focus: function(){
+ this.use()
+ },
+ blur: function(){
+ this.el.classList.remove('focused')
+ this.done()
+ }
+})
+
+var BlurredTool = Tool.extend({
+ focus: function(){
+ this.use()
+ },
+ blur: function(){
+ this.el.classList.remove('focused')
+ this.done()
+ }
+})
+
+var HiddenCheckbox = BlurredCheckbox.extend({
+ on: "o",
+ off: ".",
+ init: function (el){
+ this.el = el
+ this.lex = new Lex (el)
+ this.name = this.el.id
+ var state = localStorage.getItem("ascii.tools." + name) == "true" || this.el.innerHTML[0] == this.on
+ this.update(state)
+ },
+ update: function(state){
+ this.el.innerHTML = state ? this.on : this.off
+ if (this.memorable) { localStorage.setItem("ascii.tools." + this.name, !! state) }
+ }
+})
diff --git a/js/ui/brush.js b/js/ui/brush.js
new file mode 100644
index 0000000..668538f
--- /dev/null
+++ b/js/ui/brush.js
@@ -0,0 +1,108 @@
+var brush = (function(){
+
+ var brush = new Matrix (5, 5, function(x,y){
+ var lex = new Lex (x,y)
+ lex.build()
+ return lex
+ })
+
+ brush.modified = false
+
+ brush.mask = blit.circle
+
+ brush.generate = function(){
+ brush.fill(brush)
+ brush.mask(brush)
+ }
+
+ brush.bind = function(){
+
+ var last_point = [0,0]
+ var dragging = false
+ var erasing = false
+
+ brush.forEach(function(lex, x, y){
+
+ if (lex.bound) return
+ lex.bound = true
+
+ var point = [x,y]
+ lex.span.addEventListener('contextmenu', function(e){
+ e.preventDefault()
+ })
+ lex.span.addEventListener('mousedown', function(e){
+ e.preventDefault()
+ current_canvas = brush
+ brush.modified = true
+ dragging = true
+ erasing = (e.which == "3" || e.ctrlKey)
+ if (erasing) {
+ lex.clear()
+ }
+ else {
+ fillColor = brush.bg
+ lex.assign(brush)
+ }
+ brush.focus(x, y)
+ })
+ lex.span.addEventListener('mousemove', function(e){
+ e.preventDefault()
+ if (! dragging) {
+ return
+ }
+ erasing = (e.which == "3" || e.ctrlKey)
+ if (erasing) {
+ lex.clear()
+ }
+ else {
+ lex.assign(brush)
+ }
+ brush.focus(x, y)
+ })
+ })
+ window.addEventListener('mouseup', function(e){
+ dragging = erasing = false
+ })
+ }
+
+ brush.resize = function(w, h){
+ w = this.w = clamp(w, this.min, this.max)
+ h = this.h = clamp(h, this.min, this.max)
+ brush.rebuild()
+ controls.brush_w.char = "" + w
+ controls.brush_w.build()
+ controls.brush_h.char = "" + h
+ controls.brush_h.build()
+ }
+ brush.size_add = function(w, h){
+ brush.resize(brush.w + w, brush.h + h)
+ }
+ brush.expand = function(i){
+ brush.size_add(i, i)
+ }
+ brush.contract = function(i){
+ brush.size_add(-i, -i)
+ }
+
+ brush.load = function(lex){
+ brush.char = lex.char
+ brush.fg = lex.fg
+ brush.bg = lex.bg
+ brush.opacity = 1
+ }
+
+ brush.min = 1
+ brush.max = 100
+
+ brush.char = " "
+ brush.fg = 0
+ brush.bg = 1
+ brush.opacity = 1
+
+ brush.draw_fg = true
+ brush.draw_bg = true
+ brush.draw_char = true
+
+ return brush
+
+})()
diff --git a/js/ui/canvas.js b/js/ui/canvas.js
new file mode 100644
index 0000000..ca14323
--- /dev/null
+++ b/js/ui/canvas.js
@@ -0,0 +1,144 @@
+var canvas = current_canvas = (function(){
+
+ var cols = 100
+ var rows = 30
+
+ var canvas = new Matrix (cols, rows, function(x,y){
+ var lex = new Lex (x,y)
+ lex.build()
+ return lex
+ })
+
+ canvas.bind = function(){
+
+ canvas.forEach(function(lex, x, y){
+
+ if (lex.bound) return
+ lex.bound = true
+ var point = [x,y]
+ lex.span.addEventListener('contextmenu', function(e){
+ e.preventDefault()
+ })
+ lex.span.addEventListener('mousedown', function(e){
+ if (is_mobile) return
+ e.preventDefault()
+ dragging = true
+ current_canvas = canvas
+ if (e.altKey) {
+ if (e.shiftKey) {
+ blit.copy_from(canvas, brush, floor(x-brush.w/2), floor(y-brush.h/2))
+ brush.mask(brush)
+ draw.set_last_point(e, point)
+ }
+ else {
+ brush.load(lex)
+ brush.generate()
+ dragging = false
+ }
+ return
+ }
+ else if (drawing) {
+ undo.new()
+ draw.down(e, lex, point)
+ }
+ else if (selecting) {
+ selection.down(e, lex, point)
+ }
+ else if (transforming) {
+ transform.down(e, lex, point)
+ }
+ else if (filling) {
+ undo.new()
+ draw.fill(brush, x, y)
+ }
+ canvas.focus(x, y)
+ })
+
+ lex.span.addEventListener("mousemove", function(e){
+ mouse.x = x
+ mouse.y = y
+ if (is_mobile) return
+ if (! dragging) return
+ if (drawing) {
+ draw.move(e, lex, point)
+ }
+ else if (selecting) {
+ selection.move(e, lex, point)
+ }
+ else if (transforming) {
+ transform.move(e, lex, point)
+ }
+ canvas.focus(x, y)
+ })
+
+ })
+
+ if (is_mobile) {
+ canvas.rapper.addEventListener('touchstart', function(e){
+ e.preventDefault()
+ var x, y, point, lex
+ x = (e.touches[0].pageX - canvas.rapper.offsetTop) / canvas.aa[0][0].span.offsetWidth
+ y = (e.touches[0].pageY - canvas.rapper.offsetTop) / canvas.aa[0][0].span.offsetHeight
+ x = ~~clamp(x, 0, canvas.aa[0].length-1)
+ y = ~~clamp(y, 0, canvas.aa.length-1)
+ point = [x,y]
+ lex = canvas.aa[y][x]
+ dragging = true
+ if (drawing) {
+ undo.new()
+ draw.down(e, lex, point)
+ }
+ else if (filling) {
+ undo.new()
+ draw.fill(brush, x, y)
+ }
+ canvas.focus(x, y)
+ })
+ canvas.rapper.addEventListener("touchmove", function(e){
+ e.preventDefault()
+ var x, y, point, lex
+ x = (e.touches[0].pageX - canvas.rapper.offsetTop) / canvas.aa[0][0].span.offsetWidth
+ y = (e.touches[0].pageY - canvas.rapper.offsetTop) / canvas.aa[0][0].span.offsetHeight
+ x = ~~clamp(x, 0, canvas.aa[0].length-1)
+ y = ~~clamp(y, 0, canvas.aa.length-1)
+ point = [x,y]
+ lex = canvas.aa[y][x]
+ if (! dragging) return
+ shader_el.innerHTML = point.join(",")
+ if (drawing) {
+ draw.move(e, lex, point)
+ }
+ canvas.focus(x, y)
+ })
+ }
+
+ }
+
+ canvas.min = 1
+ canvas.max = 999
+
+ // canvas.resize(1, 1, true) // wont create undo state
+ canvas.resize = function(w, h, no_undo){
+ var old_w = this.w, old_h = this.h
+ w = this.w = clamp(w, this.min, this.max)
+ h = this.h = clamp(h, this.min, this.max)
+ if (old_w === w && old_h === h) return;
+
+ if (!no_undo){
+ undo.new()
+ undo.save_resize(w, h, old_w, old_h)
+ }
+
+ canvas.__proto__.resize.call(canvas, w, h)
+ controls.canvas_w.char = "" + w
+ controls.canvas_w.build()
+ controls.canvas_h.char = "" + h
+ controls.canvas_h.build()
+ }
+ canvas.size_add = function(w, h){
+ canvas.resize(canvas.w + w, canvas.h + h)
+ }
+
+ return canvas
+
+})()
diff --git a/js/ui/controls.js b/js/ui/controls.js
new file mode 100644
index 0000000..f75ee67
--- /dev/null
+++ b/js/ui/controls.js
@@ -0,0 +1,374 @@
+var controls = (function(){
+
+ var controls = {}
+
+ controls.cross = new Tool (cross_el)
+ controls.cross.use = function(){
+ if (brush.mask == blit.cross) {
+ controls.cross.el.innerHTML = "ssoɹɔ"
+ brush.mask = blit.inverted_cross
+ }
+ else {
+ controls.cross.el.innerHTML = "cross"
+ brush.mask = blit.cross
+ }
+ brush.generate()
+ drawing = true
+ brush.modified = false
+ }
+ controls.cross.done = function(){
+ controls.cross.el.innerHTML = "cross"
+ drawing = false
+ }
+
+ controls.circle = new Tool (circle_el)
+ controls.circle.use = function(){
+ brush.mask = blit.circle
+ brush.generate()
+ drawing = true
+ brush.modified = false
+ }
+ controls.circle.done = function(){
+ drawing = false
+ }
+
+ controls.square = new Tool (square_el)
+ controls.square.use = function(){
+ brush.mask = blit.square
+ brush.generate()
+ brush.modified = false
+ drawing = true
+ }
+ controls.square.done = function(){
+ drawing = false
+ }
+
+ controls.text = new Tool (text_el)
+ controls.text.use = function(){
+ current_filetool && current_filetool.blur()
+ }
+
+ controls.select = new Tool (select_el)
+ controls.select.use = function(){
+ selection.show()
+ }
+ controls.select.done = function(){
+ selection.hide()
+ }
+
+ controls.rotate = new Tool (rotate_el)
+ controls.rotate.use = function(){
+ transform.set_mode('rotate')
+ }
+ controls.rotate.done = function(){
+ transform.done()
+ }
+
+ controls.scale = new Tool (scale_el)
+ controls.scale.use = function(){
+ transform.set_mode('scale')
+ }
+ controls.scale.done = function(){
+ transform.done()
+ }
+
+ controls.slice = new Tool (slice_el)
+ controls.slice.use = function(){
+ transform.set_mode('slice')
+ }
+ controls.slice.done = function(){
+ transform.done()
+ }
+
+ controls.translate = new Tool (translate_el)
+ controls.translate.use = function(){
+ transform.set_mode('translate')
+ }
+ controls.translate.done = function(){
+ transform.done()
+ }
+
+ controls.fill = new Tool (fill_el)
+ controls.fill.use = function(){
+ filling = true
+ document.body.classList.add("bucket")
+ }
+ controls.fill.done = function(){
+ filling = false
+ document.body.classList.remove("bucket")
+ }
+
+ controls.undo = new BlurredTool (undo_el)
+ controls.undo.use = function(){
+ undo.undo()
+ }
+
+ controls.redo = new BlurredTool (redo_el)
+ controls.redo.use = function(){
+ undo.redo()
+ }
+
+ controls.clear = new BlurredTool (clear_el)
+ controls.clear.use = function(){
+ undo.new()
+ undo.save_rect(0, 0, canvas.w, canvas.h)
+ canvas.erase()
+ current_filetool && current_filetool.blur()
+ }
+
+ controls.webcam = new FileTool (webcam_el)
+ controls.webcam.load = function(){
+ this.loaded = true
+ webcam_close.addEventListener("click", function(){ controls.webcam.blur() })
+ window.addEventListener("message", function(e){
+ if (e.origin !== window.location.origin) return
+ controls.webcam.blur()
+ controls.circle.focus()
+ import_textarea.value = e.data
+ clipboard.import_colorcode()
+ })
+ }
+ controls.webcam.use = function(){
+ if (! this.loaded) {
+ this.load()
+ }
+ webcam_iframe.src = "webcam.html"
+ webcam_rapper.style.display = "block"
+ }
+ controls.webcam.done = function(){
+ webcam_iframe.src = ""
+ webcam_rapper.style.display = "none"
+ }
+
+ controls.grid = new BlurredCheckbox (grid_el)
+ controls.grid.memorable = true
+ controls.grid.use = function(state){
+ state = typeof state == "boolean" ? state : ! document.body.classList.contains("grid")
+ document.body.classList[ state ? "add" : "remove" ]('grid')
+ letters.grid = palette.grid = canvas.grid = state
+ canvas.resize_rapper()
+ palette.resize_rapper()
+ letters.resize_rapper()
+ if (! selection.hidden) selection.reposition()
+ this.update( state )
+ }
+ ClipboardTool = FileTool.extend({
+ blur: function(){
+ this.__blur()
+ clipboard.hide()
+ }
+ })
+ controls.save = new ClipboardTool (save_el)
+ controls.save.use = function(){
+ changed && clipboard.upload_png()
+ clipboard.show()
+ clipboard.export_mode()
+ }
+ controls.send_to_irc = new ClipboardTool (send_to_irc_el)
+ controls.send_to_irc.use = function(){
+ changed && clipboard.upload_png()
+ clipboard.show()
+ clipboard.export_mode()
+ alert('your ascii art is now on display on the IRC channel inside the panke.gallery!')
+ }
+ controls.load = new ClipboardTool (load_el)
+ controls.load.use = function(){
+ // console.log("use")
+ clipboard.show()
+ clipboard.import_mode()
+ }
+
+ controls.save_format = new RadioGroup(format_el)
+ controls.save_format.name = 'save_format'
+ controls.save_format.memorable = true
+ var cs = controls.save_format.controls
+ cs.mirc.use = cs.irssi.use = cs.ascii.use = function(){
+ clipboard.export_data()
+ }
+ //
+
+ var ShaderTool = FileTool.extend({
+ active: false,
+ use: function(state){
+ this.active = typeof state == "boolean" ? state : ! this.active
+ if (this.active) {
+ shader_rapper.style.display = "block"
+ shader_textarea.focus()
+ } else {
+ shader_rapper.style.display = "none"
+ }
+ },
+ done: function(){
+ this.use(false)
+ }
+ })
+ controls.shader = new ShaderTool (shader_el)
+ shader_textarea.value = shader_textarea.value || demo_shader.innerHTML
+ shader_textarea.addEventListener("input", function(){
+ var fn = shader.build(shader_textarea.value)
+ fn && shader.run(canvas)
+ })
+ controls.animate = new BlurredCheckbox (animate_checkbox)
+ controls.animate.use = function(state){
+ var state = shader.toggle()
+ this.update(state)
+ // controls.shader.focus()
+ controls.shader.use(true)
+ }
+
+ controls.shader_target = new RadioGroup(shader_target_el)
+ var cs = controls.shader_target.controls
+ cs.canvas.use = function(){ shader.canvas = canvas }
+ cs.brush.use = function(){ shader.canvas = brush }
+ cs.selection.use = function(){ shader.canvas = selection.canvas }
+
+ controls.experimental_palette = new HiddenCheckbox (experimental_palette_toggle)
+ controls.experimental_palette.memorable = true
+ controls.experimental_palette.use = function(state){
+ var state = palette.experimental(state)
+ this.update(state)
+ }
+
+ controls.advanced = new BlurredCheckbox (advanced_checkbox)
+ controls.advanced.memorable = true
+ controls.advanced.use = function(state){
+ console.log(state)
+ state = typeof state == "boolean" ? state : ! document.body.classList.contains('panke')
+ if (state)
+ document.body.classList.add('panke')
+ else
+ document.body.classList.remove('panke')
+ this.update(state)
+ }
+
+/*
+ controls.nopaint = new HiddenCheckbox (nopaint_toggle)
+ controls.nopaint.memorable = true
+ controls.nopaint.on = "N"
+ controls.nopaint.use = function(state){
+ var state = nopaint.toggle(state)
+ this.update(state)
+ }
+*/
+
+ //
+
+ controls.fg = new BlurredCheckbox (fg_checkbox)
+ controls.fg.use = function(state){
+ brush.draw_fg = state || ! brush.draw_fg
+ this.update(brush.draw_fg)
+ }
+
+ controls.bg = new BlurredCheckbox (bg_checkbox)
+ controls.bg.use = function(state){
+ brush.draw_bg = state || ! brush.draw_bg
+ this.update(brush.draw_bg)
+ }
+
+ controls.char = new BlurredCheckbox (char_checkbox)
+ controls.char.use = function(state){
+ brush.draw_char = state || ! brush.draw_char
+ this.update(brush.draw_char)
+ }
+
+ //
+
+// controls.turn = new BlurredCheckbox (turn_checkbox)
+// controls.turn.memorable = true
+// controls.turn.use = function(state){
+// canvas.rotated = typeof state == "boolean" ? state : ! canvas.rotated
+// canvas.resize_rapper()
+// this.update(canvas.rotated)
+// }
+
+ // controls.pixels = new BlurredCheckbox (pixels_checkbox)
+ // controls.pixels.memorable = true
+ // controls.pixels.use = function(state){
+ // canvas.pixels = typeof state == "boolean" ? state : ! canvas.pixels
+ // document.body.classList.toggle("pixels", canvas.pixels)
+ // this.update(canvas.pixels)
+ // }
+
+ controls.mirror_x = new BlurredCheckbox (mirror_x_checkbox)
+ controls.mirror_x.use = function(state){
+ window.mirror_x = typeof state == "boolean" ? state : ! window.mirror_x
+ this.update(window.mirror_x)
+ }
+ controls.mirror_y = new BlurredCheckbox (mirror_y_checkbox)
+ controls.mirror_y.use = function(state){
+ window.mirror_y = typeof state == "boolean" ? state : ! window.mirror_y
+ this.update(window.mirror_y)
+ }
+
+ //
+
+ controls.vertical = new BlurredCheckbox (vertical_checkbox)
+ controls.vertical.memorable = true
+ controls.vertical.use = function(state){
+ canvas.vertical = typeof state == "boolean" ? state : ! canvas.vertical
+ controls.vertical.refresh()
+ }
+ controls.vertical.refresh = function(){
+ if (canvas.vertical) {
+ document.body.classList.add("vertical")
+ }
+ else {
+ document.body.classList.remove("vertical")
+ }
+ palette.repaint()
+ letters.repaint()
+ this.update(canvas.vertical)
+ }
+
+ //
+
+ controls.brush_w = new Lex (brush_w_el)
+ controls.brush_h = new Lex (brush_h_el)
+ controls.canvas_w = new Lex (canvas_w_el)
+ controls.canvas_h = new Lex (canvas_h_el)
+
+ // bind
+
+ controls.bind = function(){
+
+ for (var n in controls){
+ var control = controls[n]
+ if (typeof control === 'object' && 'bind' in control){
+ control.bind()
+ }
+ }
+
+ [
+ controls.brush_w,
+ controls.brush_h,
+ controls.canvas_w,
+ controls.canvas_h
+ ].forEach(function(lex){
+ lex.span.addEventListener('mousedown', function(e){
+ lex.focus()
+ if (is_mobile) cursor_input.focus()
+ })
+ });
+
+ controls.brush_w.key = keys.single_numeral_key(controls.brush_w, function(w){ brush.resize(w, brush.h) })
+ controls.brush_w.raw_key = keys.arrow_key(function(w){ brush.size_add(w, 0) })
+
+ controls.brush_h.key = keys.single_numeral_key(controls.brush_h, function(h){ brush.resize(brush.w, h) })
+ controls.brush_h.raw_key = keys.arrow_key(function(h){ brush.size_add(0, h) })
+
+ controls.canvas_w.key = keys.multi_numeral_key(controls.canvas_w, 3)
+ controls.canvas_w.onBlur = keys.multi_numeral_blur(controls.canvas_w, function(w){ canvas.resize(w, canvas.h) })
+ controls.canvas_w.raw_key = keys.arrow_key(function(w){ canvas.size_add(w, 0) })
+
+ controls.canvas_h.key = keys.multi_numeral_key(controls.canvas_h, 3)
+ controls.canvas_h.onBlur = keys.multi_numeral_blur(controls.canvas_h, function(h){ canvas.resize(canvas.w, h) })
+ controls.canvas_h.raw_key = keys.arrow_key(function(h){ canvas.size_add(0, h) })
+
+ add_custom_el.addEventListener("click", function(){
+ custom.clone()
+ })
+
+ }
+
+ return controls
+})()
diff --git a/js/ui/custom.js b/js/ui/custom.js
new file mode 100644
index 0000000..a758d48
--- /dev/null
+++ b/js/ui/custom.js
@@ -0,0 +1,24 @@
+var custom = (function(){
+
+ var exports = {}
+
+ exports.clone = function (){
+ var new_brush = brush.clone()
+ var rapper = document.createElement("div")
+ rapper.className = "custom"
+ new_brush.append(rapper)
+ custom_rapper.appendChild(rapper)
+ // store in localstorage?
+ rapper.addEventListener("click", function(){
+ // load this brush
+ exports.load(new_brush)
+ })
+ }
+
+ exports.load = function(new_brush){
+ brush.assign( new_brush )
+ }
+
+ return exports
+
+})()
diff --git a/js/ui/goodies.js b/js/ui/goodies.js
new file mode 100644
index 0000000..67f245c
--- /dev/null
+++ b/js/ui/goodies.js
@@ -0,0 +1,132 @@
+var goodies = (function(){
+ var goodies = {}
+
+ goodies.build = () => {
+ Object.keys(goodies.list).map(() => {
+ goodies_rapper.appendChild(tool_canvas.el)
+ })
+ }
+
+ goodies.list = {}
+
+ goodies.list.choppy = {
+ mode: 'brush',
+ fn: `var char = choice(" abcdef ")
+ lex.bg = +choice("0124")
+ lex.fg = +choice("01234")
+ lex.char = char
+ lex.opacity = char == " " ? 0 : 1`,
+ }
+
+ goodies.list.foggy = {
+ mode: 'brush',
+ fn: `var char = choice(" abcdef ")
+ lex.bg = choice([14,15])
+ lex.fg = choice("367")
+ lex.char = char
+ lex.opacity = char == " " ? 0 : 1`,
+ }
+ // goodies.list.name = {
+ // fn: ``,
+ // }
+ // goodies.list.name = {
+ // fn: ``,
+ // }
+ // goodies.list.name = {
+ // fn: ``,
+ // }
+ // goodies.list.name = {
+ // fn: ``,
+ // }
+
+// >> mirror brush (up-down)
+
+// NOTE: Animate this on the canvas, then draw:
+
+// if (x > h/2) {
+// lex.assign( canvas.aa[h-y][x] )
+// }
+
+
+
+// >> rainbow stardust brush
+
+// Uncheck BG and animate this to brush:
+
+// lex.fg = hue(t)
+// lex.char = choice(" ,'.,.','****** ")
+
+
+
+// >> noise brushes, works on a black background:
+
+// lex.bg = max(5, yellow(randint(t)))
+// lex.opacity = lex.bg == colors.black ? 0 : 1
+
+
+
+// >> simple rainbow:
+
+// if (lex.bg != 1) lex.bg = randint(t)
+// lex.opacity = lex.bg == colors.black ? 0 : 1
+
+
+
+// >> self-erasing:
+
+// if (lex.bg != 1) lex.bg = yellow(randint(t))
+// lex.opacity = lex.bg == colors.black ? 0 : 1
+
+
+
+// >> cycling rainbow brush
+
+// if (lex.bg != 1) lex.bg = hue( all_color_hue_order.indexOf( color_names[ lex.bg ] ) + 1 )
+// lex.opacity = lex.bg == colors.black ? 0 : 1
+
+
+
+// >> "stars" brush.. set your brush to paint just the character "#"
+
+// if (lex.char == "#") {
+// lex.fg = hue(randint(15))
+// lex.char = random() > 0.1 ? " " : "+@*.,\"+'*-"[randint(10)]
+// }
+
+
+
+// >> use fg char to mask mask what you're drawing on the bg
+
+// if (lex.char != "/") { lex.bg = 1 }
+
+
+
+// >> sharded glitch brush
+
+// Example: http://asdf.us/z/kksnvs.png
+
+// Use on a brush:
+
+// lex.bg = t/y/x
+// lex.opacity = lex.bg % 1 ? 0 : 1
+
+
+
+// >> incremental brush
+
+// Set your brush to be the ^ character, square, about 10x10
+// Draw "char" only
+// Then animate this shader on the canvas:
+
+// if (lex.char=="^") {
+// lex.bg += 1
+// lex.char = " "
+// }
+// lex.bg += 1
+
+
+
+
+
+ return goodies
+})
\ No newline at end of file
diff --git a/js/ui/keys.js b/js/ui/keys.js
new file mode 100644
index 0000000..3cd6168
--- /dev/null
+++ b/js/ui/keys.js
@@ -0,0 +1,240 @@
+var keys = (function(){
+
+ var keys = {}
+ keys.bind = function(){
+ cursor_input.addEventListener('keydown', function(e){
+
+ // console.log("keycode:", e.keyCode)
+ if (e.altKey) {
+ document.body.classList.add("dropper")
+ }
+
+ switch (e.keyCode) {
+ case 27: // esc
+ if (!selection.hidden && current_canvas === canvas){
+ selection.hide()
+ selection.show()
+ } else if (focused){
+ focused.blur()
+ }
+ return
+ }
+
+ if (window.focused && focused.raw_key) {
+ focused.raw_key(e)
+ return
+ }
+
+ switch (e.keyCode) {
+ case 219: // [
+ if (current_tool.name != "text") {
+ e.preventDefault()
+ brush.contract(1)
+ brush.modified = false
+ check_if_lost_focus()
+ }
+ break
+ case 221: // ]
+ if (current_tool.name != "text") {
+ e.preventDefault()
+ brush.expand(1)
+ brush.modified = false
+ }
+ break
+ case 8: // backspace
+ e.preventDefault()
+ if (current_canvas === canvas)
+ undo.new()
+ current_canvas.focus_add(-1, 0)
+ if (current_canvas === canvas)
+ undo.save_focused_lex()
+ focused.char = " "
+ focused.build()
+ return
+ case 13: // return
+ e.preventDefault()
+ current_canvas.focusLex(focused.y, focused.x+1)
+ return
+ case 38: // up
+ e.preventDefault()
+ current_canvas.focus_add(0, -1)
+ break
+ case 40: // down
+ e.preventDefault()
+ current_canvas.focus_add(0, 1)
+ break
+ case 37: // left
+ e.preventDefault()
+ current_canvas.focus_add(-1, 0)
+ break
+ case 39: // right
+ e.preventDefault()
+ current_canvas.focus_add(1, 0)
+ break
+ // use typical windows and os x shortcuts
+ // undo: ctrl-z or cmd-z
+ // redo: ctrl-y or shift-cmd-z
+ case 89: // y
+ if (!e.ctrlKey && !e.metaKey) break;
+ e.preventDefault();
+ undo.redo();
+ break
+ case 90: // z
+ if (!e.ctrlKey && !e.metaKey) break;
+ e.preventDefault();
+ if (e.shiftKey)
+ undo.redo();
+ else
+ undo.undo();
+ break
+ // default:
+ // if (focused) { focused.key(undefined, e.keyCode) }
+ }
+ })
+
+ cursor_input.addEventListener('input', function(e){
+ /*
+ if (! e.metaKey && ! e.ctrlKey && ! e.altKey) {
+ e.preventDefault()
+ }
+ */
+ if (current_tool.name == "shader") {
+ cursor_input.value = ""
+ return
+ }
+ var char = cursor_input.value
+ cursor_input.value = ""
+
+ // console.log("input:", char)
+
+ if (current_tool.name != "text" && ! brush.modified) {
+ brush.char = char
+ if (char == " ") {
+ brush.bg = brush.fg
+ }
+ else if (brush.bg != fillColor) {
+ brush.fg = brush.bg
+ brush.bg = fillColor
+ }
+ brush.rebuild()
+ }
+
+ if (focused && char) {
+ var y = focused.y, x = focused.x
+ if (current_canvas === canvas){
+ undo.new()
+ undo.save_focused_lex()
+ }
+ var moving = focused.key(char, e.keyCode)
+ if ( ! moving || ! ('y' in focused && 'x' in focused) ) { return }
+ current_canvas.focus_add(1, 0)
+ }
+ })
+
+ cursor_input.addEventListener("keyup", function(e){
+ if (! e.altKey) {
+ document.body.classList.remove("dropper")
+ }
+ })
+ }
+
+ keys.int_key = function (f) {
+ return function (key, keyCode) {
+ var n = parseInt(key)
+ ! isNaN(n) && f(n)
+ }
+ }
+
+ keys.arrow_key = function (fn) {
+ return function (e){
+ switch (e.keyCode) {
+ case 38: // up
+ e.preventDefault()
+ fn(1)
+ break
+ case 40: // down
+ e.preventDefault()
+ fn(-1)
+ break
+ }
+ }
+ }
+ keys.left_right_key = function (fn) {
+ return function (e){
+ switch (e.keyCode) {
+ case 39: // right
+ e.preventDefault()
+ fn(1)
+ break
+ case 38: // up
+ case 40: // down
+ e.preventDefault()
+ fn(0)
+ break
+ case 37: // left
+ e.preventDefault()
+ fn(-1)
+ break
+ }
+ }
+ }
+
+ keys.single_numeral_key = function (lex, fn) {
+ return keys.int_key(function(n, keyCode){
+ if (n == 0) n = 10
+ lex.blur()
+ fn(n)
+ })
+ }
+ keys.multi_numeral_key = function (lex, digits){
+ return keys.int_key(function(n, keyCode){
+ lex.read()
+ if (lex.char.length < digits) {
+ n = parseInt(lex.char) * 10 + n
+ }
+ lex.char = ""+n
+ lex.build()
+ })
+ }
+ keys.multi_numeral_blur = function (lex, fn){
+ return function(){
+ var n = parseInt(lex.char)
+ if (isNaN(n)) return
+ fn(n)
+ }
+ }
+
+ // function cancelZoom() {
+ // var d = document,
+ // viewport,
+ // content,
+ // maxScale = ',maximum-scale=',
+ // maxScaleRegex = /,*maximum\-scale\=\d*\.*\d*/;
+
+ // // this should be a focusable DOM Element
+ // if (!this.addEventListener || !d.querySelector) {
+ // return;
+ // }
+
+ // viewport = d.querySelector('meta[name="viewport"]');
+ // content = viewport.content;
+
+ // function changeViewport(event) {
+ // // http://nerd.vasilis.nl/prevent-ios-from-zooming-onfocus/
+ // viewport.content = content + (event.type == 'blur' ? (content.match(maxScaleRegex, '') ? '' : maxScale + 10) : maxScale + 1);
+ // }
+
+ // // We could use DOMFocusIn here, but it's deprecated.
+ // this.addEventListener('focus', changeViewport, true);
+ // this.addEventListener('blur', changeViewport, false);
+ // }
+
+ // cancelZoom.bind(cursor_input)();
+
+ return keys
+})()
+
+function check_if_lost_focus() {
+ if (! window.focused || ! window.focused.span)
+ window.focused = canvas.aa[0][0]
+}
diff --git a/js/ui/letters.js b/js/ui/letters.js
new file mode 100644
index 0000000..d745171
--- /dev/null
+++ b/js/ui/letters.js
@@ -0,0 +1,89 @@
+var letters = (function(){
+
+ var last_charset = ""
+ var charset_index = 0
+ var charsets = [
+ 'Basic Latin',
+ 'Latin-1 Supplement',
+ 'Box Drawing',
+ 'Block Elements',
+ ]
+
+ var letters = new Matrix (1, 1, function(x,y){
+ var lex = new Lex (x,y)
+ return lex
+ })
+
+ letters.charset = ""
+
+ letters.repaint = function(charset){
+ letters.charset = charset = charset || last_charset
+ last_charset = charset
+ var chars = unicode.block(charset, 32)
+ if (chars[0] != " ") chars.unshift(" ")
+ if (canvas.vertical) {
+ letters.resize( Math.ceil( chars.length / 16 ), 16 )
+ }
+ else {
+ letters.resize( 32, Math.ceil( chars.length / 32 ) )
+ }
+
+ var i = 0
+
+ letters.forEach(function(lex,x,y){
+ if (canvas.vertical) { x=x^y;y=x^y;x=x^y }
+ var char = chars[i++]
+ if (palette.chars.indexOf(brush.char) > 1) {
+ lex.bg = brush.fg
+ lex.fg = brush.bg
+ }
+ else {
+ lex.bg = colors.black
+ lex.fg = brush.fg == fillColor ? colors.black : brush.fg
+ }
+ lex.char = char
+ lex.opacity = 1
+ lex.build()
+ })
+ }
+
+ letters.bind = function(){
+ letters.forEach(function(lex,x,y){
+ if (lex.bound) return
+ lex.bound = true
+
+ lex.span.addEventListener('mousedown', function(e){
+ e.preventDefault()
+ if (e.shiftKey) {
+ charset_index = (charset_index+1) % charsets.length
+ letters.repaint(charsets[charset_index])
+ return
+ }
+ else if (e.ctrlKey || e.which == 3) {
+ brush.char = lex.char
+ brush.bg = brush.fg
+ brush.fg = fillColor
+ }
+ else {
+ brush.char = lex.char
+ if (lex.char == " ") {
+ brush.bg = brush.fg
+ }
+ else if (brush.bg != fillColor) {
+ brush.fg = brush.bg
+ brush.bg = fillColor
+ }
+ }
+ if (! brush.modified) {
+ brush.generate()
+ }
+ palette.repaint()
+ })
+ lex.span.addEventListener('contextmenu', function(e){
+ e.preventDefault()
+ })
+ })
+ }
+
+ return letters
+})()
\ No newline at end of file
diff --git a/js/ui/nopaint.js b/js/ui/nopaint.js
new file mode 100644
index 0000000..9011953
--- /dev/null
+++ b/js/ui/nopaint.js
@@ -0,0 +1,896 @@
+var nopaint = (function(){
+
+ controls.no = new Tool (nopaint_no_el)
+ controls.no.use = function(state){
+ undo.undo()
+ controls.paint.focus()
+ }
+ controls.no.context = function(e){
+ e.preventDefault()
+ nopaint.turbo()
+ }
+
+ controls.paint = new Tool (nopaint_paint_el)
+ controls.paint.use = function(state){
+ nopaint.paint()
+ nopaint_pause_el.classList.toggle("hidden", false)
+ focused = controls.paint.lex
+ }
+ controls.paint.context = function(e){
+ e.preventDefault()
+ nopaint.autoplay()
+ }
+
+ controls.nopaint_pause = new Tool (nopaint_pause_el)
+ controls.nopaint_pause.use = function(state){
+ // nopaint.pause()
+ nopaint.autoplay(false)
+ nopaint_pause_el.classList.toggle("hidden", true)
+ focused = canvas.aa[0][0]
+ }
+
+ // use own stepwise clock to drive tweens
+ oktween.raf = function(){}
+
+ var nopaint = {}
+ nopaint.debug = true
+ nopaint.delay = nopaint.normal_delay = 100
+ nopaint.turbo_delay = 0
+ nopaint.tool = null
+ nopaint.tools = {}
+ nopaint.keys = []
+ nopaint.weights = []
+ nopaint.step = 0
+ nopaint.time = 0
+ nopaint.timeout = false
+ nopaint.toggle = function(state){
+ var state = typeof state == "boolean" ? state : nopaint_rapper.classList.contains("hidden")
+ nopaint_rapper.classList.toggle("hidden", ! state)
+ nopaint_pause_el.classList.toggle("hidden", true)
+ document.body.classList.toggle("nopaint", state)
+ return state
+ }
+ nopaint.no = function(){
+ undo.undo()
+ nopaint.paint()
+ }
+ nopaint.raw_key = controls.paint.lex.raw_key = keys.left_right_key(function(n){
+ if (! nopaint.timeout) return
+ if (n < 0) nopaint.no()
+ else if (n > 0) nopaint.paint()
+ else nopaint.pause()
+ })
+ nopaint.pause = nopaint.blur = function(){
+ clearTimeout(nopaint.timeout)
+ nopaint.timeout = 0
+ nopaint.step = 0
+ }
+ nopaint.paint = function(){
+ var state = undo.new()
+ delete state.focus
+ nopaint.pause()
+ nopaint.switch_tool()
+ nopaint.go()
+ }
+ nopaint.go = function(){
+ nopaint.timeout = setTimeout(nopaint.go, nopaint.delay)
+ oktween.update(nopaint.time)
+ nopaint.tool.paint( nopaint.step )
+ nopaint.time += 1
+ nopaint.step += 1
+ }
+ nopaint.switch_tool = function(){
+ last_tool = nopaint.tool
+ last_tool && last_tool.finish()
+ nopaint.tool = nopaint.get_random_tool( last_tool )
+ nopaint.tool.start( last_tool )
+ nopaint.debug && console.log("> %s", nopaint.tool.type)
+ }
+ nopaint.add_tool = function(fn){
+ nopaint.tools[fn.type] = fn
+ }
+ nopaint.disable_all_tools = function(){
+ Object.keys(nopaint.tools).forEach(function(key){
+ nopaint.tools[key].disabled = true
+ })
+ }
+ nopaint.enable_tools = function(keys){
+ keys.forEach(function(key){
+ if (nopaint.tools[key]) nopaint.tools[key].disabled = false
+ })
+ }
+ nopaint.get_random_tool = function( last_tool ){
+ var n = rand( nopaint.sum )
+ for (var i = 0, _len = nopaint.weights.length; i < _len; i++) {
+ if (n < nopaint.weights[i] && (! last_tool || nopaint.keys[i] !== last_tool.key)) {
+ return nopaint.tools[ nopaint.keys[i] ]
+ }
+ }
+ return nopaint.tools[ choice(nopaint.keys) ]
+ }
+ nopaint.regenerate_weights = function(){
+ nopaint.sum = 0
+ nopaint.weights = []
+ nopaint.keys = Object.keys( nopaint.tools ).sort(function(a,b){
+ return nopaint.tools[b].opt.weight-nopaint.tools[a].opt.weight
+ }).filter(function(key){
+ return ! nopaint.tools[key].disabled
+ })
+ nopaint.keys.forEach(function(key){
+ nopaint.sum += nopaint.tools[key].opt.weight
+ nopaint.weights.push( nopaint.sum )
+ })
+ }
+
+ nopaint.is_turbo = false
+ nopaint.turbo = function(state){
+ nopaint.is_turbo = typeof state == "boolean" ? state : ! nopaint.is_turbo
+ nopaint.delay = nopaint.is_turbo ? nopaint.turbo_delay : nopaint.normal_delay
+ if (nopaint.is_turbo) {
+ nopaint_no_el.classList.add("locked")
+ }
+ else {
+ nopaint_no_el.classList.remove("locked")
+ }
+ }
+
+ nopaint.is_autoplay = false
+ nopaint.autoplay = function(state){
+ nopaint.is_autoplay = typeof state == "boolean" ? state : ! nopaint.is_autoplay
+ if (nopaint.is_autoplay) {
+ nopaint_paint_el.classList.add("locked")
+ if (! nopaint.player) {
+ nopaint.player = new RandomPlayer ()
+ }
+ if (! nopaint.timeout) nopaint.paint()
+ nopaint.player.play()
+ }
+ else {
+ nopaint_paint_el.classList.remove("locked")
+ nopaint.pause()
+ nopaint.player && nopaint.player.pause()
+ }
+ }
+
+ var NopaintPlayer = Model({
+ type: "player",
+ upload_png: false,
+ upload_interval: 100,
+ step: 0,
+ timeout: null,
+ delay: function(){
+ return nopaint.is_turbo ? randrange(150, 300) : randrange(400, 800)
+ },
+ reset: function(){
+ this.no_count = 0
+ this.paint_count = 0
+ },
+ pause: function(){
+ clearTimeout(this.timeout)
+ this.timeout = 0
+ },
+ play: function(){
+ clearTimeout(this.timeout)
+ var delay = this.delay()
+ this.timeout = setTimeout(this.play.bind(this), delay)
+ this.check_fitness()
+ this.step += 1
+ },
+ check_fitness: function(){
+ switch (this.fitness()) {
+ case "no":
+ nopaint.no_count += 1
+ nopaint.since_last_no = 0
+ nopaint.since_last_paint += 1
+ nopaint.no()
+ break
+ case "paint":
+ nopaint.paint_count += 1
+ nopaint.since_last_no += 1
+ nopaint.since_last_paint = 0
+ nopaint.paint()
+ break
+ case "screenshot":
+ if (this.save_as_png) break
+ console.log("uploading...")
+ setTimeout(clipboard.upload_png, 0)
+ // fall thru
+ default:
+ nopaint.since_last_no += 1
+ nopaint.since_last_paint += 1
+ break
+ }
+ },
+ fitness: function(){},
+ })
+
+ var RandomPlayer = NopaintPlayer.extend({
+ type: "random_player",
+ upload_png: false,
+ fitness: function(){
+ var no_prob = random()
+ var paint_prob = 1 - no_prob
+ if (paint_prob < 0.3) {
+ return "paint"
+ }
+ else if (no_prob < 0.5) {
+ return "no"
+ }
+ else if ( this.paint_count > 100 && (this.step % 100) == 99 ) {
+ return "screenshot"
+ }
+ }
+ })
+
+ var StylePlayer = NopaintPlayer.extend({
+ type: "style_player",
+ upload_png: false,
+ fitness: function(){
+ var no_prob = random()
+ var paint_prob = 1 - no_prob
+ var np, pp
+ var steps = this.since_last_paint
+
+ if (nopaint.tool.is_brush) {
+ if (nopaint.tool.is_clone) {
+ if (steps < randrange(3,8)) return
+ np = 0.3
+ pp = 0.4
+ }
+ else if (nopaint.tool.is_erase) {
+ if (steps < randrange(2,6)) return
+ np = 0.3
+ pp = 0.4
+ }
+ else {
+ if (steps < randrange(2,4)) return
+ np = 0.1
+ pp = 0.3
+ }
+ }
+ if (nopaint.tool.is_shader) {
+ switch (nopaint.tool.name) {
+ case "rotate":
+ case "scale":
+ if (steps < randrange(2,4)) return
+ np = 0.1
+ pp = 0.2
+ break
+ default:
+ np = 0.2
+ pp = 0.2
+ }
+ }
+ if (nopaint.tool.is_fill) {
+ np = 0.4
+ pp = 0.1
+ }
+
+ if (steps > 10) {
+ np *= 0.7
+ pp *= 1.5
+
+ if (nopaint.is_turbo) {
+ np *= 1.2
+ pp *= 1.2
+ }
+ }
+
+ if (paint_prob < np) {
+ return "paint"
+ }
+ else if (no_prob < np) {
+ return "no"
+ }
+ else if ( this.paint_count > 100 && (this.step % 100) == 99 ) {
+ return "screenshot"
+ }
+ }
+ })
+
+ /* Base models for brushes */
+
+ var NopaintTool = Model({
+ type: "none",
+ init: function(opt){
+ this.opt = opt || {}
+ },
+ start: function(){},
+ paint: function(t){},
+ update: function(t){},
+ finish: function(){},
+ })
+
+ var NopaintBrush = NopaintTool.extend({
+ type: "brush",
+ is_brush: true,
+ init: function(opt){
+ this.opt = opt || {}
+ this.opt.max_radius = this.opt.max_radius || 10
+ this.p = {x: randint(canvas.w), y: randint(canvas.h)}
+ this.fg = 0
+ this.bg = 1
+ this.char = " "
+ this.tweens = []
+ },
+
+ start: function(last_brush){
+ this.set_brush_mask()
+ this.toggle_channels()
+ this.reset( last_brush )
+ this.regenerate()
+ draw.down({}, null, [this.p.x, this.p.y])
+ },
+
+ paint: function(t){
+ this.update(t)
+ draw.move_toroidal({}, null, [this.p.x, this.p.y])
+ },
+
+ finish: function(){
+ this.tweens.forEach(function(t){ t.cancel() })
+ this.tweens = []
+ },
+
+ reorient: function(last_brush){
+ var a = {}, b
+
+ if (last_brush) {
+ this.p.x = a.x = randint(canvas.w)
+ this.p.y = a.y = randint(canvas.h)
+ }
+ else {
+ a.x = this.p.x
+ a.y = this.p.y
+ }
+
+ b = this.get_next_point()
+
+ var tween = oktween.add({
+ obj: this.p,
+ from: a,
+ to: b,
+ duration: b.duration,
+ easing: b.easing,
+ update: b.update,
+ finished: function(){
+ this.iterate()
+ this.regenerate()
+ }.bind(this)
+ })
+ this.tweens.push(tween)
+ },
+
+ get_next_point: function(){
+ var radius = randrange(2, this.opt.max_radius)
+ var b = {}
+ b.duration = randrange(1, 7)
+ b.easing = choice(easings)
+ b.x = this.p.x + randrange(-radius, radius)
+ b.y = this.p.y + randrange(-radius, radius)
+ return b
+ },
+
+ set_brush_mask: function(){
+ var r = Math.random()
+ if (r < 0.2) {
+ brush.mask = blit.square
+ }
+ else if (r < 0.6) {
+ brush.mask = blit.circle
+ }
+ else if (r < 0.9) {
+ brush.mask = blit.cross
+ }
+ else{
+ brush.mask = blit.inverted_cross
+ }
+ },
+
+ toggle_channels: function(){
+ if (Math.random() < 0.001) { controls.bg.use(false) }
+ else if (! brush.draw_bg && Math.random() < 0.25) { controls.bg.use(true) }
+
+ if (Math.random() < 0.1) { controls.fg.use(false) }
+ else if (! brush.draw_fg && Math.random() < 0.5) { controls.fg.use(true) }
+
+ if (Math.random() < 0.02) { controls.char.use(false) }
+ else if (! brush.draw_char && Math.random() < 0.2) { controls.char.use(true) }
+ },
+
+ iterate: function( last_brush ){
+ this.reorient( last_brush )
+ },
+
+ regenerate: function(){
+ brush.load( this )
+ brush.generate()
+ }
+ })
+
+ var easings = "linear circ_out circ_in circ_in_out quad_in quad_out quad_in_out".split(" ")
+
+ /* Standard brushes */
+
+ var SolidBrush = NopaintBrush.extend({
+ type: "solid",
+
+ recolor: function(){
+ this.fg = this.bg = randint(16)
+ this.char = " "
+ },
+
+ resize: function(m,n){
+ m = m || 3
+ n = n || 0
+ var bw = xrandrange(5, 0, m) + n
+ brush.resize( round(bw * randrange(0.9, 1.8)) || 1, round(bw) || 1 )
+ },
+
+ reset: function( last_brush ){
+ this.opt.max_radius = randrange(5,20)
+ this.resize()
+ this.reorient( last_brush )
+ this.recolor( last_brush )
+ this.regenerate()
+ },
+
+ iterate: function( last_brush ){
+ this.resize()
+ this.reorient( last_brush )
+ },
+ })
+
+ var EraseBrush = SolidBrush.extend({
+ type: "erase",
+ reset: function( last_brush ){
+ this.opt.max_radius = randrange(8, 20)
+ this.reorient( last_brush )
+ this.bg = random() < 0.2 ? colors.white : colors.black
+ this.char = " "
+ brush.load( this )
+ this.resize(3,2)
+ },
+ })
+
+ var ShadowBrush = NopaintBrush.extend({
+ type: "shadow",
+ pairs: [
+ [ colors.yellow, colors.orange ],
+ [ colors.orange, colors.darkred ],
+ [ colors.red, colors.darkred ],
+ [ colors.lime, colors.green ],
+ [ colors.cyan, colors.teal ],
+ [ colors.cyan, colors.blue ],
+ [ colors.blue, colors.darkblue ],
+ [ colors.magenta, colors.purple ],
+ [ colors.lightgray, colors.darkgray ],
+ [ colors.darkgray, colors.black ],
+ [ colors.white, colors.lightgray ],
+ [ colors.white, colors.black ],
+ ],
+ shapes: [
+ [[0],[1]],
+ [[0,0],[1,1]],
+ [[1,0,0],[1,1,1]],
+ [[0,0,1],[1,1,1]],
+ [[0,0,0],[1,1,1]],
+ [[0,0,0,0],[1,1,1,1]],
+ [[1,0,0,0],[null,1,1,1]],
+ [[0,0,0,1],[1,1,1,null]],
+ [[0,0],[1,0],[1,1]],
+ [[0,0],[0,1],[1,1]],
+ ],
+ reset: function( last_brush ){
+ var pair = choice(this.pairs)
+ var shape = choice(this.shapes)
+ this.reorient( last_brush )
+ brush.char = " "
+ brush.resize(shape[0].length, shape.length)
+ brush.generate()
+ brush.rebuild()
+ brush.forEach(function(lex,x,y){
+ if (shape[y][x] == null) {
+ lex.opacity = 0
+ }
+ else {
+ lex.fg = lex.bg = pair[ shape[y][x] ]
+ lex.opacity = 1
+ }
+ lex.build()
+ })
+ },
+ regenerate: function(){},
+ })
+
+ var RandomBrush = SolidBrush.extend({
+ type: "random",
+ iterate: function( last_brush ){
+ this.reorient( last_brush )
+ this.recolor( last_brush )
+ },
+ })
+
+ var HueBrush = SolidBrush.extend({
+ type: "hue",
+ recolor: function(){
+ this.fg = this.bg = rand_hue()
+ this.char = " "
+ },
+ })
+
+ var GrayBrush = SolidBrush.extend({
+ type: "gray",
+ recolor: function(){
+ this.fg = this.bg = rand_gray()
+ this.char = " "
+ },
+ })
+
+ var LetterBrush = SolidBrush.extend({
+ type: "letter",
+ recolor: function(){
+ this.fg = rand_hue()
+ this.bg = rand_hue()
+ this.char = choice( unicode.block(letters.charset, 32) )
+ },
+ })
+
+ var RandomLetterBrush = LetterBrush.extend({
+ type: "random-letter",
+ iterate: function(){
+ if (Math.random() < 0.01) {
+ this.fg += 1
+ }
+ if (Math.random() < 0.05) {
+ var n = this.fg
+ this.fg = this.bg
+ this.bg = n
+ }
+ if (Math.random() < 0.7) {
+ this.char = choice( unicode.block(letters.charset, 32) )
+ }
+ this.regenerate()
+ this.__iterate()
+ },
+ update: function(){
+ if (Math.random() < 0.3) {
+ this.char = choice( unicode.block(letters.charset, 32) )
+ }
+ this.regenerate()
+ },
+ })
+
+ var CloneBrush = SolidBrush.extend({
+ type: "clone",
+
+ is_clone: true,
+
+ reset: function( last_brush ){
+ this.opt.max_radius = randrange(5, 20)
+ this.reorient( last_brush )
+ this.resize(4,2)
+ this.clone_random_region()
+ },
+
+ clone_random_region: function(x, y){
+ var x = randrange(0, canvas.w - brush.w)
+ var y = randrange(0, canvas.h - brush.h)
+ this.clone_region(x, y)
+ },
+
+ clone_region: function(x, y){
+ blit.copy_toroidal_from(canvas, brush, round(x-brush.w/2), round(y-brush.h/2))
+ brush.mask(brush)
+ },
+
+ iterate: function( last_brush ){
+ this.reorient( last_brush )
+ },
+
+ regenerate: function(){},
+ })
+
+ var SmearBrush = CloneBrush.extend({
+ type: "smear",
+
+ update: function(){
+ var r = random()
+ var jitter_x = randnullsign() * xrand(2, 2)
+ var jitter_y = randnullsign() * xrand(2, 2)
+ this.clone_region( this.p.x + jitter_x, this.p.y + jitter_y )
+ },
+
+ iterate: function( last_brush ){
+ this.resize(4, 2)
+ this.update()
+ this.reorient( last_brush )
+ }
+ })
+
+ var StarsTool = NopaintBrush.extend({
+ type: "stars",
+ chars: "....,,'''*",
+
+ start: function(last_brush){
+ this.reorient( last_brush )
+ },
+
+ paint: function(t){
+ if (Math.random() < 0.5) {
+ var lex = canvas.get(this.p.x, this.p.y)
+ undo.save_lex(lex.x, lex.y, lex)
+ lex.fg = rand_hue()
+ // lex.bg = colors.black
+ lex.char = choice(this.chars)
+ lex.build()
+ }
+ },
+ })
+
+ /* Fill tool */
+
+ var FillTool = NopaintTool.extend({
+ type: "fill",
+ rate: 25,
+ is_fill: true,
+ start: function(){
+ this.fill()
+ },
+ paint: function(t){
+ if ((t % this.rate) == this.rate-1) {
+ this.fill()
+ }
+ },
+ recolor: function(){
+ this.fg = this.bg = randint(16)
+ this.char = " "
+ this.opacity = 1
+ },
+ fill: function(){
+ var x = randint(canvas.w)
+ var y = randint(canvas.h)
+ this.recolor()
+ draw.fill(this, x, y)
+ }
+ })
+
+ var FillLetterTool = FillTool.extend({
+ type: "fill-letter",
+ rate: 25,
+ recolor: function(){
+ this.fg = randint(16)
+ this.bg = randint(16)
+ this.char = choice( unicode.block(letters.charset, 32) )
+ this.opacity = 1
+ },
+ })
+
+ /* Shader Tools */
+
+ var ShaderTool = NopaintTool.extend({
+ type: "shader",
+ speed: 3,
+ is_shader: true,
+ is_recursive: false,
+ start: function(){
+ undo.save_rect(0, 0, canvas.w, canvas.h)
+ this.canvas = canvas.clone()
+ },
+ paint: function(t){
+ if ((t % this.speed) == 0) {
+ var w = canvas.w
+ var h = canvas.h
+ var lex
+ if (this.is_recursive) {
+ this.canvas.assign(canvas)
+ }
+ this.before_shade()
+ for (var x = 0; x < w; x++) {
+ for (var y = 0; y < h; y++) {
+ lex = canvas.get(x, y)
+ if (! this.shade( this.canvas, canvas, lex, x, y, w, h )) {
+ lex.build()
+ }
+ }
+ }
+ }
+ },
+ before_shade: function(){},
+ shade: function(src, dest, lex, x, y, w, h){},
+ finish: function(){
+ this.canvas.demolish()
+ }
+ })
+
+ var ColorizeTool = ShaderTool.extend({
+ type: "colorize",
+ fns: [mirc_color_reverse,hue,inv_hue,gray,fire,red,yellow,green,blue,purple,dark_gray],
+ speed: 5,
+ start: function(){
+ this.__start()
+ this.i = randint(this.fns.length)
+ },
+ before_shade: function(){
+ this.i = (this.i + 1) % this.fns.length
+ this.fn = this.fns[this.i]
+ },
+ shade: function(src, dest, lex, x, y, w, h){
+ lex.bg = this.fn( lex.bg )
+ return false
+ },
+ })
+
+ var TranslateTool = ShaderTool.extend({
+ type: "translate",
+ dx: 0,
+ dy: 0,
+ speed: 3,
+ start: function(){
+ this.__start()
+ this.dx = randint(3)-1
+ this.dy = randint(3)-1
+ this.x = this.y = 0
+ if (! this.dx && ! this.dy) {
+ this.dx = 1
+ this.dy = 0
+ }
+ },
+ before_shade: function(){
+ this.x += this.dx
+ this.y += this.dy
+ },
+ shade: function(src, dest, lex, x, y, w, h){
+ var copy = src.get(x+this.x, y+this.y)
+ lex.assign(copy)
+ return true
+ },
+ })
+
+ var SliceTool = ShaderTool.extend({
+ type: "slice",
+ dx: 0,
+ dy: 0,
+ speed: 1,
+ is_recursive: true,
+ start: function(){
+ this.__start()
+ this.is_y = Math.random() > 0.3
+ this.limit = this.is_y ? canvas.h : canvas.w
+ this.position = randint(this.limit)
+ this.direction = 1
+ },
+ before_shade: function(){
+ if (Math.random() < 0.6) {
+ this.position = mod(this.position + 1, this.limit)
+ }
+ if (Math.random() > 0.8) {
+ this.direction = randsign()
+ }
+ },
+ shade: function(src, dest, lex, x, y, w, h){
+ if (this.is_y) {
+ if (y >= this.position) {
+ var copy = src.get(x + this.direction, y)
+ lex.assign(copy)
+ }
+ }
+ else if (x >= this.position) {
+ var copy = src.get(x, y + this.direction)
+ lex.assign(copy)
+ }
+ return true
+ },
+ })
+
+ var ScaleTool = ShaderTool.extend({
+ type: "scale",
+ scale: 1,
+ dscale: 0,
+ speed: 3,
+ start: function(){
+ this.__start()
+ var sign = randsign()
+ this.x_scale = 1
+ this.y_scale = 1
+ this.dx_scale = randsign() * randrange(0.0005, 0.01)
+ var r = Math.random()
+ if (r < 0.333) {
+ this.dy_scale = this.dx_scale * randrange(0.85, 1.25)
+ }
+ else if (r < 0.666) {
+ this.dy_scale = this.dx_scale
+ }
+ else {
+ this.dy_scale = randsign() * randrange(0.0005, 0.01)
+ }
+ },
+ before_shade: function(){
+ this.x_scale += this.dx_scale
+ this.y_scale += this.dy_scale
+ },
+ shade: function(src, dest, lex, x, y, w, h){
+ x = (x/w) * 2 - 1
+ y = (y/h) * 2 - 1
+ x *= this.x_scale
+ y *= this.y_scale
+ x = (x + 1) / 2 * w
+ y = (y + 1) / 2 * h
+ var copy = src.get(x, y)
+ lex.assign(copy)
+ return true
+ },
+ })
+
+ var RotateTool = ShaderTool.extend({
+ type: "rotate",
+ theta: 0,
+ d_theta: 0,
+
+ start: function(){
+ this.__start()
+ var sign = randsign()
+ this.theta = 0
+ this.d_theta = randsign() * randrange(0.001, 0.05)
+ },
+ before_shade: function(){
+ this.theta += this.d_theta
+ },
+ shade: function(src, dest, lex, x, y, w, h){
+ x = (x/w) * 2 - 1
+ y = (y/h) * 2 - 1
+ var ca = cos(this.theta)
+ var sa = sin(this.theta)
+ var a = x * ca - y * sa
+ var b = x * sa + y * ca
+ x = (a + 1) / 2 * w
+ y = (b + 1) / 2 * h
+ var copy = src.get(x, y)
+ lex.assign(copy)
+ return true
+ },
+ })
+
+ var CycleTool = ShaderTool.extend({
+ type: "cycle",
+ n: 0,
+ speed: 5,
+ is_recursive: true,
+ start: function(){
+ this.__start()
+ this.n = randsign()
+ if (random() < 0.2) this.n *= randint(15)
+ },
+ shade: function(src, dest, lex, x, y){
+ lex.bg += this.n
+ return false
+ },
+ })
+
+ nopaint.add_tool( new SolidBrush({ weight: 5 }) )
+ nopaint.add_tool( new ShadowBrush({ weight: 10 }) )
+ nopaint.add_tool( new EraseBrush({ weight: 5 }) )
+ nopaint.add_tool( new RandomBrush({ weight: 4 }) )
+ nopaint.add_tool( new HueBrush({ weight: 5 }) )
+ nopaint.add_tool( new GrayBrush({ weight: 5 }) )
+ nopaint.add_tool( new LetterBrush({ weight: 2 }) )
+ nopaint.add_tool( new RandomLetterBrush({ weight: 12 }) )
+ nopaint.add_tool( new CloneBrush({ weight: 8 }) )
+ nopaint.add_tool( new SmearBrush({ weight: 10 }) )
+ nopaint.add_tool( new FillTool({ weight: 3 }) )
+ nopaint.add_tool( new FillLetterTool({ weight: 6 }) )
+ nopaint.add_tool( new StarsTool({ weight: 2 }) )
+ nopaint.add_tool( new TranslateTool({ weight: 4 }) )
+ nopaint.add_tool( new CycleTool({ weight: 1 }) )
+ nopaint.add_tool( new ScaleTool({ weight: 3 }) )
+ nopaint.add_tool( new RotateTool({ weight: 3 }) )
+ nopaint.add_tool( new SliceTool({ weight: 4 }) )
+ nopaint.add_tool( new ColorizeTool({ weight: 1 }) )
+ nopaint.regenerate_weights()
+
+ nopaint.toggle(true)
+
+ nopaint.player = new StylePlayer ()
+
+ return nopaint
+})()
diff --git a/js/ui/palette.js b/js/ui/palette.js
new file mode 100644
index 0000000..40da8b6
--- /dev/null
+++ b/js/ui/palette.js
@@ -0,0 +1,106 @@
+var palette = (function(){
+
+ var palette = new Matrix (32, 2, function(x,y){
+ var lex = new Lex (x,y)
+ return lex
+ })
+
+ var palette_index = localStorage.getItem("ascii.palette") || 1
+ var palette_list = [all_hue, all_inv_hue, mirc_color, mirc_color_reverse]
+ var palette_fn = palette_list[palette_index]
+ palette.chars = " " + dither.a + dither.b + dither.c
+
+ palette.repaint = function(){
+ var xw = use_experimental_palette ? 5 : 2
+ if (canvas.vertical) {
+ palette.resize( xw, 16 )
+ }
+ else {
+ palette.resize( 32, xw )
+ }
+
+ palette.forEach(function(lex,x,y){
+ if (canvas.vertical) { x=x^y;y=x^y;x=x^y;x*=2 }
+ if (y < 2) {
+ lex.bg = palette_fn(x>>1)
+ lex.fg = palette_fn(x>>1)
+ }
+ else {
+ lex.bg = fillColor
+ lex.fg = palette_fn(x>>1)
+ }
+ lex.char = palette.chars[y]
+ lex.opacity = 1
+ lex.build()
+ if (lex.char == "_") lex.char = " "
+ })
+ }
+ palette.repaint()
+ var use_experimental_palette = false
+ palette.experimental = function(state){
+ use_experimental_palette = typeof state == "boolean" ? state : ! use_experimental_palette
+ use_experimental_palette ? palette.resize(32, 5) : palette.resize(32, 2)
+ palette.repaint()
+ return use_experimental_palette
+ }
+
+ palette.bind = function(){
+ palette.forEach(function(lex, x, y){
+ if (lex.bound) return
+ lex.bound = true
+
+ lex.span.addEventListener('mousedown', function(e){
+ e.preventDefault()
+ if (e.shiftKey) {
+ palette_index = (palette_index+1) % palette_list.length
+ localStorage.setItem("ascii.palette", palette_index)
+ palette_fn = palette_list[palette_index]
+ palette.repaint()
+ return
+ }
+ if (e.ctrlKey || e.which == 3) return
+ if (brush.char == " " && lex.char == " ") {
+ brush.fg = lex.fg
+ brush.bg = lex.bg
+ brush.char = lex.char
+ }
+ else if (lex.char != " ") {
+ brush.fg = lex.bg
+ brush.bg = lex.fg
+ brush.char = lex.char
+ }
+ else {
+ brush.fg = lex.bg
+ brush.bg = fillColor
+// brush.char = lex.char
+ }
+ brush.opacity = lex.opacity
+ if (! brush.modified) {
+ brush.generate()
+ }
+ if (filling || e.ctrlKey) {
+ fillColor = lex.bg
+ }
+ letters.repaint()
+ })
+
+ lex.span.addEventListener('contextmenu', function(e){
+ e.preventDefault()
+ fillColor = y ? lex.fg : lex.bg
+ palette.repaint()
+ brush.fg = lex.fg
+ brush.char = lex.char
+ brush.opacity = lex.opacity
+ brush.generate()
+ brush_rapper.style.borderColor = css_reverse_lookup[fillColor]
+ return
+ })
+
+ })
+ }
+
+ brush_rapper.style.borderColor = css_reverse_lookup[fillColor]
+
+ return palette
+
+})()
diff --git a/js/ui/selection.js b/js/ui/selection.js
new file mode 100644
index 0000000..a2720cb
--- /dev/null
+++ b/js/ui/selection.js
@@ -0,0 +1,159 @@
+var selection = (function(){
+
+ var creating = false, moving = false, copying = false
+
+ var selection_canvas = new Matrix (1, 1, function(x,y){
+ var lex = new Lex (x,y)
+ lex.build()
+ return lex
+ })
+
+ var selector_el = document.createElement("div")
+ selector_el.className = "selector_el"
+ selection_canvas.append(selector_el)
+ document.body.appendChild(selector_el)
+
+ // in selection mode..
+ // - we start by clicking the canvas. this positions the selection, and copies
+ // the character
+ // - then we drag down and to the right. this resizes the selection and pushes new
+ // rows and columns. each of these copies the character underneath.
+ // - on mouseup, the selection is locked. then..
+ // - drag the selection to move it -- this "cuts" it and leaves a blank space on the canvas.
+ // - shift-drag the selection to copy it
+
+ var a = [0, 0]
+ var b = [0, 0]
+ var c = [0, 0]
+ var d = [0, 0]
+
+ function reset () {
+ a[0] = a[1] = b[0] = b[1] = 0
+ }
+ function left (a,b) { return min(a[0],b[0]) }
+ function top (a,b) { return min(a[1],b[1]) }
+ function right (a,b) { return max(a[0],b[0]) }
+ function bottom (a,b) { return max(a[1],b[1]) }
+ function width (a,b) { return abs(a[0]-b[0])+1 }
+ function height (a,b) { return abs(a[1]-b[1])+1 }
+ function mag_x (a,b) { return a[0]-b[0] }
+ function mag_y (a,b) { return a[1]-b[1] }
+ function orient (a,b) {
+ var l = left(a,b), m = top(a,b), n = right(a,b), o = bottom(a,b)
+ a[0] = l ; a[1] = m ; b[0] = n ; b[1] = o
+ }
+
+ function contains (a,b,point) {
+ var contains_x = a[0] <= point[0] && point[0] <= b[0]
+ var contains_y = a[1] <= point[1] && point[1] <= b[1]
+ return (contains_x && contains_y)
+ }
+ function reposition (aa, bb) {
+ aa = aa || a
+ bb = bb || b
+ var cell = canvas.aa[top(aa, bb)][left(aa, bb)].span
+ var cell_left = cell.offsetLeft
+ var cell_top = cell.offsetTop
+ var cell_width = cell.offsetWidth
+ var cell_height = cell.offsetHeight
+
+ var w = width(aa, bb)
+ var h = height(aa, bb)
+
+ selector_el.style.top = (cell_top-1) + "px"
+ selector_el.style.left = (cell_left-1) + "px"
+ selector_el.style.width = (cell_width*w+1) + "px"
+ selector_el.style.height = (cell_height*h+1) + "px"
+ }
+ function down (e, lex, point){
+ if ( ! contains(a,b,point) ) {
+ copying = false
+ moving = false
+ creating = true
+ a[0] = point[0]
+ a[1] = point[1]
+ b[0] = point[0]
+ b[1] = point[1]
+ reposition(a,b)
+ selection.hidden = false
+ selector_el.classList.add("creating")
+ } else {
+ copying = false
+ moving = true
+ creating = false
+ c[0] = point[0]
+ c[1] = point[1]
+ d[0] = point[0]
+ d[1] = point[1]
+ }
+ show()
+ selector_el.classList.remove("dragging")
+ }
+ function move (e, lex, point){
+ if (creating) {
+ b[0] = point[0]
+ b[1] = point[1]
+ reposition(a,b)
+ }
+ else if (moving) {
+ d[0] = point[0]
+ d[1] = point[1]
+ var dx = - clamp( mag_x(c,d), b[0] - canvas.w + 1, a[0] )
+ var dy = - clamp( mag_y(c,d), b[1] - canvas.h + 1, a[1] )
+ reposition( [ a[0] + dx, a[1] + dy ], [ b[0] + dx, b[1] + dy ])
+ }
+ else if (copying) {
+ }
+ }
+ function up (e) {
+ if (creating) {
+ orient(a,b)
+ selection_canvas.resize(width(a,b), height(a,b))
+ reposition(a,b)
+ blit.copy_from( canvas, selection_canvas, a[0], a[1] )
+ selection_canvas.build()
+ selector_el.classList.remove("creating")
+ }
+ if (moving) {
+ var dx = - clamp( mag_x(c,d), b[0] - canvas.w + 1, a[0] )
+ var dy = - clamp( mag_y(c,d), b[1] - canvas.h + 1, a[1] )
+ a[0] += dx
+ a[1] += dy
+ b[0] += dx
+ b[1] += dy
+ undo.new()
+ undo.save_rect(a[0], a[1], b[0] - a[0] + 1, b[1] - a[1] + 1)
+ blit.copy_to( canvas, selection_canvas, a[0], a[1] )
+ }
+ if (copying) {
+ }
+ creating = moving = copying = false
+ selector_el.classList.remove("dragging")
+ }
+
+ function show () {
+ selecting = true
+ }
+ function hide () {
+ reset()
+ selector_el.style.top = "-9999px"
+ selector_el.style.left = "-9999px"
+ selector_el.style.width = "0px"
+ selector_el.style.height = "0px"
+ creating = moving = copying = false
+ selection.hidden = true
+ selecting = false
+ }
+
+ var selection = {}
+ selection.reposition = reposition
+ selection.down = down
+ selection.move = move
+ selection.up = up
+ selection.canvas = selection_canvas
+ selection.show = show
+ selection.hide = hide
+ selection.hidden = true
+ return selection
+
+})()
diff --git a/js/ui/transform.js b/js/ui/transform.js
new file mode 100644
index 0000000..d6253bc
--- /dev/null
+++ b/js/ui/transform.js
@@ -0,0 +1,176 @@
+var transform = (function(){
+
+ var p = [0,0], q = [0,0]
+ var mode
+ var copy
+
+ function down (e, lex, point){
+ p[0] = point[0]
+ p[1] = point[1]
+ q[0] = e.pageX
+ q[1] = e.pageY
+ undo.new()
+ undo.save_rect(0, 0, canvas.w, canvas.h)
+ copy = canvas.clone()
+ mode.init(e)
+ }
+ function move (e, lex, point){
+ var pdx = point[0] - p[0]
+ var pdy = point[1] - p[1]
+ var dx = e.pageX - q[0]
+ var dy = e.pageY - q[1]
+ var w = canvas.w
+ var h = canvas.h
+ mode.before(dx, dy, pdx, pdy, point)
+ for (var x = 0; x < w; x++) {
+ for (var y = 0; y < h; y++) {
+ lex = canvas.get(x, y)
+ if (! mode.shade( copy, canvas, lex, x, y, w, h )) {
+ lex.build()
+ }
+ }
+ }
+ }
+ function up (e){
+ }
+
+ var modes = {
+
+ rotate: {
+ init: function(e){
+ mode.theta = 0
+ },
+ before: function(dx, dy){
+ var radius = dist(0, 0, dx, dy)
+ if (radius < 10) return
+ mode.theta = angle(0, 0, dx, -dy)
+ },
+ shade: function(src, dest, lex, x, y, w, h){
+ x = (x/w) * 2 - 1
+ y = (y/h) * 2 - 1
+ var ca = cos(mode.theta)
+ var sa = sin(mode.theta)
+ var a = x * ca - y * sa
+ var b = x * sa + y * ca
+ x = (a + 1) / 2 * w
+ y = (b + 1) / 2 * h
+ var copy = src.get(x, y)
+ lex.assign(copy)
+ return true
+ },
+ },
+
+ scale: {
+ init: function(e){
+ mode.independent = e.shiftKey || e.altKey || e.metaKey
+ mode.x_scale = mode.y_scale = 0
+ },
+ before: function(dx, dy, pdx, pdy){
+ if (mode.independent) {
+ mode.x_scale = Math.pow(2, -pdx / (canvas.w / 8))
+ mode.y_scale = Math.pow(2, -pdy / (canvas.h / 8))
+ }
+ else {
+ mode.x_scale = mode.y_scale = Math.pow(2, -pdx / (canvas.w / 8))
+ }
+ },
+ shade: function(src, dest, lex, x, y, w, h){
+ x = ((x-p[0])/w) * 2 - 1
+ y = ((y-p[1])/h) * 2 - 1
+ x *= mode.x_scale
+ y *= mode.y_scale
+ x = (x + 1) / 2 * w
+ y = (y + 1) / 2 * h
+ var copy = src.get(x+p[0], y+p[1])
+ lex.assign(copy)
+ return true
+ },
+ },
+
+ translate: {
+ init: function(e){
+ mode.dx = mode.dy = 0
+ },
+ before: function(dx, dy, pdx, pdy){
+ mode.dx = -pdx
+ mode.dy = -pdy
+ },
+ shade: function(src, dest, lex, x, y, w, h){
+ var copy = src.get(x+mode.dx, y+mode.dy)
+ lex.assign(copy)
+ return true
+ },
+ },
+
+ slice: {
+ init: function(e){
+ mode.is_y = ! (e.altKey || e.metaKey)
+ mode.reverse = !! (e.shiftKey)
+ mode.position = 0
+ mode.direction = 0
+ mode.last_dd = -1
+ },
+ before: function(dx, dy, pdx, pdy, point){
+ var new_position = mode.is_y ? point[1] : point[0]
+ var dd = mode.is_y ? pdx : pdy
+
+ if (mode.position !== new_position) {
+ mode.position = new_position
+ mode.direction = 0
+ }
+ if (mode.last_dd !== -1) {
+ mode.direction = mode.last_dd - dd
+ }
+ console.log(mode.position)
+ mode.last_dd = dd
+ copy.assign(canvas)
+ },
+ shade: function(src, dest, lex, x, y, w, h){
+ if (mode.is_y) {
+ if (y >= mode.position || (mode.reverse && mode.position >= y)) {
+ var copy = src.get(x + mode.direction, y)
+ lex.assign(copy)
+ }
+ }
+ else if (x >= mode.position || (mode.reverse && mode.position >= x)) {
+ var copy = src.get(x, y + mode.direction)
+ lex.assign(copy)
+ }
+ return true
+ },
+ },
+
+/*
+ mode: {
+ init: function(e){
+ },
+ before: function(dx, dy, pdx, pdy){
+ },
+ shade: function(src, dest, lex, x, y, w, h){
+ },
+ },
+*/
+ }
+
+ function set_mode(m){
+ if (m in modes) {
+ mode = modes[m]
+ transforming = true
+ }
+ }
+
+ function done(){
+ transforming = false
+ copy && copy.demolish()
+ }
+
+ return {
+ down: down,
+ move: move,
+ up: up,
+ set_mode: set_mode,
+ modes: modes,
+ done: done,
+ }
+
+})()
\ No newline at end of file
diff --git a/js/undo.js b/js/undo.js
new file mode 100644
index 0000000..35a27d9
--- /dev/null
+++ b/js/undo.js
@@ -0,0 +1,227 @@
+var undo = (function(){
+
+var max_states = 200;
+
+// undotimetotal = 0;
+
+var stack = {undo: [], redo: []};
+var current_undo = null;
+var dom = {undo: undo_el, redo: redo_el};
+dom.undo.is_visible = dom.redo.is_visible = false
+
+var LexState = function(lex){
+ this.fg = lex.fg;
+ this.bg = lex.bg;
+ this.char = lex.char;
+ this.opacity = lex.opacity;
+};
+
+var update_dom_visibility = function(type){
+ var el = dom[type]
+ if (el.is_visible){
+ if (stack[type].length === 0) {
+ el.classList.add('hidden')
+ el.is_visible = false
+ }
+ } else if (stack[type].length > 0){
+ el.classList.remove('hidden')
+ el.is_visible = true
+ }
+}
+var update_dom = function(){
+ update_dom_visibility('undo')
+ update_dom_visibility('redo')
+}
+
+// state is an undo or redo state that might contain these props
+// { lexs: {'0,0': LexState, ...}, // for sparse lex changes (eg brush, fill)
+// focus: {x:, y: },
+// size: {w:, h: },
+// rects: [{x:, y:, w:, h:, lexs: [LexState, ...]}, ...]
+// }
+var new_state = function(){
+ var state = {lexs:{}};
+ save_focus(canvas.focus_x, canvas.focus_y, state)
+ return state
+}
+var new_redo = function(){
+ return new_state()
+}
+var new_undo = function(){
+ current_undo = new_state()
+ stack.redo = []
+ stack.undo.push(current_undo)
+ if (stack.undo.length > max_states) stack.undo.shift();
+ update_dom()
+ return current_undo
+}
+
+var save_focus = function(x, y, state){
+ state = state || current_undo
+ state.focus = {x:x, y:y}
+}
+var save_size = function(w, h, state){
+ state = state || current_undo
+ state.size = {w:w, h:h};
+}
+// the reason for stringifying the x y coords is so that each
+// coordinate is saved only once in an undo state.
+// otherwise there would be problems with, eg, a brush stroke
+// that passed over the same grid cell twice.
+var save_lex = function(x, y, lex, state){
+ // var start = Date.now()
+ state = state || current_undo
+ var lexs = state.lexs;
+ var xy = x + "," + y;
+ if (xy in lexs) return;
+ lexs[xy] = new LexState(lex)
+ // undotimetotal += Date.now() - start
+}
+var save_focused_lex = function(state){
+ state = state || current_undo
+ var x = canvas.focus_x
+ var y = canvas.focus_y
+ save_lex(x, y, canvas.aa[y][x], state)
+}
+var save_rect = function(xpos, ypos, w, h, state){
+ if (w === 0 || h === 0) return;
+ state = state || current_undo;
+ state.rects = state.rects || []
+ var aa = canvas.aa;
+ var rect = {x: xpos, y: ypos, w: w, h: h, lexs: []}
+ var lexs = rect.lexs
+ var xlen = xpos + w
+ var ylen = ypos + h
+ for (var y = ypos; y < ylen; y++){
+ var aay = aa[y]
+ for (var x = xpos; x < xlen; x++){
+ lexs.push(new LexState(aay[x]))
+ }
+ }
+ state.rects.push(rect)
+}
+var save_resize = function(w, h, old_w, old_h, state){
+ state = state || current_undo
+ save_size(old_w, old_h, state)
+ if (old_w > w){
+ // .---XX
+ // | XX
+ // |___XX
+ save_rect(w, 0, old_w - w, old_h, state)
+ if (old_h > h){
+ // .----.
+ // | |
+ // XXXX_|
+ save_rect(0, h, w, old_h - h, state)
+ }
+ } else if (old_h > h){
+ // .----.
+ // | |
+ // XXXXXX
+ save_rect(0, h, old_w, old_h - h, state)
+ }
+}
+
+var restore_state = function(state){
+ // all redo states will have a cached undo state on them
+ // an undo state might have a cached redo state
+ // if it doesn't have one, generate one
+ var make_redo = ! ('redo' in state || 'undo' in state);
+ var aa = canvas.aa
+ var lex, lexs;
+
+ if (make_redo){
+ state.redo = new_redo()
+
+ // copy saved rects that intersect with current canvas size
+ // important to do this before resizing canvas
+ if ('rects' in state){
+ for (var ri=0, rect; rect=state.rects[ri]; ri++){
+ if (rect.x >= canvas.w ||
+ rect.y >= canvas.h) continue;
+ var w = Math.min(rect.w, canvas.w - rect.x)
+ var h = Math.min(rect.h, canvas.h - rect.y)
+ save_rect(rect.x, rect.y, w, h, state.redo)
+ }
+ }
+ if ('size' in state){
+ save_resize(state.size.w, state.size.h, canvas.w, canvas.h, state.redo)
+ }
+ }
+
+ if ('size' in state){
+ canvas.resize(state.size.w, state.size.h, true);
+ }
+
+ if ('rects' in state){
+ for (var ri=0, rect; rect=state.rects[ri]; ri++){
+ lexs = rect.lexs
+ for (var li=0; lex=lexs[li]; li++){
+ var x = (li % rect.w) + rect.x
+ var y = ((li / rect.w)|0) + rect.y
+ aa[y][x].assign(lex)
+ }
+ }
+ }
+
+ lexs = state.lexs
+ for (var key in lexs){
+ var xy = key.split(',');
+ lex = aa[xy[1]][xy[0]]
+ if (make_redo)
+ save_lex(xy[0], xy[1], lex, state.redo)
+ lex.assign(lexs[key])
+ }
+
+ if ('focus' in state){
+ canvas.focus_x = state.focus.x
+ canvas.focus_y = state.focus.y
+ if (current_canvas === canvas){
+ canvas.focus()
+ }
+ }
+}
+
+var undo = function(){
+ var state = stack.undo.pop();
+ if (!state) return;
+
+ restore_state(state)
+
+ // now take the applied undo state and store it on the redo state
+ // and push the redo state to the redo stack
+ state.redo.undo = state
+ stack.redo.push(state.redo)
+ delete state.redo
+
+ update_dom()
+}
+
+var redo = function(){
+ var state = stack.redo.pop();
+ if (!state) return;
+
+ restore_state(state)
+
+ state.undo.redo = state
+ stack.undo.push(state.undo)
+ delete state.undo
+
+ update_dom()
+}
+
+return {
+ stack: stack,
+ new: new_undo,
+// new_redo: new_redo,
+ save_focus: save_focus,
+ save_size: save_size,
+ save_lex: save_lex,
+ save_focused_lex: save_focused_lex,
+ save_rect: save_rect,
+ save_resize: save_resize,
+ undo: undo,
+ redo: redo
+}
+
+})()
diff --git a/js/unicode.js b/js/unicode.js
new file mode 100644
index 0000000..2199750
--- /dev/null
+++ b/js/unicode.js
@@ -0,0 +1,543 @@
+var unicode = (function(){
+ var UNICODE_BLOCK_LIST = [
+ 0x0020, 0x007F, "Basic Latin",
+ 0x0080, 0x00FF, "Latin-1 Supplement",
+ 0x0100, 0x017F, "Latin Extended-A",
+ 0x0180, 0x024F, "Latin Extended-B",
+ 0x0250, 0x02AF, "IPA Extensions",
+ 0x02B0, 0x02FF, "Spacing Modifier Letters",
+ 0x0300, 0x036F, "Combining Diacritical Marks",
+ 0x0370, 0x03FF, "Greek and Coptic",
+ 0x0400, 0x04FF, "Cyrillic",
+ 0x0500, 0x052F, "Cyrillic Supplement",
+ 0x0530, 0x058F, "Armenian",
+ 0x0590, 0x05FF, "Hebrew",
+ 0x0600, 0x06FF, "Arabic",
+ 0x0700, 0x074F, "Syriac",
+ 0x0750, 0x077F, "Arabic Supplement",
+ 0x0780, 0x07BF, "Thaana",
+ 0x07C0, 0x07FF, "NKo",
+ 0x0800, 0x083F, "Samaritan",
+ 0x0840, 0x085F, "Mandaic",
+ 0x08A0, 0x08FF, "Arabic Extended-A",
+ 0x0900, 0x097F, "Devanagari",
+ 0x0980, 0x09FF, "Bengali",
+ 0x0A00, 0x0A7F, "Gurmukhi",
+ 0x0A80, 0x0AFF, "Gujarati",
+ 0x0B00, 0x0B7F, "Oriya",
+ 0x0B80, 0x0BFF, "Tamil",
+ 0x0C00, 0x0C7F, "Telugu",
+ 0x0C80, 0x0CFF, "Kannada",
+ 0x0D00, 0x0D7F, "Malayalam",
+ 0x0D80, 0x0DFF, "Sinhala",
+ 0x0E00, 0x0E7F, "Thai",
+ 0x0E80, 0x0EFF, "Lao",
+ 0x0F00, 0x0FFF, "Tibetan",
+ 0x1000, 0x109F, "Myanmar",
+ 0x10A0, 0x10FF, "Georgian",
+ 0x1100, 0x11FF, "Hangul Jamo",
+ 0x1200, 0x137F, "Ethiopic",
+ 0x1380, 0x139F, "Ethiopic Supplement",
+ 0x13A0, 0x13FF, "Cherokee",
+ 0x1400, 0x167F, "Unified Canadian Aboriginal Syllabics",
+ 0x1680, 0x169F, "Ogham",
+ 0x16A0, 0x16FF, "Runic",
+ 0x1700, 0x171F, "Tagalog",
+ 0x1720, 0x173F, "Hanunoo",
+ 0x1740, 0x175F, "Buhid",
+ 0x1760, 0x177F, "Tagbanwa",
+ 0x1780, 0x17FF, "Khmer",
+ 0x1800, 0x18AF, "Mongolian",
+ 0x18B0, 0x18FF, "Unified Canadian Aboriginal Syllabics Extended",
+ 0x1900, 0x194F, "Limbu",
+ 0x1950, 0x197F, "Tai Le",
+ 0x1980, 0x19DF, "New Tai Lue",
+ 0x19E0, 0x19FF, "Khmer Symbols",
+ 0x1A00, 0x1A1F, "Buginese",
+ 0x1A20, 0x1AAF, "Tai Tham",
+ 0x1AB0, 0x1AFF, "Combining Diacritical Marks Extended",
+ 0x1B00, 0x1B7F, "Balinese",
+ 0x1B80, 0x1BBF, "Sundanese",
+ 0x1BC0, 0x1BFF, "Batak",
+ 0x1C00, 0x1C4F, "Lepcha",
+ 0x1C50, 0x1C7F, "Ol Chiki",
+ 0x1CC0, 0x1CCF, "Sundanese Supplement",
+ 0x1CD0, 0x1CFF, "Vedic Extensions",
+ 0x1D00, 0x1D7F, "Phonetic Extensions",
+ 0x1D80, 0x1DBF, "Phonetic Extensions Supplement",
+ 0x1DC0, 0x1DFF, "Combining Diacritical Marks Supplement",
+ 0x1E00, 0x1EFF, "Latin Extended Additional",
+ 0x1F00, 0x1FFF, "Greek Extended",
+ 0x2000, 0x206F, "General Punctuation",
+ 0x2070, 0x209F, "Superscripts and Subscripts",
+ 0x20A0, 0x20CF, "Currency Symbols",
+ 0x20D0, 0x20FF, "Combining Diacritical Marks for Symbols",
+ 0x2100, 0x214F, "Letterlike Symbols",
+ 0x2150, 0x218F, "Number Forms",
+ 0x2190, 0x21FF, "Arrows",
+ 0x2200, 0x22FF, "Mathematical Operators",
+ 0x2300, 0x23FF, "Miscellaneous Technical",
+ 0x2400, 0x243F, "Control Pictures",
+ 0x2440, 0x245F, "Optical Character Recognition",
+ 0x2460, 0x24FF, "Enclosed Alphanumerics",
+ 0x2500, 0x257F, "Box Drawing",
+ 0x2580, 0x259F, "Block Elements",
+ 0x25A0, 0x25FF, "Geometric Shapes",
+ 0x2600, 0x26FF, "Miscellaneous Symbols",
+ 0x2700, 0x27BF, "Dingbats",
+ 0x27C0, 0x27EF, "Miscellaneous Mathematical Symbols-A",
+ 0x27F0, 0x27FF, "Supplemental Arrows-A",
+ 0x2800, 0x28FF, "Braille Patterns",
+ 0x2900, 0x297F, "Supplemental Arrows-B",
+ 0x2980, 0x29FF, "Miscellaneous Mathematical Symbols-B",
+ 0x2A00, 0x2AFF, "Supplemental Mathematical Operators",
+ 0x2B00, 0x2BFF, "Miscellaneous Symbols and Arrows",
+ 0x2C00, 0x2C5F, "Glagolitic",
+ 0x2C60, 0x2C7F, "Latin Extended-C",
+ 0x2C80, 0x2CFF, "Coptic",
+ 0x2D00, 0x2D2F, "Georgian Supplement",
+ 0x2D30, 0x2D7F, "Tifinagh",
+ 0x2D80, 0x2DDF, "Ethiopic Extended",
+ 0x2DE0, 0x2DFF, "Cyrillic Extended-A",
+ 0x2E00, 0x2E7F, "Supplemental Punctuation",
+ 0x2E80, 0x2EFF, "CJK Radicals Supplement",
+ 0x2F00, 0x2FDF, "Kangxi Radicals",
+ 0x2FF0, 0x2FFF, "Ideographic Description Characters",
+ 0x3000, 0x303F, "CJK Symbols and Punctuation",
+ 0x3040, 0x309F, "Hiragana",
+ 0x30A0, 0x30FF, "Katakana",
+ 0x3100, 0x312F, "Bopomofo",
+ 0x3130, 0x318F, "Hangul Compatibility Jamo",
+ 0x3190, 0x319F, "Kanbun",
+ 0x31A0, 0x31BF, "Bopomofo Extended",
+ 0x31C0, 0x31EF, "CJK Strokes",
+ 0x31F0, 0x31FF, "Katakana Phonetic Extensions",
+ 0x3200, 0x32FF, "Enclosed CJK Letters and Months",
+ 0x3300, 0x33FF, "CJK Compatibility",
+ 0x3400, 0x4DBF, "CJK Unified Ideographs Extension A",
+ 0x4DC0, 0x4DFF, "Yijing Hexagram Symbols",
+ 0x4E00, 0x9FFF, "CJK Unified Ideographs",
+ 0xA000, 0xA48F, "Yi Syllables",
+ 0xA490, 0xA4CF, "Yi Radicals",
+ 0xA4D0, 0xA4FF, "Lisu",
+ 0xA500, 0xA63F, "Vai",
+ 0xA640, 0xA69F, "Cyrillic Extended-B",
+ 0xA6A0, 0xA6FF, "Bamum",
+ 0xA700, 0xA71F, "Modifier Tone Letters",
+ 0xA720, 0xA7FF, "Latin Extended-D",
+ 0xA800, 0xA82F, "Syloti Nagri",
+ 0xA830, 0xA83F, "Common Indic Number Forms",
+ 0xA840, 0xA87F, "Phags-pa",
+ 0xA880, 0xA8DF, "Saurashtra",
+ 0xA8E0, 0xA8FF, "Devanagari Extended",
+ 0xA900, 0xA92F, "Kayah Li",
+ 0xA930, 0xA95F, "Rejang",
+ 0xA960, 0xA97F, "Hangul Jamo Extended-A",
+ 0xA980, 0xA9DF, "Javanese",
+ 0xA9E0, 0xA9FF, "Myanmar Extended-B",
+ 0xAA00, 0xAA5F, "Cham",
+ 0xAA60, 0xAA7F, "Myanmar Extended-A",
+ 0xAA80, 0xAADF, "Tai Viet",
+ 0xAAE0, 0xAAFF, "Meetei Mayek Extensions",
+ 0xAB00, 0xAB2F, "Ethiopic Extended-A",
+ 0xAB30, 0xAB6F, "Latin Extended-E",
+ 0xABC0, 0xABFF, "Meetei Mayek",
+ 0xAC00, 0xD7AF, "Hangul Syllables",
+ 0xD7B0, 0xD7FF, "Hangul Jamo Extended-B",
+ 0xD800, 0xDB7F, "High Surrogates",
+ 0xDB80, 0xDBFF, "High Private Use Surrogates",
+ 0xDC00, 0xDFFF, "Low Surrogates",
+ 0xE000, 0xF8FF, "Private Use Area",
+ 0xF900, 0xFAFF, "CJK Compatibility Ideographs",
+ 0xFB00, 0xFB4F, "Alphabetic Presentation Forms",
+ 0xFB50, 0xFDFF, "Arabic Presentation Forms-A",
+ 0xFE00, 0xFE0F, "Variation Selectors",
+ 0xFE10, 0xFE1F, "Vertical Forms",
+ 0xFE20, 0xFE2F, "Combining Half Marks",
+ 0xFE30, 0xFE4F, "CJK Compatibility Forms",
+ 0xFE50, 0xFE6F, "Small Form Variants",
+ 0xFE70, 0xFEFF, "Arabic Presentation Forms-B",
+ 0xFF00, 0xFFEF, "Halfwidth and Fullwidth Forms",
+ 0xFFF0, 0xFFFF, "Specials",
+ 0x10000, 0x1007F, "Linear B Syllabary",
+ 0x10080, 0x100FF, "Linear B Ideograms",
+ 0x10100, 0x1013F, "Aegean Numbers",
+ 0x10140, 0x1018F, "Ancient Greek Numbers",
+ 0x10190, 0x101CF, "Ancient Symbols",
+ 0x101D0, 0x101FF, "Phaistos Disc",
+ 0x10280, 0x1029F, "Lycian",
+ 0x102A0, 0x102DF, "Carian",
+ 0x102E0, 0x102FF, "Coptic Epact Numbers",
+ 0x10300, 0x1032F, "Old Italic",
+ 0x10330, 0x1034F, "Gothic",
+ 0x10350, 0x1037F, "Old Permic",
+ 0x10380, 0x1039F, "Ugaritic",
+ 0x103A0, 0x103DF, "Old Persian",
+ 0x10400, 0x1044F, "Deseret",
+ 0x10450, 0x1047F, "Shavian",
+ 0x10480, 0x104AF, "Osmanya",
+ 0x10500, 0x1052F, "Elbasan",
+ 0x10530, 0x1056F, "Caucasian Albanian",
+ 0x10600, 0x1077F, "Linear A",
+ 0x10800, 0x1083F, "Cypriot Syllabary",
+ 0x10840, 0x1085F, "Imperial Aramaic",
+ 0x10860, 0x1087F, "Palmyrene",
+ 0x10880, 0x108AF, "Nabataean",
+ 0x10900, 0x1091F, "Phoenician",
+ 0x10920, 0x1093F, "Lydian",
+ 0x10980, 0x1099F, "Meroitic Hieroglyphs",
+ 0x109A0, 0x109FF, "Meroitic Cursive",
+ 0x10A00, 0x10A5F, "Kharoshthi",
+ 0x10A60, 0x10A7F, "Old South Arabian",
+ 0x10A80, 0x10A9F, "Old North Arabian",
+ 0x10AC0, 0x10AFF, "Manichaean",
+ 0x10B00, 0x10B3F, "Avestan",
+ 0x10B40, 0x10B5F, "Inscriptional Parthian",
+ 0x10B60, 0x10B7F, "Inscriptional Pahlavi",
+ 0x10B80, 0x10BAF, "Psalter Pahlavi",
+ 0x10C00, 0x10C4F, "Old Turkic",
+ 0x10E60, 0x10E7F, "Rumi Numeral Symbols",
+ 0x11000, 0x1107F, "Brahmi",
+ 0x11080, 0x110CF, "Kaithi",
+ 0x110D0, 0x110FF, "Sora Sompeng",
+ 0x11100, 0x1114F, "Chakma",
+ 0x11150, 0x1117F, "Mahajani",
+ 0x11180, 0x111DF, "Sharada",
+ 0x111E0, 0x111FF, "Sinhala Archaic Numbers",
+ 0x11200, 0x1124F, "Khojki",
+ 0x112B0, 0x112FF, "Khudawadi",
+ 0x11300, 0x1137F, "Grantha",
+ 0x11480, 0x114DF, "Tirhuta",
+ 0x11580, 0x115FF, "Siddham",
+ 0x11600, 0x1165F, "Modi",
+ 0x11680, 0x116CF, "Takri",
+ 0x118A0, 0x118FF, "Warang Citi",
+ 0x11AC0, 0x11AFF, "Pau Cin Hau",
+ 0x12000, 0x123FF, "Cuneiform",
+ 0x12400, 0x1247F, "Cuneiform Numbers and Punctuation",
+ 0x13000, 0x1342F, "Egyptian Hieroglyphs",
+ 0x16800, 0x16A3F, "Bamum Supplement",
+ 0x16A40, 0x16A6F, "Mro",
+ 0x16AD0, 0x16AFF, "Bassa Vah",
+ 0x16B00, 0x16B8F, "Pahawh Hmong",
+ 0x16F00, 0x16F9F, "Miao",
+ 0x1B000, 0x1B0FF, "Kana Supplement",
+ 0x1BC00, 0x1BC9F, "Duployan",
+ 0x1BCA0, 0x1BCAF, "Shorthand Format Controls",
+ 0x1D000, 0x1D0FF, "Byzantine Musical Symbols",
+ 0x1D100, 0x1D1FF, "Musical Symbols",
+ 0x1D200, 0x1D24F, "Ancient Greek Musical Notation",
+ 0x1D300, 0x1D35F, "Tai Xuan Jing Symbols",
+ 0x1D360, 0x1D37F, "Counting Rod Numerals",
+ 0x1D400, 0x1D7FF, "Mathematical Alphanumeric Symbols",
+ 0x1E800, 0x1E8DF, "Mende Kikakui",
+ 0x1EE00, 0x1EEFF, "Arabic Mathematical Alphabetic Symbols",
+ 0x1F000, 0x1F02F, "Mahjong Tiles",
+ 0x1F030, 0x1F09F, "Domino Tiles",
+ 0x1F0A0, 0x1F0FF, "Playing Cards",
+ 0x1F100, 0x1F1FF, "Enclosed Alphanumeric Supplement",
+ 0x1F200, 0x1F2FF, "Enclosed Ideographic Supplement",
+ 0x1F300, 0x1F5FF, "Miscellaneous Symbols and Pictographs",
+ 0x1F600, 0x1F64F, "Emoticons",
+ 0x1F650, 0x1F67F, "Ornamental Dingbats",
+ 0x1F680, 0x1F6FF, "Transport and Map Symbols",
+ 0x1F700, 0x1F77F, "Alchemical Symbols",
+ 0x1F780, 0x1F7FF, "Geometric Shapes Extended",
+ 0x1F800, 0x1F8FF, "Supplemental Arrows-C",
+ 0x20000, 0x2A6DF, "CJK Unified Ideographs Extension B",
+ 0x2A700, 0x2B73F, "CJK Unified Ideographs Extension C",
+ 0x2B740, 0x2B81F, "CJK Unified Ideographs Extension D",
+ 0x2F800, 0x2FA1F, "CJK Compatibility Ideographs Supplement",
+ 0xE0000, 0xE007F, "Tags",
+ 0xE0100, 0xE01EF, "Variation Selectors Supplement",
+ 0xF0000, 0xFFFFF, "Supplementary Private Use Area-A",
+ 0x100000, 0x10FFFF, "Supplementary Private Use Area-B",
+ ]
+ var UNICODE_BLOCK_COUNT = UNICODE_BLOCK_LIST.length / 3
+ var UNICODE_LOOKUP = {}
+ for (var i = 0, len = UNICODE_BLOCK_LIST.length; i < len; i += 3) {
+ UNICODE_LOOKUP[ UNICODE_BLOCK_LIST[i+2] ] = [ UNICODE_BLOCK_LIST[i], UNICODE_BLOCK_LIST[i+1] ]
+ }
+
+ function index (j) {
+ return [ UNICODE_BLOCK_LIST[j*3], UNICODE_BLOCK_LIST[j*3+1], UNICODE_BLOCK_LIST[j*3+2], [] ]
+ }
+ function range(m,n){
+ if (m > n) return []
+ var a = new Array (n-m)
+ for (var i = 0, j = m; j <= n; i++, j++) {
+ a[i] = j
+ }
+ return a
+ }
+ function paginate (a, n){
+ var aa = [], ai, i = 0
+ while (i < 100) {
+ ai = a.slice(i * n, (i+1) * n)
+ if (! ai.length) break
+ aa.push(ai)
+ i++
+ }
+ return aa
+ }
+ function block (name, n){
+ var b = UNICODE_LOOKUP[name]
+ if (! b) return ""
+ return range.apply(null, b).map(function(n){ return String.fromCharCode(n) })
+ }
+ function entities (a) {
+ return a.map(function(k){ return "" + k.join(";") + ";" }).join(" ")
+ }
+ function findGroups (chars){
+ var groups = [], row, list
+ for (var i = 0, j = -1, next = -1, len = chars.length; i < len; i++) {
+ if (chars[i] < next) {
+ list.push(chars[i])
+ continue
+ }
+ do {
+ j += 1
+ next = UNICODE_BLOCK_LIST[(j+1)*3]
+ } while (chars[i] > next)
+ row = index(j)
+ list = row[3]
+ groups.push( row )
+ }
+ return groups
+ }
+
+ // encodes unicode characters as escaped utf16 - \xFFFF
+ // encodes ONLY non-ascii characters
+ function escapeToUtf16 (txt) {
+ var escaped_txt = "", kode
+ for (var i = 0; i < txt.length; i++) {
+ kode = txt.charCodeAt(i)
+ if (kode > 0x7f) {
+ kode = kode.toString(16)
+ switch (kode.length) {
+ case 2:
+ kode = "0" + kode
+ case 3:
+ kode = "0" + kode
+ }
+ escaped_txt += "\\u" + kode
+ }
+ else {
+ escaped_txt += txt[i]
+ }
+ }
+ return escaped_txt
+ }
+
+ // encodes unicode characters as escaped bytes - \xFF
+ // encodes ONLY non-ascii characters
+ function escapeToEscapedBytes (txt) {
+ var escaped_txt = "", kode, utf8_bytes
+ for (var i = 0; i < txt.length; i++) {
+ kode = txt.charCodeAt(i)
+ if (kode > 0x7f) {
+ utf8_bytes = convertUnicodeCodePointToUtf8Bytes(kode)
+ escaped_txt += convertBytesToEscapedString(utf8_bytes, 16)
+ }
+ else {
+ escaped_txt += txt[i]
+ }
+ }
+ return escaped_txt
+ }
+
+ // encodes unicode characters as escaped bytes - \xFF
+ // encodes an ENTIRE string
+ function escapeAllToEscapedBytes(str, base) {
+ var unicode_codes = convertStringToUnicodeCodePoints(str);
+ var data_bytes = convertUnicodeCodePointsToBytes(unicode_codes);
+ return convertBytesToEscapedString(data_bytes, 16);
+ }
+ // [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] => '\xE3\x81\x82\xE3\x81\x84'
+ // [ 0343, 0201, 0202, 0343, 0201, 0204 ] => '\343\201\202\343\201\204'
+ function convertBytesToEscapedString(data_bytes, base) {
+ var escaped = '';
+ for (var i = 0; i < data_bytes.length; ++i) {
+ var prefix = (base == 16 ? "\\x" : "\\");
+ var num_digits = base == 16 ? 2 : 3;
+ var escaped_byte = prefix + formatNumber(data_bytes[i], base, num_digits)
+ escaped += escaped_byte;
+ }
+ return escaped;
+ }
+ // [ 0x3042, 0x3044 ] => [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ]
+ function convertUnicodeCodePointsToBytes(unicode_codes) {
+ var utf8_bytes = [];
+ for (var i = 0; i < unicode_codes.length; ++i) {
+ var bytes = convertUnicodeCodePointToUtf8Bytes(unicode_codes[i]);
+ utf8_bytes = utf8_bytes.concat(bytes);
+ }
+ return utf8_bytes;
+ }
+ // 0x3042 => [ 0xE3, 0x81, 0x82 ]
+ function convertUnicodeCodePointToUtf8Bytes(unicode_code) {
+ var utf8_bytes = [];
+ if (unicode_code < 0x80) { // 1-byte
+ utf8_bytes.push(unicode_code);
+ } else if (unicode_code < (1 << 11)) { // 2-byte
+ utf8_bytes.push((unicode_code >>> 6) | 0xC0);
+ utf8_bytes.push((unicode_code & 0x3F) | 0x80);
+ } else if (unicode_code < (1 << 16)) { // 3-byte
+ utf8_bytes.push((unicode_code >>> 12) | 0xE0);
+ utf8_bytes.push(((unicode_code >> 6) & 0x3f) | 0x80);
+ utf8_bytes.push((unicode_code & 0x3F) | 0x80);
+ } else if (unicode_code < (1 << 21)) { // 4-byte
+ utf8_bytes.push((unicode_code >>> 18) | 0xF0);
+ utf8_bytes.push(((unicode_code >> 12) & 0x3F) | 0x80);
+ utf8_bytes.push(((unicode_code >> 6) & 0x3F) | 0x80);
+ utf8_bytes.push((unicode_code & 0x3F) | 0x80);
+ }
+ return utf8_bytes;
+ }
+ // "ã‚ã„" => [ 0x3042, 0x3044 ]
+ function convertStringToUnicodeCodePoints(str) {
+ var surrogate_1st = 0;
+ var unicode_codes = [];
+ for (var i = 0; i < str.length; ++i) {
+ var utf16_code = str.charCodeAt(i);
+ if (surrogate_1st != 0) {
+ if (utf16_code >= 0xDC00 && utf16_code <= 0xDFFF) {
+ var surrogate_2nd = utf16_code;
+ var unicode_code = (surrogate_1st - 0xD800) * (1 << 10) + (1 << 16) +
+ (surrogate_2nd - 0xDC00);
+ unicode_codes.push(unicode_code);
+ } else {
+ // Malformed surrogate pair ignored.
+ }
+ surrogate_1st = 0;
+ } else if (utf16_code >= 0xD800 && utf16_code <= 0xDBFF) {
+ surrogate_1st = utf16_code;
+ } else {
+ unicode_codes.push(utf16_code);
+ }
+ }
+ return unicode_codes;
+ }
+ // 0xff => "ff"
+ // 0xff => "377"
+ function formatNumber(number, base, num_digits) {
+ var str = number.toString(base).toUpperCase();
+ for (var i = str.length; i < num_digits; ++i) {
+ str = "0" + str;
+ }
+ return str;
+ }
+
+ // convert \xFF\xFF\xFF to unicode
+ function unescapeFromEscapedBytes (str) {
+ var data_bytes = convertEscapedBytesToBytes(str);
+ var unicode_codes = convertUtf8BytesToUnicodeCodePoints(data_bytes);
+ return convertUnicodeCodePointsToString(unicode_codes);
+ }
+ // r'\xE3\x81\x82\xE3\x81\x84' => [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ]
+ // r'\343\201\202\343\201\204' => [ 0343, 0201, 0202, 0343, 0201, 0204 ]
+ function convertEscapedBytesToBytes(str) {
+ var parts = str.split("\\x");
+ parts.shift(); // Trim the first element.
+ var codes = [];
+ var max = Math.pow(2, 8);
+ for (var i = 0; i < parts.length; ++i) {
+ var code = parseInt(parts[i], 16);
+ if (code >= 0 && code < max) {
+ codes.push(code);
+ } else {
+ // Malformed code ignored.
+ }
+ }
+ return codes;
+ }
+ // [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] => [ 0x3042, 0x3044 ]
+ function convertUtf8BytesToUnicodeCodePoints(utf8_bytes) {
+ var unicode_codes = [];
+ var unicode_code = 0;
+ var num_followed = 0;
+ for (var i = 0; i < utf8_bytes.length; ++i) {
+ var utf8_byte = utf8_bytes[i];
+ if (utf8_byte >= 0x100) {
+ // Malformed utf8 byte ignored.
+ } else if ((utf8_byte & 0xC0) == 0x80) {
+ if (num_followed > 0) {
+ unicode_code = (unicode_code << 6) | (utf8_byte & 0x3f);
+ num_followed -= 1;
+ } else {
+ // Malformed UTF-8 sequence ignored.
+ }
+ } else {
+ if (num_followed == 0) {
+ unicode_codes.push(unicode_code);
+ } else {
+ // Malformed UTF-8 sequence ignored.
+ }
+ if (utf8_byte < 0x80){ // 1-byte
+ unicode_code = utf8_byte;
+ num_followed = 0;
+ } else if ((utf8_byte & 0xE0) == 0xC0) { // 2-byte
+ unicode_code = utf8_byte & 0x1f;
+ num_followed = 1;
+ } else if ((utf8_byte & 0xF0) == 0xE0) { // 3-byte
+ unicode_code = utf8_byte & 0x0f;
+ num_followed = 2;
+ } else if ((utf8_byte & 0xF8) == 0xF0) { // 4-byte
+ unicode_code = utf8_byte & 0x07;
+ num_followed = 3;
+ } else {
+ // Malformed UTF-8 sequence ignored.
+ }
+ }
+ }
+ if (num_followed == 0) {
+ unicode_codes.push(unicode_code);
+ } else {
+ // Malformed UTF-8 sequence ignored.
+ }
+ unicode_codes.shift(); // Trim the first element.
+ return unicode_codes;
+ }
+ // [ 0x3042, 0x3044 ] => [ 0x3042, 0x3044 ]
+ // [ 0xD840, 0xDC0B ] => [ 0x2000B ] // A surrogate pair.
+ function convertUnicodeCodePointsToUtf16Codes(unicode_codes) {
+ var utf16_codes = [];
+ for (var i = 0; i < unicode_codes.length; ++i) {
+ var unicode_code = unicode_codes[i];
+ if (unicode_code < (1 << 16)) {
+ utf16_codes.push(unicode_code);
+ } else {
+ var first = ((unicode_code - (1 << 16)) / (1 << 10)) + 0xD800;
+ var second = (unicode_code % (1 << 10)) + 0xDC00;
+ utf16_codes.push(first)
+ utf16_codes.push(second)
+ }
+ }
+ return utf16_codes;
+ }
+ // [ 0x3042, 0x3044 ] => "ã‚ã„"
+ function convertUnicodeCodePointsToString(unicode_codes) {
+ var utf16_codes = convertUnicodeCodePointsToUtf16Codes(unicode_codes);
+ return convertUtf16CodesToString(utf16_codes);
+ }
+ // [ 0x3042, 0x3044 ] => "ã‚ã„"
+ function convertUtf16CodesToString(utf16_codes) {
+ var unescaped = '';
+ for (var i = 0; i < utf16_codes.length; ++i) {
+ unescaped += String.fromCharCode(utf16_codes[i]);
+ }
+ return unescaped;
+ }
+
+ return {
+ raw: UNICODE_BLOCK_LIST,
+ lookup: UNICODE_LOOKUP,
+ index: index,
+ range: range,
+ block: block,
+ findGroups: findGroups,
+ paginate: paginate,
+ escapeToEscapedBytes: escapeToEscapedBytes,
+ unescapeFromEscapedBytes: unescapeFromEscapedBytes,
+ }
+})()
diff --git a/js/upload.js b/js/upload.js
new file mode 100644
index 0000000..d60ae54
--- /dev/null
+++ b/js/upload.js
@@ -0,0 +1,83 @@
+var upload = (function(){
+ var el = document.getElementById("upload_input")
+ var button = document.getElementById("upload_button")
+ var uploading = false
+
+ function upload(blob, filename, tag, ascii){
+ if (uploading) return
+ filename = filename || get_filename()
+ tag = tag || "shader"
+
+ button.innerHTML = "uploading..."
+ button.className = "uploading"
+
+ uploading = true
+
+ uploadImage({
+ blob: blob,
+ ascii: ascii,
+ filename: filename,
+ username: user.username,
+ tag: tag,
+ success: function(data){
+
+ // data.url
+ // data.filesize
+ // data.success
+
+ console.log(data);
+ el.style.display = "block"
+ el.value = data.url
+ el.focus()
+ setCaretToPos(el, 0)
+ button.innerHTML = "upload"
+ button.className = ""
+ uploading = false
+ },
+ error: function(data){
+ console.log(data)
+ console.log("error uploading: " + data.error)
+ button.innerHTML = "upload"
+ button.className = ""
+ uploading = false
+ }
+ });
+ }
+
+ function uploadImage(opt){
+ if (! opt.blob || ! opt.filename) return;
+
+ opt.username = opt.username || "";
+ opt.success = opt.success || noop;
+ opt.error = opt.error || noop;
+
+ var form = new FormData();
+
+ form.append("username", opt.username);
+ form.append("filename", opt.filename);
+ form.append("qqfile", opt.blob);
+ form.append("tag", opt.tag);
+ if (opt.ascii) {
+ form.append("ascii", opt.ascii);
+ }
+
+ var req = new XMLHttpRequest();
+ req.open("POST", "/cgi-bin/im/shader/upload");
+ req.onload = function(event) {
+ if (req.status == 200) {
+ var res = JSON.parse(req.responseText);
+ if (res.success) {
+ opt.success(res);
+ }
+ else {
+ opt.error(res);
+ }
+ } else {
+ opt.error({ success: false, error: req.status });
+ }
+ };
+ req.send(form);
+ }
+
+ return upload
+})()
diff --git a/js/user.js b/js/user.js
new file mode 100644
index 0000000..1546db9
--- /dev/null
+++ b/js/user.js
@@ -0,0 +1,67 @@
+var user = (function(){
+
+ var user = {}
+ var el = document.getElementById("username_input")
+
+ user.init = function(){
+ user.load()
+ user.bind()
+ }
+ user.bind = function(){
+ el.addEventListener("input", user.save)
+ }
+ user.load = function(){
+ user.username = user.getCookie()
+ if (! user.username) {
+ user.username = '00' + randint(9876876)
+ user.setCookie(user.username)
+ }
+ if (!user.username.match(/^00/)) {
+ el.value = user.username
+ }
+ }
+ user.prefs = new function(){}
+ user.prefs.get = function (key){
+ return localStorage.getItem("im.prefs." + key)
+ }
+ user.prefs.set = function (key,value){
+ return localStorage.setItem("im.prefs." + key, value)
+ }
+ user.sanitize = function(){
+ return el.value.replace(/[^-_ a-zA-Z0-9]/g,"")
+ }
+ user.getCookie = function () {
+ var username = localStorage.getItem("im.name") || "";
+ if (document.cookie && ! username.length) {
+ var cookies = document.cookie.split(";")
+ for (i in cookies) {
+ var cookie = cookies[i].split("=")
+ if (cookie[0].indexOf("imname") !== -1) {
+ if (cookie[1] !== 'false' && cookie[1] !== 'undefined' && cookie[1].length) {
+ return cookie[1]
+ }
+ }
+ }
+ }
+ return username
+ }
+ var timeout
+ user.save = function(){
+ clearTimeout(timeout)
+ timeout = setTimeout(function(){
+ var username = user.sanitize()
+ if (username != user.username) user.setCookie(username);
+ })
+ }
+ user.setCookie = function(username){
+ if (!user.username.match(/^00/)) {
+ console.log("setting to " + username)
+ }
+ document.cookie = "imname="+username+";path=/;domain=.asdf.us;max-age=1086400"
+ localStorage.setItem("im.name", username);
+ }
+
+ user.init()
+
+ return user
+})()
diff --git a/js/util.js b/js/util.js
new file mode 100644
index 0000000..ca97630
--- /dev/null
+++ b/js/util.js
@@ -0,0 +1,199 @@
+if (window.$) {
+ $.fn.int = function(){ return parseInt($(this).val(),10) }
+ $.fn.float = function(){ return parseFloat($(this).val()) }
+ $.fn.string = function(){ return trim($(this).val()) }
+ $.fn.enable = function() { return $(this).attr("disabled",null) }
+ $.fn.disable = function() { return $(this).attr("disabled","disabled") }
+}
+
+function noop(){}
+function trim(s){ return s.replace(/^\s+/,"").replace(/\s+$/,"") }
+
+var E = Math.E
+var PI = Math.PI
+var PHI = (1+Math.sqrt(5))/2
+var TWO_PI = PI*2
+var LN10 = Math.LN10
+function clamp(n,a,b){ return n= 0.5 ? -1 : 1 }
+function randnullsign(){ var r = random(); return r < 0.333 ? -1 : r < 0.666 ? 0 : 1 }
+
+function xrandom(exp){ return Math.pow(Math.random(), exp) }
+function xrand(exp,n){ return (xrandom(exp)*n) }
+function xrandint(exp,n){ return rand(exp,n)|0 }
+function xrandrange(exp,a,b){ return a + xrand(exp,b-a) }
+
+function choice(a){ return a[randint(a.length)] }
+function deg(n){ return n*180/PI }
+function rad(n){ return n*PI/180 }
+function xor(a,b){ a=!!a; b=!!b; return (a||b) && !(a&&b) }
+function mod(n,m){ n = n % m; return n < 0 ? (m + n) : n }
+function modi(n,m){ return floor(mod(n,m)) }
+function dist(x0,y0,x1,y1){ return sqrt(pow(x1-x0,2)+pow(y1-y0,2)) }
+function angle(x0,y0,x1,y1){ return atan2(y1-y0,x1-x0) }
+function avg(m,n,a){ return (m*(a-1)+n)/a }
+function quantize(a,b){ return floor(a/b)*b }
+function quantile(a,b){ return floor(a/b) }
+
+function pixel(x,y){ return 4*(mod(y,actual_h)*actual_w+mod(x,actual_w)) }
+function rgbpixel(d,x,y){
+ var p = pixel(~~x,~~y)
+ r = d[p]
+ g = d[p+1]
+ b = d[p+2]
+ a = d[p+3]
+}
+function fit(d,x,y){ rgbpixel(d,x*actual_w/w,y*actual_h/h) }
+
+function step(a, b){
+ return (b >= a) + 0
+ // ^^ bool -> int
+}
+
+function julestep (a,b,n) {
+ return clamp(norm(n,a,b), 0.0, 1.0);
+}
+
+// hermite curve apparently
+function smoothstep(min,max,n){
+ var t = clamp((n - min) / (max - min), 0.0, 1.0);
+ return t * t * (3.0 - 2.0 * t)
+}
+
+function toArray(a){ return Array.prototype.slice.call(a) }
+function shuffle(a){
+ for (var i = a.length; i > 0; i--){
+ var r = randint(i)
+ var swap = a[i-1]
+ a[i-1] = a[r]
+ a[r] = swap
+ }
+ return a
+}
+function reverse(a){
+ var reversed = []
+ for (var i = 0, _len = a.length-1; i <= _len; i++){
+ reversed[i] = a[_len-i]
+ }
+ return reversed
+}
+function deinterlace(a){
+ var odd = [], even = []
+ for (var i = 0, _len = a.length; i < _len; i++) {
+ if (i % 2) even.push(a[i])
+ else odd.push(a[i])
+ }
+ return [even, odd]
+}
+function weave(a){
+ var aa = deinterlace(a)
+ var b = []
+ aa[0].forEach(function(el){ b.push(el) })
+ reverse(aa[1]).forEach(function(el){ b.push(el) })
+ return b
+}
+function cssRule (selector, declaration) {
+ var x = document.styleSheets, y = x.length-1;
+ x[y].insertRule(selector+"{"+declaration+"}", x[y].cssRules.length);
+}
+
+// easing functions
+function circular (t) { return Math.sqrt( 1 - ( --t * t ) ) }
+function quadratic (t) { return t * ( 2 - t ) }
+function back (t) {
+ var b = 4;
+ return ( t = t - 1 ) * t * ( ( b + 1 ) * t + b ) + 1;
+}
+function bounce (t) {
+ if (t >= 1) return 1;
+ if ( ( t /= 1 ) < ( 1 / 2.75 ) ) {
+ return 7.5625 * t * t;
+ } else if ( t < ( 2 / 2.75 ) ) {
+ return 7.5625 * ( t -= ( 1.5 / 2.75 ) ) * t + 0.75;
+ } else if ( t < ( 2.5 / 2.75 ) ) {
+ return 7.5625 * ( t -= ( 2.25 / 2.75 ) ) * t + 0.9375;
+ } else {
+ return 7.5625 * ( t -= ( 2.625 / 2.75 ) ) * t + 0.984375;
+ }
+}
+function elastic (t) {
+ var f = 0.22,
+ e = 0.4;
+
+ if ( t === 0 ) { return 0; }
+ if ( t == 1 ) { return 1; }
+
+ return ( e * Math.pow( 2, - 10 * t ) * Math.sin( ( t - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );
+}
+
+Model=function a(b,c,d,e){function f(){var a=this,f={};a.on=function(a,b){(f[a]||
+(f[a]=[])).push(b)},a.trigger=function(a,b){for(var c=f[a],d=0;c&&d-1;)d.splice(c
+,1);f[a]=b?d:[]};for(c in b)d=b[c],a[c]=typeof d=="function"?function(){return(
+d=this.apply(a,arguments))===e?a:d}.bind(d):d;a.init&&a.init.apply(a,arguments)
+}return f.extend=function(f){d={};for(c in b)d[c]=b[c];for(c in f)d[c]=f[c],b[c
+]!==e&&(d["__"+c]=b[c]);return a(d)},f},typeof module=="object"&&(module.exports
+=Model); // c-{{{-<
+
+function defaults (dest, src) {
+ dest = dest || {}
+ for (var i in src) {
+ dest[i] = typeof dest[i] == 'undefined' ? src[i] : dest[i]
+ }
+ return dest
+}
+
+function setSelectionRange(input, selectionStart, selectionEnd) {
+ if (input.setSelectionRange) {
+ input.focus();
+ input.setSelectionRange(selectionStart, selectionEnd);
+ }
+ else if (input.createTextRange) {
+ var range = input.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', selectionEnd);
+ range.moveStart('character', selectionStart);
+ range.select();
+ }
+}
+function setCaretToPos(input, pos) {
+ setSelectionRange(input, pos, pos);
+}
+
+// Naive useragent detection pattern
+var is_iphone = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i))
+var is_ipad = (navigator.userAgent.match(/iPad/i))
+var is_android = (navigator.userAgent.match(/Android/i))
+var is_mobile = is_iphone || is_ipad || is_android
+var is_desktop = ! is_mobile;
diff --git a/js/vendor/FileSaver.js b/js/vendor/FileSaver.js
new file mode 100644
index 0000000..0f596ab
--- /dev/null
+++ b/js/vendor/FileSaver.js
@@ -0,0 +1,232 @@
+/* FileSaver.js
+ * A saveAs() FileSaver implementation.
+ * 2013-10-21
+ *
+ * By Eli Grey, http://eligrey.com
+ * License: X11/MIT
+ * See LICENSE.md
+ */
+
+/*global self */
+/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
+ plusplus: true */
+
+/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
+
+var saveAs = saveAs
+ || (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
+ || (function(view) {
+ "use strict";
+ var
+ doc = view.document
+ // only get URL when necessary in case BlobBuilder.js hasn't overridden it yet
+ , get_URL = function() {
+ return view.URL || view.webkitURL || view;
+ }
+ , URL = view.URL || view.webkitURL || view
+ , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
+ , can_use_save_link = !view.externalHost && "download" in save_link
+ , click = function(node) {
+ var event = doc.createEvent("MouseEvents");
+ event.initMouseEvent(
+ "click", true, false, view, 0, 0, 0, 0, 0
+ , false, false, false, false, 0, null
+ );
+ node.dispatchEvent(event);
+ }
+ , webkit_req_fs = view.webkitRequestFileSystem
+ , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
+ , throw_outside = function (ex) {
+ (view.setImmediate || view.setTimeout)(function() {
+ throw ex;
+ }, 0);
+ }
+ , force_saveable_type = "application/octet-stream"
+ , fs_min_size = 0
+ , deletion_queue = []
+ , process_deletion_queue = function() {
+ var i = deletion_queue.length;
+ while (i--) {
+ var file = deletion_queue[i];
+ if (typeof file === "string") { // file is an object URL
+ URL.revokeObjectURL(file);
+ } else { // file is a File
+ file.remove();
+ }
+ }
+ deletion_queue.length = 0; // clear queue
+ }
+ , dispatch = function(filesaver, event_types, event) {
+ event_types = [].concat(event_types);
+ var i = event_types.length;
+ while (i--) {
+ var listener = filesaver["on" + event_types[i]];
+ if (typeof listener === "function") {
+ try {
+ listener.call(filesaver, event || filesaver);
+ } catch (ex) {
+ throw_outside(ex);
+ }
+ }
+ }
+ }
+ , FileSaver = function(blob, name) {
+ // First try a.download, then web filesystem, then object URLs
+ var
+ filesaver = this
+ , type = blob.type
+ , blob_changed = false
+ , object_url
+ , target_view
+ , get_object_url = function() {
+ var object_url = get_URL().createObjectURL(blob);
+ deletion_queue.push(object_url);
+ return object_url;
+ }
+ , dispatch_all = function() {
+ dispatch(filesaver, "writestart progress write writeend".split(" "));
+ }
+ // on any filesys errors revert to saving with object URLs
+ , fs_error = function() {
+ // don't create more object URLs than needed
+ if (blob_changed || !object_url) {
+ object_url = get_object_url(blob);
+ }
+ if (target_view) {
+ target_view.location.href = object_url;
+ } else {
+ window.open(object_url, "_blank");
+ }
+ filesaver.readyState = filesaver.DONE;
+ dispatch_all();
+ }
+ , abortable = function(func) {
+ return function() {
+ if (filesaver.readyState !== filesaver.DONE) {
+ return func.apply(this, arguments);
+ }
+ };
+ }
+ , create_if_not_found = {create: true, exclusive: false}
+ , slice
+ ;
+ filesaver.readyState = filesaver.INIT;
+ if (!name) {
+ name = "download";
+ }
+ if (can_use_save_link) {
+ object_url = get_object_url(blob);
+ // FF for Android has a nasty garbage collection mechanism
+ // that turns all objects that are not pure javascript into 'deadObject'
+ // this means `doc` and `save_link` are unusable and need to be recreated
+ // `view` is usable though:
+ doc = view.document;
+ save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a");
+ save_link.href = object_url;
+ save_link.download = name;
+ var event = doc.createEvent("MouseEvents");
+ event.initMouseEvent(
+ "click", true, false, view, 0, 0, 0, 0, 0
+ , false, false, false, false, 0, null
+ );
+ save_link.dispatchEvent(event);
+ filesaver.readyState = filesaver.DONE;
+ dispatch_all();
+ return;
+ }
+ // Object and web filesystem URLs have a problem saving in Google Chrome when
+ // viewed in a tab, so I force save with application/octet-stream
+ // http://code.google.com/p/chromium/issues/detail?id=91158
+ if (view.chrome && type && type !== force_saveable_type) {
+ slice = blob.slice || blob.webkitSlice;
+ blob = slice.call(blob, 0, blob.size, force_saveable_type);
+ blob_changed = true;
+ }
+ // Since I can't be sure that the guessed media type will trigger a download
+ // in WebKit, I append .download to the filename.
+ // https://bugs.webkit.org/show_bug.cgi?id=65440
+ if (webkit_req_fs && name !== "download") {
+ name += ".download";
+ }
+ if (type === force_saveable_type || webkit_req_fs) {
+ target_view = view;
+ }
+ if (!req_fs) {
+ fs_error();
+ return;
+ }
+ fs_min_size += blob.size;
+ req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
+ fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
+ var save = function() {
+ dir.getFile(name, create_if_not_found, abortable(function(file) {
+ file.createWriter(abortable(function(writer) {
+ writer.onwriteend = function(event) {
+ target_view.location.href = file.toURL();
+ deletion_queue.push(file);
+ filesaver.readyState = filesaver.DONE;
+ dispatch(filesaver, "writeend", event);
+ };
+ writer.onerror = function() {
+ var error = writer.error;
+ if (error.code !== error.ABORT_ERR) {
+ fs_error();
+ }
+ };
+ "writestart progress write abort".split(" ").forEach(function(event) {
+ writer["on" + event] = filesaver["on" + event];
+ });
+ writer.write(blob);
+ filesaver.abort = function() {
+ writer.abort();
+ filesaver.readyState = filesaver.DONE;
+ };
+ filesaver.readyState = filesaver.WRITING;
+ }), fs_error);
+ }), fs_error);
+ };
+ dir.getFile(name, {create: false}, abortable(function(file) {
+ // delete file if it already exists
+ file.remove();
+ save();
+ }), abortable(function(ex) {
+ if (ex.code === ex.NOT_FOUND_ERR) {
+ save();
+ } else {
+ fs_error();
+ }
+ }));
+ }), fs_error);
+ }), fs_error);
+ }
+ , FS_proto = FileSaver.prototype
+ , saveAs = function(blob, name) {
+ return new FileSaver(blob, name);
+ }
+ ;
+ FS_proto.abort = function() {
+ var filesaver = this;
+ filesaver.readyState = filesaver.DONE;
+ dispatch(filesaver, "abort");
+ };
+ FS_proto.readyState = FS_proto.INIT = 0;
+ FS_proto.WRITING = 1;
+ FS_proto.DONE = 2;
+
+ FS_proto.error =
+ FS_proto.onwritestart =
+ FS_proto.onprogress =
+ FS_proto.onwrite =
+ FS_proto.onabort =
+ FS_proto.onerror =
+ FS_proto.onwriteend =
+ null;
+
+ view.addEventListener("unload", process_deletion_queue, false);
+ return saveAs;
+}(this.self || this.window || this.content));
+// `self` is undefined in Firefox for Android content script context
+// while `this` is nsIContentFrameMessageManager
+// with an attribute `content` that corresponds to the window
+
+if (typeof module !== 'undefined') module.exports = saveAs;
\ No newline at end of file
diff --git a/js/vendor/colorcode.js b/js/vendor/colorcode.js
new file mode 100644
index 0000000..1d035d3
--- /dev/null
+++ b/js/vendor/colorcode.js
@@ -0,0 +1,551 @@
+!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var o;"undefined"!=typeof window?o=window:"undefined"!=typeof global?o=global:"undefined"!=typeof self&&(o=self),o.colorcode=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o cp437 function by sheetjs
+// edited from https://github.com/SheetJS/js-codepage/blob/master/bits/437.js
+var cp437 = (function(){ var d = "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ", D = [], e = {}; for(var i=0;i!=d.length;++i) { if(d.charCodeAt(i) !== 0xFFFD) e[d[i]] = i; D[i] = d.charAt(i); } return {"enc": e, "dec": D }; })();
+
+var render_char = function(font, char_value, ctx, ctx_x, ctx_y){
+ char_value = cp437.enc[String.fromCharCode(char_value)] | 0;
+ var sheet_x = (char_value % font.sheet_w_in_chars) * font.char_w
+ var sheet_y = ((char_value / font.sheet_w_in_chars) |0) * font.char_h
+ ctx.drawImage(font.sheet,
+ sheet_x|0, sheet_y|0, font.char_w, font.char_h,
+ ctx_x|0, ctx_y|0, font.char_w, font.char_h)
+
+}
+
+for (var i=0, wh; wh=cp437s[i]; i++){
+ var font = {};
+ font.is_char_blank = require('../fontutil').is_char_blank;
+ font.render_char = render_char;
+ font.name = 'cp437_' + wh[0] + 'x' + wh[1];
+ font.sheet_url = './img/' + font.name + '.png'
+ font.sheet_w_in_chars = 16;
+ font.char_w = wh[0]
+ font.char_h = wh[1]
+ fonts[font.name] = font;
+}
+
+
+
+// window.cp437 = cp437;
+
+},{"../fontutil":7}],6:[function(require,module,exports){
+var fsexps = [[8,16,0],[8,15,1],[8,8,5]]
+var fonts = {};
+module.exports = fonts;
+
+var render_char = function(font, char_value, ctx, ctx_x, ctx_y, char){
+ var sheet_x = 0, sheet_y = 3;
+ if (char_value >= 0x20 && char_value <= 0x7e){ // ascii
+ sheet_x = (char_value - 0x20) * font.char_w_sheet
+ if (char.i){ // italic
+ sheet_y = 1 * font.char_h_sheet + 3
+ }
+ } else if (char_value >= 0x80 && char_value <= 0xff){ // latin-1
+ sheet_x = (char_value - 0x80) * font.char_w_sheet;
+ sheet_y = 2 * font.char_h_sheet + 3
+ } else if (char_value >= 0x0100 && char_value <= 0x017f){ // latin a
+ sheet_x = (char_value - 0x0100) * font.char_w_sheet;
+ sheet_y = 3 * font.char_h_sheet + 3
+ } else if (char_value >= 0x0180 && char_value <= 0x024f){ // latin b
+ sheet_x = (char_value - 0x0180) * font.char_w_sheet;
+ sheet_y = 4 * font.char_h_sheet + 3
+ } else if (char_value >= 0x2500 && char_value <= 0x25ff){ // geom
+ sheet_x = (char_value - 0x2500) * font.char_w_sheet;
+ sheet_y = 5 * font.char_h_sheet + 3
+ } else if (char_value >= 0x2600 && char_value <= 0x26ff){ // emoji
+ sheet_x = (char_value - 0x2600) * font.char_w_sheet;
+ sheet_y = 6 * font.char_h_sheet + 3
+ }
+
+ // var sheet_x = (char_value % font.sheet_w_in_chars) * font.char_w
+ // var sheet_y = ((char_value / font.sheet_w_in_chars) |0) * font.char_h + 3
+ ctx.drawImage(font.sheet,
+ sheet_x|0, (sheet_y|0) + font.y_adj, font.char_w, font.char_h,
+ ctx_x|0, ctx_y|0, font.char_w, font.char_h)
+
+}
+
+for (var i=0, wh; wh=fsexps[i]; i++){
+ var font = {
+ name: 'fixedsys_' + wh[0] + 'x' + wh[1],
+ sheet_url: './img/fsex-simple.png',
+ sheet_w_in_chars: 128,
+ char_w_sheet: 8,
+ char_h_sheet: 16,
+ char_w: wh[0],
+ char_h: wh[1],
+ y_adj: wh[2],
+ is_char_blank: require('../fontutil').is_char_blank,
+ render_char: render_char
+ }
+ fonts[font.name] = font
+}
+},{"../fontutil":7}],7:[function(require,module,exports){
+var util = {};
+module.exports = util;
+
+util.is_char_blank = function(char_value){
+ if (char_value === 32) return true;
+}
+
+util.render_char = function(font, char_value, ctx, ctx_x, ctx_y){
+ var sheet_x = (char_value % font.sheet_w_in_chars) * font.char_w
+ var sheet_y = ((char_value / font.sheet_w_in_chars) |0) * font.char_h
+ ctx.drawImage(font.sheet,
+ sheet_x|0, sheet_y|0, font.char_w, font.char_h,
+ ctx_x|0, ctx_y|0, font.char_w, font.char_h)
+
+}
+
+
+},{}],8:[function(require,module,exports){
+var char_color = '\x03';
+
+var make_colorcode_fgbg = function(fg, bg){
+ // pad numbers: this prevents irc parsing confusion
+ // when the character after the colorcode is a number
+ if (fg < 10) fg = "0" + fg;
+ if (bg < 10) bg = "0" + bg;
+ return char_color + fg + "," + bg
+}
+
+var colorcode_from_json = function(json, opts){
+ var out = "";
+ for (var li=0, line; line=json.lines[li]; li++){
+ for (var ci=0, char; char=line[ci]; ci++){
+ out += make_colorcode_fgbg(char.fg, char.bg)
+ out += String.fromCharCode(char.value)
+ }
+ out += "\n";
+ }
+ return out;
+}
+
+
+module.exports = colorcode_from_json;
+
+},{}],9:[function(require,module,exports){
+// default settings for fonts, colors, etc
+var style = {};
+module.exports = style;
+
+},{}],10:[function(require,module,exports){
+var char_color = '\x03';
+var regexp_color = /(^[\d]{1,2})?(?:,([\d]{1,2}))?/;
+
+var style_chars = {
+ '\x02': 'bold',
+ '\x1d': 'italic',
+ '\x1f': 'underline',
+ '\x0f': 'reset',
+ '\x16': 'inverse'
+};
+
+var Style = function(style){
+ this.b = style.b;
+ this.i = style.i;
+ this.u = style.u;
+ this.fg = style.fg;
+ this.bg = style.bg;
+};
+
+var style_fns = {};
+
+style_fns.bold = function(style){ style.b = !style.b };
+
+style_fns.italic = function(style){ style.i = !style.i };
+
+style_fns.underline = function(style){ style.u = !style.u };
+
+style_fns.inverse = function(style){
+ var tmp = style.fg;
+ style.fg = style.bg;
+ style.bg = tmp;
+};
+
+style_fns.reset = function(style, base_style){
+ style.b = base_style.b;
+ style.i = base_style.i;
+ style.u = base_style.u;
+ style.fg = base_style.fg;
+ style.bg = base_style.bg;
+};
+
+var colorcode_to_json = function(string, opts){
+ // looks like its already converted
+ if (typeof string === 'object' &&
+ 'lines' in string &&
+ 'w' in string &&
+ 'h' in string)
+ return string;
+
+
+ opts = opts || {};
+ var d = colorcode_to_json.defaults;
+
+ var base_style = {
+ b: "b" in opts ? opts.b : d.b,
+ i: "i" in opts ? opts.i : d.i,
+ u: "u" in opts ? opts.u : d.u,
+ fg: "fg" in opts ? opts.fg : d.fg,
+ bg: "bg" in opts ? opts.bg : d.bg
+ };
+
+ var lines_in = string.split(/\r?\n/);
+ var lines_out = [];
+ var w = 0, h = 0;
+
+ for (var i=0; i 0xD7FF && codePoint < 0xE000) {
+ // last char was a lead
+ if (leadSurrogate) {
+ // 2 leads in a row
+ if (codePoint < 0xDC00) {
+ if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+ leadSurrogate = codePoint
+ continue
+ } else {
+ // valid surrogate pair
+ codePoint = leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00 | 0x10000
+ leadSurrogate = null
+ }
+ } else {
+ // no lead yet
+
+ if (codePoint > 0xDBFF) {
+ // unexpected trail
+ if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+ continue
+ } else if (i + 1 === length) {
+ // unpaired lead
+ if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+ continue
+ } else {
+ // valid lead
+ leadSurrogate = codePoint
+ continue
+ }
+ }
+ } else if (leadSurrogate) {
+ // valid bmp char, but last char was a lead
+ if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
+ leadSurrogate = null
+ }
+
+ // encode utf8
+ if (codePoint < 0x80) {
+ if ((units -= 1) < 0) break
+ bytes.push(codePoint)
+ } else if (codePoint < 0x800) {
+ if ((units -= 2) < 0) break
+ bytes.push(
+ codePoint >> 0x6 | 0xC0,
+ codePoint & 0x3F | 0x80
+ )
+ } else if (codePoint < 0x10000) {
+ if ((units -= 3) < 0) break
+ bytes.push(
+ codePoint >> 0xC | 0xE0,
+ codePoint >> 0x6 & 0x3F | 0x80,
+ codePoint & 0x3F | 0x80
+ )
+ } else if (codePoint < 0x200000) {
+ if ((units -= 4) < 0) break
+ bytes.push(
+ codePoint >> 0x12 | 0xF0,
+ codePoint >> 0xC & 0x3F | 0x80,
+ codePoint >> 0x6 & 0x3F | 0x80,
+ codePoint & 0x3F | 0x80
+ )
+ } else {
+ throw new Error('Invalid code point')
+ }
+ }
+
+ return bytes
+}
+
+function utf8Slice (buf, start, end) {
+ var res = ''
+ var tmp = ''
+ end = Math.min(buf.length, end || Infinity)
+ start = start || 0;
+
+ for (var i = start; i < end; i++) {
+ if (buf[i] <= 0x7F) {
+ res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i])
+ tmp = ''
+ } else {
+ tmp += '%' + buf[i].toString(16)
+ }
+ }
+
+ return res + decodeUtf8Char(tmp)
+}
+
+function decodeUtf8Char (str) {
+ try {
+ return decodeURIComponent(str)
+ } catch (err) {
+ return String.fromCharCode(0xFFFD) // UTF 8 invalid char
+ }
+}
+
+TextEncoderLite.prototype.encode = function (str) {
+ var result;
+
+ if ('undefined' === typeof Uint8Array) {
+ result = utf8ToBytes(str);
+ } else {
+ result = new Uint8Array(utf8ToBytes(str));
+ }
+
+ return result;
+};
+
+TextDecoderLite.prototype.decode = function (bytes) {
+ return utf8Slice(bytes, 0, bytes.length);
+}
+
+}());
+
+if (typeof TextEncoder === 'undefined') TextEncoder = TextEncoderLite
+if (typeof TextDecoder === 'undefined') TextDecoder = TextDecoderLite