Write or draw as you like, then export to html and clear to write or draw again.
There's no way to maintain more than one file. There's no way to reimport the exported html.
MYSB7QNIZPXKWIWQBZWYIFY46HHKENB6VPYLO2ME2JSJVGRJPQ6QC E5FYDACSQNKJG4USM52I6C4KTN3U4Z47C4TK4QYC6RF2FFCZCYCAC F4QQIBEH24YBBOCBWZ3L7MR7AG7QNTYWQ76W5RDHKDALDKE757VQC TSK2OXU2FTB2X44SN73Z4W4O6IDV6G6V3QU5UVTAGQY52Z7AHECAC GTRSST7PQKKBFOJ7TR5SQJDWZU2SU4UABIEE2MS25QQ6OUPKXICAC HXJ2I2OHQVQOASWIHEXMXBXM5EPDD2UP6NKAILV3IXIMXPOKTHBAC KQWT45AA6OT3RTTBVIMG6SP5CI5EHQYDUQHMRGGG2XATQYAIZFGQC KAT5YGM6WUSRNXPTU56RP423SCHOZYNGYBY53LMJD5M5EAJHY54AC YD46MI2FYKOWUUVGYRRRSDEXYJUJKZO5RFVUBJBCNMZYVS3MXDAQC KKMFQDR43ZWVCDRHQLWWX3FCWCFA3ZSXYOBRJNPHUQZR2XPKWULAC NOMLSMKK3XOFEG74ITJRBTCZGS3XETGIFV4UJOJIPS324BC3IF7QC 5RDWSYK2YESTIEDMGOD2T7E4KCOA6DOM35ECMZT2XZT57JSCRJEQC OBRUGSND2YN3AKWN7DC2NDFH3GIWVRAHSNUR6MMJMSAVLS6T4BGAC ZQDQLLCLNA2GHTR32BP3CJ4J5M43BWJXELK2M6Y3LKUDMFIHB46QC 2CK5QI7WA7M4IVSACFGOJYAIDKRUTZVMMPSFWEJTUNMWTN7AX4NAC OTIBCAUJ3KDQJLVDN3A536DLZGNRYMGJLORZVR3WLCGXGO6UGO6AC VHQCNMARPMNBSIUFLJG7HVK4QGDNPCGNVFLHS3I4IGNVSV5MRLYQC R5QXEHUIZLELJGGCZAE7ATNS3CLRJ7JFRENMGH4XXH24C5WABZDQC 2L5MEZV344TOZLVY3432RHJFIRVXFD6O3GWLL5O4CV66BGAFTURQC LF7BWEG4DKQI7NMXMZC4LC2BE5PB42HK5PD6OYBNIDMAZBJASOKQC FS2ITYYHBLFT66YUC3ENPFYI2HOYHOVEPQIN7NQR6KF5MEK4NKZAC ZTMRQZSWUL6FJRI4C4H37MR2IMV22DB6KRGEOUNYRWW5CTAVQFKAC D4FEFHQCSILZFQ5VLWNXAIRZNUMCDNGJSM4UJ6T6FDMMIWYRYILQC PNBKVYZ4ANUAZNQN6KEWYNDF7552ROZPNAPRJE7Q6O7ZZJMJ3S3QC CZRMAMSBRVX26IXKHNPG6M3YSWMOZTM73X3XHAMBDSNETTFVRCUQC KQWIMWJ5VRAXM7SFNWDSBZMQ6ZE3CZQTKZHVM5ZQCW4RHPTI64MQC IDGP4BJZTKAD6ZO4RLAWYVN6IFCMIM76G6HJGPTE27K4D6CDBUHQC MLG2OGU7OBWWPX5TDJQWTDTHSTM75WIMAW57546C4XLEVZQOYJ7AC 34TC5SYKYVUCVIQM3GNVYURQAMIXX64IOSJ4TYBPSRDS65QLTHWAC EV36VCVF362E3QE22RO33TOCZRL3X7SJUDGVPL3YXISIR5LHK6JAC VC2CU2GGRIWXIFJELD5NAELDUIRY5S5LEAFJCM2A5P3CUBYF3Z3AC SBS2F7GRG4VYLB7DP2W6LPN5UEX6TK5DZFGCM2P4IVDUIUJRKJ7QC File_select_bar_value = nilFile_select_bar_text = nil
-- state machine:-- states:-- - editing: if Export_filename_value == nil-- - export: if Export_filename_value and not Export_filename_doublecheck-- - doublecheck: if Export_filename_doublecheck-- transitions:-- editing | press 'export' button -> export-- export | hit return ->-- | Export_filename_value doesn't exist -> editing [after writing]-- | Export_filename_value exists -> doublecheck-- doublecheck | hit return -> editing [after writing]-- doublecheck | hit some other key -> editing [cancel]Export_filename_value = nilExport_filename_text = nilExport_filename_doublecheck = false
Editor_state = edit.initialize_state(Margin_top, 45 + Margin_left, App.screen.width-Margin_right, Font_height, Line_height)
Editor_state = edit.initialize_state(Menu_bar_height + Margin_top, Margin_left, App.screen.width-Margin_right, Font_height, Line_height)
run.draw_file_select_button()
if not Export_filename_value thenrun.draw_buttons()elseif Export_filename_doublecheck thenlove.graphics.setColor(0,1,0)love.graphics.print(Export_filename_value..' already exists. Overwrite? Hit enter/return to confirm, any other key to cancel', 15, 5)end
function run.draw_file_select_button()button(Editor_state, 'open', {x=0, y=0, w=40,h=40, color={1,0.7,1},
function run.draw_buttons()local button_text = 'clear'local width = to_text(button_text):getWidth()local x=5button(Editor_state, button_text, {x=x, y=5, w=width+10,h=Line_height, color={1,0.7,1},icon = function(button_params)local x,y = button_params.x, button_params.ylocal w,h = button_params.w, button_params.hlove.graphics.setColor(0.4,0.4,0.7)love.graphics.rectangle('line', x,y, w,h, 5,5)love.graphics.setColor(0,0,0)love.graphics.print(button_text, x+5, y)end,onpress1 = function()Editor_state.lines = {{mode='text', data=''}}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}Editor_state.cursor1 = {line=1, pos=1}end,})x = x+width+10 + 10button_text = 'export'width = to_text(button_text):getWidth()button(Editor_state, button_text, {x=x, y=5, w=width+10,h=Line_height, color={1,0.7,1},
App.screen.draw(File_select_bar_text, text_start_x,y-5)Text.draw_cursor(Editor_state, text_start_x+App.width(File_select_bar_text),y-5)
App.screen.draw(Export_filename_text, text_start_x,y-5)Text.draw_cursor(Editor_state, text_start_x+App.width(Export_filename_text),y-5)
if File_select_bar_value then return endreturn edit.mouse_release(Editor_state, x,y, mouse_button)
if Export_filename_value then return endedit.mouse_release(Editor_state, x,y, mouse_button)
if File_select_bar_value thenFile_select_bar_value = File_select_bar_value..tFile_select_bar_text = nil
if Export_filename_doublecheck then-- keychord_press did the work; we'll just clean upExport_filename_value = nilExport_filename_text = nilExport_filename_doublecheck = falseelseif Export_filename_value thenExport_filename_value = Export_filename_value..tExport_filename_text = nil
if File_select_bar_value then
if Export_filename_doublecheck thenif chord == 'return' thenlocal outfilename = love.filesystem.getSourceBaseDirectory()..'/'..Export_filename_value..'.html'export(outfilename)Current_flash = 'exported'Current_flash_time = App.getTime()endelseif Export_filename_value then
run.switch_to_file(File_select_bar_value)File_select_bar_value = nilFile_select_bar_text = nil
local outfilename = love.filesystem.getSourceBaseDirectory()..'/'..Export_filename_value..'.html'if file_exists(outfilename) thenExport_filename_doublecheck = trueelseexport(outfilename)Export_filename_value = nilExport_filename_text = nilCurrent_flash = 'exported'Current_flash_time = App.getTime()end
local len = utf8.len(File_select_bar_value)local byte_offset = Text.offset(File_select_bar_value, len)File_select_bar_value = string.sub(File_select_bar_value, 1, byte_offset-1)File_select_bar_text = nil
local len = utf8.len(Export_filename_value)local byte_offset = Text.offset(Export_filename_value, len)Export_filename_value = string.sub(Export_filename_value, 1, byte_offset-1)Export_filename_text = nil
function run.switch_to_file(filename)-- first make sure to save edits on any existing fileif Editor_state.next_save thensave_to_disk(Editor_state)end-- handle people hitting enter without a filenameif filename == '' thenreturnend-- clear the slate for the new fileEditor_state.filename = love.filesystem.getSourceBaseDirectory()..'/'..filenameload_from_disk(Editor_state)Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}Editor_state.cursor1 = {line=1, pos=1}love.window.setTitle('lines.love - '..Editor_state.filename)end
base64 = require('base64')function export(filename)local outfile = io.open(filename, 'w')if outfile == nil thenerror('failed to create "'..filename)endfor _,line in ipairs(Editor_state.lines) doif line.mode == 'drawing' thenlocal svg_contents = export_drawing(line)outfile:write('<img src="data:image/svg+xml;base64,'..base64.encode(svg_contents)..'"/>')elseoutfile:write(line.data)endoutfile:write('<br/>\n')endoutfile:close()endfunction export_drawing(drawing)local out = {}table.insert(out, '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="128">\n')for _,shape in ipairs(drawing.shapes) doif shape.mode == 'freehand' thenexport_freehand(shape, out)elseif shape.mode == 'line' or shape.mode == 'manhattan' thenexport_line(drawing, shape, out)elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' thenexport_polygon(drawing, shape, out)elseif shape.mode == 'circle' thenexport_circle(drawing, shape, out)elseif shape.mode == 'arc' thenexport_circle_arc(drawing, shape, out)elseif shape.mode == 'deleted' then-- ignoreelseprint(shape.mode)assert(false)endtable.insert(out, '\n')endtable.insert(out, '</svg>\n')return table.concat(out)endfunction file_exists(filename)local f = io.open(filename)if f thenf:close()return trueendreturn falseendfunction export_freehand(shape, out)table.insert(out, ('<path stroke="black" fill="none" d="M %d %d L'):format(shape.points[1].x, shape.points[1].y))for i=2,#shape.points dotable.insert(out, (' %d %d'):format(shape.points[i].x, shape.points[i].y))endtable.insert(out, '"/>\n')endfunction export_line(drawing, shape, out)local p1 = drawing.points[shape.p1]local p2 = drawing.points[shape.p2]table.insert(out, ('<line stroke="black" x1="%d" y1="%d" x2="%d" y2="%d"/>\n'):format(p1.x, p1.y, p2.x, p2.y))export_point(p1, out)export_point(p2, out)endfunction export_polygon(drawing, shape, out)table.insert(out, '<polygon stroke="black" fill="none" points="')for _,p in ipairs(shape.vertices) dolocal vertex = drawing.points[p]table.insert(out, ('%d,%d, '):format(vertex.x, vertex.y))endtable.insert(out, '"/>\n')for _,p in ipairs(shape.vertices) doexport_point(drawing.points[p], out)endendfunction export_circle(drawing, shape, out)local center = drawing.points[shape.center]table.insert(out, ('<circle stroke="black" fill="none" cx="%d" cy="%d" r="%d"/>\n'):format(center.x, center.y, shape.radius))export_point(center, out)endfunction export_circle_arc(drawing, shape, out)local cx,cy = shape.center.x, shape.center.y-- angle 0local zx,zy = cx+shape.radius, cylocal sx,sy = geom.rotate(cx,cy, zx,zy, shape.start_angle)local ex,ey = geom.rotate(cx,cy, zx,zy, shape.end_angle)local sweep_flag = 0if shape.start_angle < shape.end_angle thensweep_flag = 1endtable.insert(out, ('<path stroke="black" fill="none" d="M %d %d A %d,%d 0 0 %d %d,%d"/>'):format(sx,sy, shape.radius,shape.radius, sweep_flag, ex,ey))export_point(shape.center, out)endfunction export_point(p, out)table.insert(out, ('<circle cx="%d" cy="%d" r="1"/>\n'):format(p.x, p.y))if p.name then-- couple of adjustments:-- * lines.love text coordinate starts at top left, but SVG starts at baseline.---- * lines.love renders labels at x,y both offset by 5px on the screen.-- While rendering SVG, however, we don't have pixels, only viewport coordinates. Seems to work well to just drop the increment.table.insert(out, ('<text fill="black" x="%d" y="%d">%s</text>\n'):format(p.x, p.y+Drawing_text_baseline_height, p.name))endendfunction basename(s)return string.gsub(s, "(.*/)(.*)", "%2")end
--[[base64 -- v1.5.3 public domain Lua base64 encoder/decoderno warranty implied; use at your own riskauthor: Ilya Kolbin (iskolbin@gmail.com)url: github.com/iskolbin/lbase64LICENSESee end of file for license information.--]]local base64 = {}function extract( v, from, width )local w = 0local flag = 2^fromfor i = 0, width-1 dolocal flag2 = flag + flagif v % flag2 >= flag thenw = w + 2^iendflag = flag2endreturn wendfunction base64.makeencoder( s62, s63, spad )local encoder = {}for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} doencoder[b64code] = char:byte()endreturn encoderendfunction base64.makedecoder( s62, s63, spad )local decoder = {}for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) dodecoder[charcode] = b64codeendreturn decoderendlocal DEFAULT_ENCODER = base64.makeencoder()local DEFAULT_DECODER = base64.makedecoder()local char, concat = string.char, table.concatfunction base64.encode( str, encoder, usecaching )encoder = encoder or DEFAULT_ENCODERlocal t, k, n = {}, 1, #strlocal lastn = n % 3local cache = {}for i = 1, n-lastn, 3 dolocal a, b, c = str:byte( i, i+2 )local v = a*0x10000 + b*0x100 + clocal sif usecaching thens = cache[v]if not s thens = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])cache[v] = sendelses = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])endt[k] = sk = k + 1endif lastn == 2 thenlocal a, b = str:byte( n-1, n )local v = a*0x10000 + b*0x100t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64])elseif lastn == 1 thenlocal v = str:byte( n )*0x10000t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64])endreturn concat( t )endfunction base64.decode( b64, decoder, usecaching )decoder = decoder or DEFAULT_DECODERlocal pattern = '[^%w%+%/%=]'if decoder thenlocal s62, s63for charcode, b64code in pairs( decoder ) doif b64code == 62 then s62 = charcodeelseif b64code == 63 then s63 = charcodeendendpattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) )endb64 = b64:gsub( pattern, '' )local cache = usecaching and {}local t, k = {}, 1local n = #b64local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0for i = 1, padding > 0 and n-4 or n, 4 dolocal a, b, c, d = b64:byte( i, i+3 )local sif usecaching thenlocal v0 = a*0x1000000 + b*0x10000 + c*0x100 + ds = cache[v0]if not s thenlocal v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))cache[v0] = sendelselocal v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))endt[k] = sk = k + 1endif padding == 1 thenlocal a, b, c = b64:byte( n-3, n-1 )local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40t[k] = char( extract(v,16,8), extract(v,8,8))elseif padding == 2 thenlocal a, b = b64:byte( n-3, n-2 )local v = decoder[a]*0x40000 + decoder[b]*0x1000t[k] = char( extract(v,16,8))endreturn concat( t )endreturn base64--[[------------------------------------------------------------------------------This software is available under 2 licenses -- choose whichever you prefer.------------------------------------------------------------------------------ALTERNATIVE A - MIT LicenseCopyright (c) 2018 Ilya KolbinPermission is hereby granted, free of charge, to any person obtaining a copy ofthis software and associated documentation files (the "Software"), to deal inthe Software without restriction, including without limitation the rights touse, copy, modify, merge, publish, distribute, sublicense, and/or sell copiesof the Software, and to permit persons to whom the Software is furnished to doso, subject to the following conditions:The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE.------------------------------------------------------------------------------ALTERNATIVE B - Public Domain (www.unlicense.org)This is free and unencumbered software released into the public domain.Anyone is free to copy, modify, publish, use, compile, sell, or distribute thissoftware, either in source code form or as a compiled binary, for any purpose,commercial or non-commercial, and by any means.In jurisdictions that recognize copyright laws, the author or authors of thissoftware dedicate any and all copyright interest in the software to the publicdomain. We make this dedication for the benefit of the public at large and tothe detriment of our heirs and successors. We intend this dedication to be anovert act of relinquishment in perpetuity of all present and future rights tothis software under copyright law.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN ANACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTIONWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.--------------------------------------------------------------------------------]]
An editor for plain text where you can also seamlessly insert line drawings.
Write or draw as much as you like, but only to a single scratch space.Designed for a tablet device, but usable anywhere.When you're done, export to html and clear the slate.etch.love is a compatible fork of [lines.love](http://akkartik.name/lines.html),an editor for plain text where you can also seamlessly insert line drawings.
http://akkartik.name/lines.htmlThis in-progress fork tries to fix some issues on mobile devices with touchscreens and more restrictive storage models:* File icons are hard to get a hold of, so this fork instead switches filesusing a hotkey (`ctrl+w`)* ... _(there will likely be others)_
By default, lines.love reads/writes the file `lines.txt` in a directoryspecific to this app (https://love2d.org/wiki/love.filesystem.getSourceBaseDirectory).
* Undo/redo may be sluggish in large files. Large files may grow sluggish inother ways. lines.love works well in all circumstances with files under50KB.