Template repo for tiny cross-platform apps that can be modified on phone, tablet or computer.

hypertext browser and some basic online help

Try browsing to the file '--help'.

https://akkartik.name/post/2025-03-08-devlog
https://akkartik.name/post/luaML2
https://akkartik.name/post/2025-04-04-devlog

+1694 -106
+10 -1
array.lua
··· 27 27 return false 28 28 end 29 29 30 + function array.all(arr, f) 31 + for i,x in ipairs(arr) do 32 + if not f(x) then 33 + return false 34 + end 35 + end 36 + return true 37 + end 38 + 30 39 -- like ipairs, but support an index to start iterating from 31 40 -- idx is inclusive; the first call to the iterator will return idx 32 41 function array.each(arr, idx) ··· 71 80 function(a, b) return a+b end) 72 81 end 73 82 74 - function product(arr, f) 83 + function array.product(arr, f) 75 84 return array.reduce(array.map(arr, f), 1, 76 85 function(a, b) return a*b end) 77 86 end
+1
defs/0011-on.initialize
··· 7 7 Font = love.graphics.newFont(Font_filename, Font_height) 8 8 end 9 9 love.graphics.setFont(Font) 10 + initialize_docimg_fonts() 10 11 Line_height = math.floor(Font_height*1.3) 11 12 Line_number_padding = Line_number_width*Font:getWidth('m') 12 13 if love.window.getSafeArea then
+8
defs/0127-one_time_load
··· 9 9 edit.clear(Current_pane.editor_state) 10 10 local dir = Current_pane.is_stash and Stash_directory or Directory 11 11 edit.load_file(Current_pane.editor_state, dir..Current_pane.filename) 12 + if is_docimg(Current_pane) then 13 + Current_pane.docimg_viewport = Viewports[Current_pane.filename] or { 14 + x=0, y=0, w=Safe_width, h=Safe_height, 15 + zoom=1.0, 16 + } 17 + clear_touch_state() -- decontaminate old viewport 18 + load_commands_from_docimg(Current_pane) 19 + end 12 20 -- Disable autosave; undo isn't accessible in mobile devices. 13 21 Current_pane.editor_state.filename = nil 14 22 -- Clear some other recent state
-11
defs/0198-draw_docimg
··· 1 1 draw_docimg = function() 2 - initialize_fonts() 3 - if Current_pane.commands == nil or Current_pane.commands.filename ~= Current_pane.filename then 4 - if Current_pane.docimg_viewport == nil then 5 - Current_pane.docimg_viewport = { 6 - x=0, y=0, w=Safe_width, h=Safe_height, 7 - zoom=1.0, 8 - } 9 - clear_touch_state() -- decontaminate old viewport 10 - end 11 - load_commands_from_docimg(Current_pane) 12 - end 13 2 for _,cmd in ipairs(Current_pane.commands) do 14 3 draw_command( 15 4 Current_pane.docimg_viewport,
+1 -1
defs/0201-load_commands_from_docimg
··· 7 7 if f == nil then error(err) end 8 8 Current_pane.commands = f() 9 9 Current_pane.commands.filename = Current_pane.filename 10 + infer_blocks(Current_pane.commands) 10 11 index_nodes(Current_pane.commands) 11 - infer_fonts(Current_pane.commands) 12 12 infer_edges(Current_pane.commands) 13 13 infer_arrows(Current_pane.commands) 14 14 infer_viewport(Current_pane.commands)
+5 -5
defs/0202-get_font defs/0202-scaled_font
··· 1 - get_font = function(cmd) 2 - local scaled_font_size = docimg_scale(Current_pane.docimg_viewport, cmd.font_size or 21) 1 + scaled_font = function(font_settings) 2 + font_settings = utils.deepcopy(font_settings) or {size=21} 3 + local scaled_font_size = docimg_scale(Current_pane.docimg_viewport, font_settings.size) 3 4 scaled_font_size = math.min(scaled_font_size, 96) 4 5 scaled_font_size = math.max(scaled_font_size, 8) 5 6 scaled_font_size = math.floor(scaled_font_size) ··· 8 9 assert(scaled_font_size > 8) 9 10 scaled_font_size = scaled_font_size - 1 10 11 end 11 - local scaled_font = Fonts[scaled_font_size] 12 - assert(scaled_font) 13 - return scaled_font 12 + font_settings.size = scaled_font_size 13 + return font_from_settings(font_settings) 14 14 end
-21
defs/0204-print_cmd
··· 1 - print_cmd = function(v, cmd) 2 - local font = cmd.scaled_font 3 - local old_font = love.graphics.getFont() 4 - love.graphics.setFont(font) 5 - if cmd.target then 6 - love.graphics.setColor(0,0,1) 7 - else 8 - love.graphics.setColor(0,0,0) 9 - end 10 - local x = docimg_vx(v, cmd.x+5) 11 - local y = docimg_vy(v, cmd.y+5) 12 - if type(cmd.data) ~= 'table' then 13 - love.graphics.print(cmd.data, x, y) 14 - else 15 - for _,line in ipairs(cmd.data) do 16 - love.graphics.print(line, x, y) 17 - y = y+docimg_scale(v, font:getHeight()+5) 18 - end 19 - end 20 - love.graphics.setFont(old_font) 21 - end
+4 -4
defs/0206-infer_viewport
··· 1 1 infer_viewport = function(cmds) 2 2 assert(#cmds > 0) 3 3 local xlo, ylo, xhi, yhi = cmds[1].x, cmds[1].y, cmds[1].x, cmds[1].y 4 + assert(xlo) 4 5 for _,cmd in ipairs(cmds) do 5 6 local b = bounds(cmd) 6 7 xlo = math.min(xlo, b.x) 7 8 ylo = math.min(ylo, b.y) 8 - xhi = math.max(xhi, b.w and b.x+b.w or b.x) 9 - yhi = math.max(yhi, b.y+b.h) 9 + xhi = math.max(xhi, b.x+b.width) 10 + yhi = math.max(yhi, b.y+b.height) 10 11 end 11 - Current_pane.docimg_viewport_border = {x=xlo, y=ylo, w=xhi-xlo, h=yhi-ylo} 12 - Current_pane.docimg_viewport_bounds = {x=xlo-Safe_width/2, y=ylo-Safe_height/2, w=xhi-xlo+Safe_width/2, h=yhi-ylo+Safe_height/2} 12 + Current_pane.docimg_viewport_bounds = {x=xlo-Safe_width/2, y=ylo-Safe_height/2, width=xhi-xlo+Safe_width/2, height=yhi-ylo+Safe_height/2} 13 13 end
+1 -1
defs/0208-centroid
··· 1 1 centroid = function(rect) 2 - return {sx=rect.x+rect.w/2, sy=rect.y+rect.h/2} 2 + return {sx=rect.x+rect.width/2, sy=rect.y+rect.height/2} 3 3 end
+4 -1
defs/0209-add_arrow
··· 1 1 add_arrow = function(arrows, s2, e2) 2 2 table.insert(arrows, {type='line', 3 3 x1=s2.sx, y1=s2.sy, 4 - x2=e2.sx, y2=e2.sy}) 4 + x2=e2.sx, y2=e2.sy, 5 + color=Default_foreground_color_slice}) 5 6 -- arrow head 6 7 local angle = angle(e2.sx, e2.sy, s2.sx, s2.sy) 7 8 -- e2.sx + D*cos(angle) == s2.sx ··· 14 15 x1=e2.sx, y1=e2.sy, 15 16 x2=e2.sx+arrow_len*math.cos(angle+d), 16 17 y2=e2.sy+arrow_len*math.sin(angle+d), 18 + color=Default_foreground_color_slice, 17 19 }) 18 20 table.insert(arrows, {type='line', 19 21 x1=e2.sx, y1=e2.sy, 20 22 x2=e2.sx+arrow_len*math.cos(angle-d), 21 23 y2=e2.sy+arrow_len*math.sin(angle-d), 24 + color=Default_foreground_color_slice, 22 25 }) 23 26 end
+8 -8
defs/0212-intersect_with_centroid
··· 3 3 -- collect nearest intersection with all 4 boundaries 4 4 local candidates = {} 5 5 local y = y_at_x(sx,sy, c.sx,c.sy, rect.x-10) 6 - if y and y >= rect.y-10 and y < rect.y+rect.h+10 then 6 + if y and y >= rect.y-10 and y < rect.y+rect.height+10 then 7 7 table.insert(candidates, {sx=rect.x-10, sy=y}) 8 8 end 9 - y = y_at_x(sx,sy, c.sx,c.sy, rect.x+rect.w+10) 10 - if y and y >= rect.y-10 and y < rect.y+rect.h+10 then 11 - table.insert(candidates, {sx=rect.x+rect.w+10, sy=y}) 9 + y = y_at_x(sx,sy, c.sx,c.sy, rect.x+rect.width+10) 10 + if y and y >= rect.y-10 and y < rect.y+rect.height+10 then 11 + table.insert(candidates, {sx=rect.x+rect.width+10, sy=y}) 12 12 end 13 13 local x = x_at_y(sx,sy, c.sx,c.sy, rect.y-10) 14 - if x and x >= rect.x-10 and x < rect.x+rect.w+10 then 14 + if x and x >= rect.x-10 and x < rect.x+rect.width+10 then 15 15 table.insert(candidates, {sx=x, sy=rect.y-10}) 16 16 end 17 - x = x_at_y(sx,sy, c.sx,c.sy, rect.y+rect.h+10) 18 - if x and x >= rect.x-10 and x < rect.x+rect.w+10 then 19 - table.insert(candidates, {sx=x, sy=rect.y+rect.h+10}) 17 + x = x_at_y(sx,sy, c.sx,c.sy, rect.y+rect.height+10) 18 + if x and x >= rect.x-10 and x < rect.x+rect.width+10 then 19 + table.insert(candidates, {sx=x, sy=rect.y+rect.height+10}) 20 20 end 21 21 if #candidates == 0 then 22 22 -- no intersection; just return the same point
+3 -10
defs/0218-draw_command
··· 1 1 draw_command = function(v, cmd) 2 - love.graphics.setColor(0,0,0) 2 + love.graphics.setColor(select_rgb(Default_background_color, cmd.color or Default_foreground_color_slice)) 3 3 if cmd.type == 'line' then 4 4 love.graphics.line( 5 5 docimg_vx(v, cmd.x1), 6 6 docimg_vy(v, cmd.y1), 7 7 docimg_vx(v, cmd.x2), 8 8 docimg_vy(v, cmd.y2)) 9 - elseif cmd.type == 'label' then 10 - print_cmd(v, cmd) 11 - else -- default type is a node, a box that can connect to arrows 12 - love.graphics.rectangle('line', 13 - docimg_vx(v, cmd.x), 14 - docimg_vy(v, cmd.y), 15 - docimg_scale(v, cmd.w), 16 - docimg_scale(v, cmd.h)) 17 - print_cmd(v, cmd) 9 + elseif cmd.type == 'text' or cmd.type == 'rows' or cmd.type == 'cols' then 10 + draw_block(v, cmd) 18 11 end 19 12 end
+2 -2
defs/0222-within_rect
··· 1 1 within_rect = function(rect, x,y) 2 - return within_range(x, rect.x, rect.x+rect.w) 3 - and within_range(y, rect.y, rect.y+rect.h) 2 + return within_range(x, rect.x, rect.x+rect.width) 3 + and within_range(y, rect.y, rect.y+rect.height) 4 4 end
+3 -13
defs/0227-send_press_to_docimg
··· 1 1 send_press_to_docimg = function(v, x,y) 2 2 local sx, sy = docimg_sx(v, x), docimg_sy(v, y) 3 3 for _,cmd in ipairs(Current_pane.commands) do 4 - if cmd.target and within_rect(cmd, sx,sy) then 5 - if Current_pane.history == nil then 6 - Current_pane.history = {} 4 + if cmd.type == 'text' or cmd.type == 'rows' or cmd.type == 'cols' then 5 + if press_on_block(cmd, sx,sy) then 6 + return true 7 7 end 8 - Viewports[Current_pane.filename] = utils.deepcopy(Current_pane.docimg_viewport) 9 - local history = Current_pane.history 10 - table.insert(history, Current_pane.filename) 11 - table.clear(Current_pane) 12 - Current_pane.history = history 13 - Current_pane.editor_state = code_editor_state() 14 - Current_pane.filename = cmd.target 15 - one_time_load() 16 - Current_pane.docimg_viewport = Viewports[Current_pane.filename] -- may be nil 17 - return true 18 8 end 19 9 end 20 10 end
-1
defs/0230-touch_move_on_docimg
··· 15 15 v.zoom = math.max(v.zoom, 0.5) 16 16 v.zoom = math.min(v.zoom, 3) 17 17 adjust_viewport(oldzoom) 18 - adjust_fonts() 19 18 elseif Touch_state.first then 20 19 pan_current_viewport(Touch_state.start[Touch_state.first], x,y) 21 20 end
+17
defs/0248-initialize_docimg_fonts
··· 1 + initialize_docimg_fonts = function() 2 + for _,sz in ipairs(Typographic_scale) do 3 + Fonts[sz] = { 4 + -- bold? 5 + [false]={ 6 + -- italic? 7 + [false]=love.graphics.newFont('fonts/Vera.ttf', sz), 8 + [true]=love.graphics.newFont('fonts/Vera-Italic.ttf', sz), 9 + }, 10 + [true]={ 11 + -- italic? 12 + [false]=love.graphics.newFont('fonts/Vera-Bold.ttf', sz), 13 + [true]=love.graphics.newFont('fonts/Vera-Bold-Italic.ttf', sz), 14 + }, 15 + } 16 + end 17 + end
-6
defs/0248-initialize_fonts
··· 1 - initialize_fonts = function() 2 - if #Fonts > 0 then return end 3 - for _,sz in ipairs(Typographic_scale) do 4 - Fonts[sz] = love.graphics.newFont(sz) 5 - end 6 - end
-5
defs/0249-infer_fonts
··· 1 - infer_fonts = function(cmds) 2 - for _,cmd in ipairs(cmds) do 3 - cmd.scaled_font = get_font(cmd) 4 - end 5 - end
-8
defs/0250-adjust_fonts
··· 1 - adjust_fonts = function() 2 - assert(Touch_state.zoom_time) 3 - if Current_time < Touch_state.zoom_time then return end 4 - Touch_state.zoom_time = Current_time + 0.1 5 - for _,cmd in ipairs(Current_pane.commands) do 6 - cmd.scaled_font = get_font(cmd) 7 - end 8 - end
+2 -2
defs/0252-bounds
··· 3 3 return { 4 4 x=math.min(cmd.x1, cmd.x2), 5 5 y=math.min(cmd.y1, cmd.y2), 6 - w=math.abs(cmd.x1 - cmd.x2), 7 - h=math.abs(cmd.y1 - cmd.y2), 6 + width=math.abs(cmd.x1 - cmd.x2), 7 + height=math.abs(cmd.y1 - cmd.y2), 8 8 } 9 9 else 10 10 return cmd
+2 -2
defs/0253-clamp_viewport_to_bounds
··· 1 1 clamp_viewport_to_bounds = function() 2 2 local v = Current_pane.docimg_viewport 3 3 local bounds = Current_pane.docimg_viewport_bounds 4 - v.x = math.min(v.x, bounds.x + bounds.w) 4 + v.x = math.min(v.x, bounds.x + bounds.width) 5 5 v.x = math.max(v.x, bounds.x) 6 - v.y = math.min(v.y, bounds.y + bounds.h) 6 + v.y = math.min(v.y, bounds.y + bounds.height) 7 7 v.y = math.max(v.y, bounds.y) 8 8 end
+46
defs/0258-set_attrs
··· 1 + set_attrs = function(v, block, attrs, x,y, orig_color, orig_font) 2 + if attrs.bg then 3 + orig_color = orig_color or {love.graphics.getColor()} 4 + if attrs.bg_rgb == nil then 5 + attrs.bg_rgb = docimg_color_to_rgb(attrs.bg) 6 + end 7 + love.graphics.setColor(attrs.bg_rgb) 8 + love.graphics.rectangle('fill', 9 + docimg_vx(v, x), 10 + docimg_vy(v, y), 11 + docimg_scale(v, block.width), 12 + docimg_scale(v, block.height)) 13 + love.graphics.setColor(orig_color) 14 + end 15 + if attrs.border then 16 + orig_color = orig_color or {love.graphics.getColor()} 17 + if attrs.border_rgb == nil then 18 + attrs.border_rgb = select_rgb(attrs.bg, attrs.border) 19 + end 20 + love.graphics.setColor(attrs.border_rgb) 21 + love.graphics.rectangle('line', 22 + docimg_vx(v, x), 23 + docimg_vy(v, y), 24 + docimg_scale(v, block.width), 25 + docimg_scale(v, block.height)) 26 + love.graphics.setColor(orig_color) 27 + end 28 + if attrs.color then 29 + orig_color = orig_color or {love.graphics.getColor()} 30 + if attrs.color_rgb == nil then 31 + attrs.color_rgb = select_rgb(attrs.bg, attrs.color) 32 + end 33 + love.graphics.setColor(attrs.color_rgb) 34 + elseif attrs.target then 35 + orig_color = orig_color or {love.graphics.getColor()} 36 + if attrs.color_rgb == nil then 37 + attrs.color_rgb = select_rgb(attrs.bg, Default_link_color) 38 + end 39 + love.graphics.setColor(Default_link_color) 40 + end 41 + if attrs.font then 42 + orig_font = orig_font or love.graphics.getFont() 43 + love.graphics.setFont(scaled_font(attrs.font)) 44 + end 45 + return orig_color, orig_font 46 + end
+34
defs/0259-draw_block
··· 1 + draw_block = function(v, block) 2 + local orig_color, orig_font 3 + if block.type == 'rows' then 4 + orig_color, orig_font = set_attrs(v, block, block, block.x, block.y, orig_color, orig_font) 5 + for _,row in ipairs(block) do 6 + draw_block(v, row) 7 + end 8 + elseif block.type == 'cols' then 9 + orig_color, orig_font = set_attrs(v, block, block, block.x, block.y, orig_color, orig_font) 10 + for _,col in ipairs(block) do 11 + draw_block(v, col) 12 + end 13 + elseif block.type == 'text' then 14 + orig_color, orig_font = set_attrs(v, block, block, block.x, block.y, orig_color, orig_font) 15 + for _,line in ipairs(block) do 16 + for _,screen_line_rect in ipairs(line.rects.screen_line_rects) do 17 + for _,char_rect in ipairs(screen_line_rect.char_rects) do 18 + set_attrs(v, char_rect, char_rect.attrs, char_rect.x, char_rect.y) 19 + if char_rect.data then 20 + love.graphics.print(char_rect.data, 21 + docimg_vx(v, char_rect.x), 22 + docimg_vy(v, char_rect.y)) 23 + end 24 + end 25 + end 26 + end 27 + elseif block.type == 'filler' then 28 + orig_color = set_attrs(v, block, block, block.x, block.y, orig_color) 29 + else 30 + assert(false, 'unknown type '..block.type) 31 + end 32 + if orig_font then love.graphics.setFont(orig_font) end 33 + if orig_color then love.graphics.setColor(orig_color) end 34 + end
+20
defs/0260-parse_all_inline_styles
··· 1 + parse_all_inline_styles = function(block) 2 + if block.type == 'rows' or block.type == 'cols' then 3 + for _,child in ipairs(block) do 4 + parse_all_inline_styles(child) 5 + end 6 + elseif block.type == 'text' then 7 + for i,line in ipairs(block) do 8 + if type(line) == 'string' then 9 + block[i] = {{line, attrs={}}} 10 + else 11 + assert(line.segments == nil) 12 + block[i] = parse_inline_styles(line[1], line.attrs) 13 + end 14 + end 15 + elseif block.type == 'filler' then 16 + -- nothing 17 + else 18 + assert(false, 'unknown type '..block.type) 19 + end 20 + end
+21
defs/0261-parse_inline_styles
··· 1 + -- populate inline styles into segments and map them to values in attrs 2 + parse_inline_styles = function(text, attrs) 3 + local segments = {} 4 + local offset = 1 5 + while offset <= #text do 6 + local styleStart, nameEnd, styleName = text:find("([%w_]+)@{", offset) 7 + if not styleStart then 8 + table.insert(segments, {text:sub(offset), attrs={}}) 9 + break 10 + end 11 + if styleStart > offset then 12 + table.insert(segments, {text:sub(offset, styleStart-1), attrs={}}) 13 + end 14 + local styleEnd = text:find("}", nameEnd) 15 + assert(styleEnd, "unbalanced inline style: "..text) 16 + local styledText = text:sub(nameEnd+1, styleEnd-1) 17 + table.insert(segments, {styledText, attrs=attrs[styleName] or {}}) 18 + offset = styleEnd+1 19 + end 20 + return segments 21 + end
+38
defs/0262-compute_fonts
··· 1 + -- postcondition: every segment of text has font set 2 + -- default_font can be overridden by internal fonts 3 + compute_fonts = function(block, default_font) 4 + if block.type == 'rows' or block.type == 'cols' then 5 + for _,child in ipairs(block) do 6 + compute_fonts(child, (block.default and block.default.font) or default_font) 7 + end 8 + elseif block.type == 'text' then 9 + if default_font == nil then 10 + -- nothing 11 + elseif not block.font then 12 + block.font = utils.deepcopy(default_font) 13 + elseif block.font and block.font.size then 14 + -- ignore default_font when changing font size 15 + elseif block.font and not block.font.size then 16 + -- inherit default font size 17 + block.font.size = default_font.size 18 + end 19 + assert(block.font, "could not determine font") 20 + assert(block.font.size, "could not determine font size") 21 + for _,line in ipairs(block) do 22 + for _,segment in ipairs(line) do 23 + local font = utils.deepcopy(block.font) 24 + if segment.attrs.font then 25 + assert(segment.attrs.font.size == nil, "inline styles can't change font size") 26 + for k,v in pairs(segment.attrs.font) do 27 + font[k] = v 28 + end 29 + end 30 + segment.attrs.font = font 31 + end 32 + end 33 + elseif block.type == 'filler' then 34 + -- nothing 35 + else 36 + assert(false, 'unknown type '..block.type) 37 + end 38 + end
+28
defs/0263-compute_colors
··· 1 + -- postcondition: every segment of text has color and bg set 2 + -- default_color and default_bg can be overridden 3 + compute_colors = function(block, default_color, default_bg, default_border) 4 + if block.type == 'rows' or block.type == 'cols' then 5 + block.border = block.border or default_border 6 + for _,child in ipairs(block) do 7 + compute_colors(child, 8 + (block.default and block.default.color) or block.color or default_color, 9 + (block.default and block.default.bg) or block.bg or default_bg, 10 + (block.default and block.default.border) or block.border or default_border) 11 + end 12 + elseif block.type == 'text' then 13 + block.color = block.color or (block.target and Default_link_color) or default_color 14 + block.border = block.border or default_border 15 + block.bg = block.bg or default_bg 16 + for _,line in ipairs(block) do 17 + for _,segment in ipairs(line) do 18 + segment.attrs.color = segment.attrs.color or (segment.attrs.target and Default_link_color) or block.color 19 + segment.attrs.bg = segment.attrs.bg or block.bg 20 + end 21 + end 22 + elseif block.type == 'filler' then 23 + block.border = block.border or default_border 24 + block.bg = block.bg or default_bg 25 + else 26 + assert(false, 'unknown type '..block.type) 27 + end 28 + end
+15
defs/0264-compute_line_heights
··· 1 + -- postcondition: every text descendant has line_height set 2 + -- default_line_height can be overridden by internal line_heights 3 + compute_line_heights = function(block, default_line_height) 4 + if block.type == 'rows' or block.type == 'cols' then 5 + for _,child in ipairs(block) do 6 + compute_line_heights(child, (block.default and block.default.line_height) or default_line_height) 7 + end 8 + elseif block.type == 'text' then 9 + block.line_height = block.line_height or default_line_height 10 + elseif block.type == 'filler' then 11 + -- nothing 12 + else 13 + assert(false, 'unknown type '..block.type) 14 + end 15 + end
+60
defs/0265-compute_widths
··· 1 + -- postcondition: every rows/cols/text descendant has width set 2 + -- if a node starts out with width set, it affects its descendants 3 + -- if a node starts out without width set, it is affected by its descendants 4 + -- imposed_width overrides any internal widths 5 + -- default_font can be overridden by internal fonts 6 + compute_widths = function(block, imposed_width, default_font) 7 + if block.type == 'rows' then 8 + for _,row in ipairs(block) do 9 + compute_widths(row, 10 + imposed_width and (imposed_width-Padding_x*2) or block.width, 11 + (block.default and block.default.font) or default_font) 12 + end 13 + if imposed_width then 14 + block.width = imposed_width 15 + else 16 + block.width = 0 17 + for _,row in ipairs(block) do 18 + if row.type ~= 'filler' then 19 + block.width = math.max(block.width, row.width) 20 + end 21 + end 22 + end 23 + for _,row in ipairs(block) do 24 + if row.type == 'filler' then 25 + row.width = block.width 26 + end 27 + end 28 + block.width = block.width + Padding_x*2 29 + elseif block.type == 'cols' then 30 + assert(imposed_width == nil, "don't know how to split up width among cols") 31 + for _,col in ipairs(block.data) do 32 + compute_widths(col) 33 + end 34 + block.width = 0 35 + for _,col in ipairs(block) do 36 + block.width = block.width + col.width 37 + end 38 + block.width = block.width + Padding_x*2 39 + elseif block.type == 'text' then 40 + if imposed_width then 41 + block.width = imposed_width 42 + else 43 + -- compute width without wrapping 44 + block.width = 0 45 + for _,line in ipairs(block) do 46 + local width = 0 47 + for _,segment in ipairs(line) do 48 + local font = font_from_settings(segment.attrs.font) 49 + width = width + font:getWidth(segment[1]) 50 + end 51 + block.width = math.max(block.width, width) 52 + end 53 + end 54 + block.width = block.width + Padding_x*2 55 + elseif block.type == 'filler' then 56 + -- nothing 57 + else 58 + assert(false, 'unknown type '..block.type) 59 + end 60 + end
+13
defs/0266-compute_rects
··· 1 + compute_rects = function(block) 2 + if block.type == 'rows' or block.type == 'cols' then 3 + for _,child in ipairs(block) do 4 + compute_rects(child) 5 + end 6 + elseif block.type == 'text' then 7 + compute_rects_for_text(block) 8 + elseif block.type == 'filler' then 9 + -- nothing 10 + else 11 + assert(false, 'unknown type '..block.type) 12 + end 13 + end
+54
defs/0267-compute_rects_for_line
··· 1 + compute_rects_for_line = function(line, text) 2 + -- construct line without attrs as a single string for word-wrap calculations 3 + local line_text = {} 4 + for _, segment in ipairs(line) do 5 + table.insert(line_text, segment[1]) 6 + end 7 + line_text = table.concat(line_text) 8 + -- 9 + local screen_lines = {} 10 + local curr_screen_line = {} 11 + local x, y = 0, 0 12 + local pos = 0 13 + for _,segment in ipairs(line) do 14 + local font = font_from_settings(segment.attrs.font) 15 + for p,char in my_utf8.chars(segment[1]) do 16 + pos = pos+1 17 + assert(my_utf8.codepoint(line_text, pos) == char) 18 + local w = font:getWidth(char) 19 + if char:match('%s') then 20 + if should_word_wrap(font, text.width, x, line_text, pos) then 21 + table.insert(curr_screen_line, 22 + {x=x, y=y, width=w, height=text.line_height, data=char, attrs=segment.attrs}) 23 + table.insert(screen_lines, 24 + {x=0, y=y, width=text.width, height=text.line_height, char_rects=curr_screen_line}) 25 + curr_screen_line = {} 26 + x = 0 27 + y = y + text.line_height 28 + else 29 + table.insert(curr_screen_line, {x=x, y=y, width=w, height=text.line_height, data=char, attrs=segment.attrs}) 30 + x = x + w 31 + end 32 + else 33 + if x+w > text.width then 34 + assert(pos > 1) 35 + table.insert(curr_screen_line, 36 + {x=x, y=y, width=text.width-x, height=text.line_height, attrs=segment.attrs}) -- filler 37 + table.insert(screen_lines, 38 + {x=0, y=y, width=text.width, height=text.line_height, char_rects=curr_screen_line}) 39 + curr_screen_line = {} 40 + x = 0 41 + y = y + text.line_height 42 + else 43 + -- nothing 44 + end 45 + table.insert(curr_screen_line, {x=x, y=y, width=w, height=text.line_height, data=char, attrs=segment.attrs}) 46 + x = x + w 47 + end 48 + end 49 + end 50 + table.insert(screen_lines, 51 + {x=0, y=y, width=text.width, height=text.line_height, char_rects=curr_screen_line}) 52 + y = y + text.line_height 53 + line.rects = {x=0, y=0, width=text.width, height=y, screen_line_rects=screen_lines} 54 + end
+5
defs/0268-compute_rects_for_text
··· 1 + compute_rects_for_text = function(text) 2 + for _,line in ipairs(text) do 3 + compute_rects_for_line(line, text) 4 + end 5 + end
+8
defs/0269-should_word_wrap
··· 1 + -- check if space character at pos/x is final possible candidate for word wrapping before right margin 2 + should_word_wrap = function(font, width, x, line, pos) 3 + if x < 0.8*width then return false end 4 + local offset = my_utf8.offset(line, pos+1) 5 + local s = line:match('%S*', offset) 6 + local w = font:getWidth(s) 7 + return x+w > width 8 + end
+41
defs/0270-compute_heights
··· 1 + -- postcondition: every rows/cols/text descendant has height set 2 + -- row/cols nodes never start out with height set 3 + -- text height is sum of heights of screen lines 4 + compute_heights = function(block) 5 + if block.type == 'rows' then 6 + for _,row in ipairs(block) do 7 + compute_heights(row) 8 + end 9 + block.height = 0 10 + for _,row in ipairs(block) do 11 + block.height = block.height + row.height 12 + end 13 + block.height = block.height + Padding_y*2 14 + elseif block.type == 'cols' then 15 + for _,col in ipairs(block) do 16 + compute_heights(col) 17 + end 18 + block.height = 0 19 + for _,col in ipairs(block) do 20 + if col.type ~= 'filler' then 21 + block.height = math.max(block.height, col.height) 22 + end 23 + end 24 + for _,col in ipairs(block) do 25 + if col.type == 'filler' then 26 + col.height = block.height 27 + end 28 + end 29 + block.height = block.height + Padding_y*2 30 + elseif block.type == 'text' then 31 + block.height = 0 32 + for _,line in ipairs(block) do 33 + block.height = block.height + line.rects.height 34 + end 35 + block.height = block.height + Padding_y*2 36 + elseif block.type == 'filler' then 37 + -- nothing 38 + else 39 + assert(false, 'unknown type '..block.type) 40 + end 41 + end
+3
defs/0271-font_from_settings
··· 1 + font_from_settings = function(font_settings) 2 + return Fonts[font_settings.size][bool(font_settings.bold)][bool(font_settings.italic)] 3 + end
+3
defs/0272-bool
··· 1 + bool = function(val) 2 + return not not val 3 + end
+1
defs/0273-Padding_x
··· 1 + Padding_x = 5
+1
defs/0274-Padding_y
··· 1 + Padding_y = 5
+24
defs/0276-infer_blocks
··· 1 + infer_blocks = function(cmds) 2 + for _,cmd in ipairs(cmds) do 3 + if cmd.type == 'text' or cmd.type == 'rows' or cmd.type == 'cols' then 4 + parse_all_inline_styles(cmd) 5 + -- once we have inline styles we can compute colors of segments 6 + compute_colors(cmd, 7 + Default_foreground_color_slice, 8 + Default_background_color) 9 + -- once we have inline styles we can compute fonts of segments 10 + compute_fonts(cmd, {size=21}) 11 + -- once we have fonts for everything we can compute widths 12 + compute_widths(cmd) 13 + compute_line_heights(cmd, 35) 14 + -- once we have widths we can figure out where to wrap lines 15 + compute_rects(cmd) 16 + -- once we know line heights and where lines wrap we can compute heights of all blocks 17 + compute_heights(cmd) 18 + -- once we know widths and heights we can compute the position of all blocks 19 + -- If the markup contains a single block you don't need to specify an x and y at the topmost level. 20 + -- But if you do anything more complex than that you probably should. 21 + compute_positions(cmd, cmd.x or 60, cmd.y or 60) 22 + end 23 + end 24 + end
+13
defs/0278-switch_to_file
··· 1 + switch_to_file = function(filename) 2 + if Current_pane.history == nil then 3 + Current_pane.history = {} 4 + end 5 + Viewports[Current_pane.filename] = utils.deepcopy(Current_pane.docimg_viewport) 6 + local history = Current_pane.history 7 + table.insert(history, Current_pane.filename) 8 + table.clear(Current_pane) 9 + Current_pane.history = history 10 + Current_pane.editor_state = code_editor_state() 11 + Current_pane.filename = filename 12 + one_time_load() 13 + end
+38
defs/0279-press_on_block
··· 1 + press_on_block = function(block, x,y) 2 + if block.type == 'rows' then 3 + for _,row in ipairs(block) do 4 + if press_on_block(row, x,y) then 5 + return true 6 + end 7 + end 8 + elseif block.type == 'cols' then 9 + for _,col in ipairs(block) do 10 + if press_on_block(col, x,y) then 11 + return true 12 + end 13 + end 14 + elseif block.type == 'text' then 15 + if block.target and within_rect(block, x,y) then 16 + print('click on block '..block.target) 17 + switch_to_file(block.target) 18 + return true 19 + end 20 + for _,line in ipairs(block) do 21 + for _,screen_line_rect in ipairs(line.rects.screen_line_rects) do 22 + for _,char_rect in ipairs(screen_line_rect.char_rects) do 23 + if within_rect(char_rect, x,y) then 24 + if char_rect.attrs.target then 25 + print('click on '..char_rect.attrs.target) 26 + switch_to_file(char_rect.attrs.target) 27 + return true 28 + end 29 + end 30 + end 31 + end 32 + end 33 + elseif block.type == 'filler' then 34 + -- nothing 35 + else 36 + assert(false, 'unknown type '..block.type) 37 + end 38 + end
+30
defs/0280-compute_positions
··· 1 + compute_positions = function(block, x,y) 2 + block.x, block.y = x, y 3 + x, y = x + Padding_x, y + Padding_y 4 + if block.type == 'rows' then 5 + for _,row in ipairs(block) do 6 + compute_positions(row, x,y) 7 + y = y + row.height 8 + end 9 + elseif block.type == 'cols' then 10 + for _,col in ipairs(block) do 11 + compute_positions(col, x,y) 12 + x = x + col.width 13 + end 14 + elseif block.type == 'text' then 15 + for _,line in ipairs(block) do 16 + for _,screen_line_rect in ipairs(line.rects.screen_line_rects) do 17 + x = block.x + Padding_x 18 + for _,char_rect in ipairs(screen_line_rect.char_rects) do 19 + char_rect.x, char_rect.y = x, y 20 + x = x + char_rect.width 21 + end 22 + y = y + screen_line_rect.height 23 + end 24 + end 25 + elseif block.type == 'filler' then 26 + -- nothing 27 + else 28 + assert(false, 'unknown type '..block.type) 29 + end 30 + end
+5
defs/0281-in_gamut
··· 1 + in_gamut = function(r,g,b) 2 + return 0 <= r and r <= 1 3 + and 0 <= g and g <= 1 4 + and 0 <= b and b <= 1 5 + end
+3
defs/0282-clamp
··· 1 + clamp = function(val, min, max) 2 + return math.max(min, math.min(val, max)) 3 + end
+7
defs/0283-linear_to_sRGB
··· 1 + linear_to_sRGB = function(val) 2 + if val <= 0.0031308 then 3 + return val * 12.92 4 + else 5 + return 1.055 * (val ^ (1/2.4)) - 0.055 6 + end 7 + end
+8
defs/0284-sRGB_to_linear
··· 1 + -- https://stackoverflow.com/questions/12524623/what-are-the-practical-differences-when-working-with-colors-in-a-linear-vs-a-no 2 + sRGB_to_linear = function(val) 3 + if val <= 0.04045 then 4 + return val / 12.92 5 + else 6 + return ((val + 0.055) / 1.055) ^ 2.4 7 + end 8 + end
+7
defs/0285-luminance
··· 1 + -- https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html#dfn-relative-luminance 2 + luminance = function(r, g, b) 3 + r = sRGB_to_linear(r) 4 + g = sRGB_to_linear(g) 5 + b = sRGB_to_linear(b) 6 + return 0.2126*r + 0.7152*g + 0.0722*b 7 + end
+9
defs/0286-contrast_ratio
··· 1 + -- https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html#dfn-contrast-ratio 2 + contrast_ratio = function(r1,g1,b1, r2,g2,b2) 3 + local l1 = luminance(r1,g1,b1) 4 + local l2 = luminance(r2,g2,b2) 5 + if l1 < l2 then 6 + l1, l2 = l2, l1 7 + end 8 + return (l1+0.05) / (l2+0.05) 9 + end
+21
defs/0287-oklch_to_sRGB
··· 1 + -- https://bottosson.github.io/posts/oklab 2 + oklch_to_sRGB = function(L, C, h) 3 + local hRad = math.rad(h) 4 + -- Oklch -> Oklab 5 + local a = C * math.cos(hRad) 6 + local b = C * math.sin(hRad) 7 + -- Oklab -> lms 8 + local l = L + 0.3963377774 * a + 0.2158037573 * b 9 + local m = L - 0.1055613458 * a - 0.0638541728 * b 10 + local s = L - 0.0894841775 * a - 1.2914855480 * b 11 + l, m, s = l^3, m^3, s^3 -- non-linearity 12 + -- lms -> linear 13 + local r_l = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s 14 + local g_l = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s 15 + local b_l = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s 16 + -- linear -> sRGB 17 + local r = clamp(linear_to_sRGB(r_l), 0, 1) 18 + local g = clamp(linear_to_sRGB(g_l), 0, 1) 19 + local b = clamp(linear_to_sRGB(b_l), 0, 1) 20 + return r, g, b 21 + end
+10
defs/0288-Hue_map
··· 1 + Hue_map = { 2 + red = 30, 3 + orange = 60, 4 + yellow = 90, 5 + green = 150, 6 + cyan = 200, 7 + blue = 250, 8 + purple = 300, 9 + magenta = 330 10 + }
+46
defs/0289-parse_docimg_color
··· 1 + -- Parse a particular syntax representing a color in perceptually uniform Oklch space. Oklch space has 3 dimensions: 2 + -- perceptual lightness (L) 3 + -- chromatic intensity (C) 4 + -- hue or color (h) 5 + -- 6 + -- This syntax contains these components in reverse order, separated by colons: 'h:C:L' 7 + -- 8 + -- h can take one of 8 values: red orange yellow green cyan blue purple magenta 9 + -- C is on an 8 point scale from 0 to 7 10 + -- L is on an 8 point scale from 0 to 7 11 + -- Example: 'red:8:7' 12 + -- 13 + -- One special case is greyscale, which doesn't get a chromatic intensity. Black is 'grey:0' and white is 'grey:7'. 14 + -- 15 + -- This function also parses incomplete colors which represent subspaces. The hue is always required. 16 + parse_docimg_color = function(color_str) 17 + local parts = {} 18 + for part in color_str:gmatch('[^:]+') do 19 + table.insert(parts, part) 20 + end 21 + local result 22 + local h = parts[1]:lower() 23 + if h == 'grey' or h == 'gray' then 24 + result = { 25 + h = --[[can be anything]] 0, 26 + C = 0, 27 + L = tonumber(parts[2]), 28 + } 29 + else 30 + result = { 31 + h = Hue_map[h], 32 + C = tonumber(parts[2]), 33 + L = tonumber(parts[3]), 34 + } 35 + assert(result.h, 'unknown hue '..parts[1]) 36 + if result.C then 37 + result.C = result.C/7 * 0.3 38 + else 39 + result.C = 0.3 -- max chroma 40 + end 41 + end 42 + if result.L then 43 + result.L = result.L/7 44 + end 45 + return result 46 + end
+4
defs/0290-convert_rgb
··· 1 + convert_rgb = function(c) 2 + local r, g, b = oklch_to_sRGB(c.L, c.C, c.h) 3 + return {r, g, b} 4 + end
+20
defs/0291-function
··· 1 + function select_lightness(bg, fg) 2 + local target_contrast = 7 3 + bg, fg = utils.deepcopy(bg), utils.deepcopy(fg) 4 + bg.r, bg.g, bg.b = oklch_to_sRGB(bg.L, bg.C, bg.h) 5 + local ls 6 + if bg.L < 0.5 then 7 + ls = {0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1.0} 8 + else 9 + ls = {0.45, 0.4, 0.35, 0.3, 0.25, 0.2, 0.15, 0.1, 0.05, 0} 10 + end 11 + for _, fg_l in ipairs(ls) do 12 + fg.r, fg.g, fg.b = oklch_to_sRGB(fg_l, fg.C, fg.h) 13 + if in_gamut(fg.r, fg.g, fg.b) then 14 + local contrast = contrast_ratio(bg.r, bg.g, bg.b, fg.r, fg.g, fg.b) 15 + if contrast >= target_contrast then 16 + return {fg.r, fg.g, fg.b} 17 + end 18 + end 19 + end 20 + end
+9
defs/0292-select_chroma_lightness
··· 1 + select_chroma_lightness = function(bg, fg) 2 + local dchromas = {0.05, -0.05, 0.1, -0.1, 0.15, -0.15, 0.2, -0.2, 0.25, -0.25} 3 + for _, dchroma in ipairs(dchromas) do 4 + if 0 <= fg.C+dchroma and fg.C+dchroma <= 0.5 then 5 + local result = select_lightness(bg, {h=fg.h, C=fg.C+dchroma}) 6 + if result then return result end 7 + end 8 + end 9 + end
+7
defs/0293-select_hue_chroma_lightness
··· 1 + select_hue_chroma_lightness = function(bg, fg) 2 + local dhues = {5, -5, 10, -10, 15, -15} 3 + for _, dhue in ipairs(dhues) do 4 + local result = select_chroma_lightness(bg, {h=fg.h+dhue, C=fg.C}) 5 + if result then return result end 6 + end 7 + end
+20
defs/0294-select_lightness
··· 1 + select_lightness = function(bg, fg) 2 + local target_contrast = 7 3 + bg, fg = utils.deepcopy(bg), utils.deepcopy(fg) 4 + bg.r, bg.g, bg.b = oklch_to_sRGB(bg.L, bg.C, bg.h) 5 + local ls 6 + if bg.L < 0.5 then 7 + ls = {0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1.0} 8 + else 9 + ls = {0.45, 0.4, 0.35, 0.3, 0.25, 0.2, 0.15, 0.1, 0.05, 0} 10 + end 11 + for _, fg_l in ipairs(ls) do 12 + fg.r, fg.g, fg.b = oklch_to_sRGB(fg_l, fg.C, fg.h) 13 + if in_gamut(fg.r, fg.g, fg.b) then 14 + local contrast = contrast_ratio(bg.r, bg.g, bg.b, fg.r, fg.g, fg.b) 15 + if contrast >= target_contrast then 16 + return {fg.r, fg.g, fg.b} 17 + end 18 + end 19 + end 20 + end
+3
defs/0295-docimg_color_to_rgb
··· 1 + docimg_color_to_rgb = function(color_str) 2 + return convert_rgb(parse_docimg_color(color_str)) 3 + end
+15
defs/0296-select_rgb
··· 1 + -- Given a spec for a baseline color and a foreground color _slice_, return a specific foreground color in the slice that is sufficiently high-contrast as per WCAG. 2 + -- For the syntax of the foreground and background specs, see parse_docimg_color. 3 + select_rgb = function(bg_str, fg_str) 4 + local bg = parse_docimg_color(bg_str) 5 + assert(bg.L, 'background color must contain a lightness (index 3)') 6 + local fg = parse_docimg_color(fg_str) 7 + if fg.L then return convert_rgb(fg) end -- no wiggle room; return the color that came in 8 + local result = select_lightness(bg, fg) 9 + if result then return result end 10 + result = select_chroma_lightness(bg, fg) 11 + if result then return result end 12 + result = select_hue_chroma_lightness(bg, fg) 13 + if result then return result end 14 + error('could not find a color; something is wrong') 15 + end
+1
defs/0297-Default_background_color
··· 1 + Default_background_color = 'grey:7'
+1
defs/0298-Default_foreground_color_slice
··· 1 + Default_foreground_color_slice = 'grey'
+123
fonts/Bitstream Vera License.txt
··· 1 + Bitstream Vera Fonts Copyright 2 + 3 + The fonts have a generous copyright, allowing derivative works (as 4 + long as "Bitstream" or "Vera" are not in the names), and full 5 + redistribution (so long as they are not *sold* by themselves). They 6 + can be be bundled, redistributed and sold with any software. 7 + 8 + The fonts are distributed under the following copyright: 9 + 10 + Copyright 11 + ========= 12 + 13 + Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream 14 + Vera is a trademark of Bitstream, Inc. 15 + 16 + Permission is hereby granted, free of charge, to any person obtaining 17 + a copy of the fonts accompanying this license ("Fonts") and associated 18 + documentation files (the "Font Software"), to reproduce and distribute 19 + the Font Software, including without limitation the rights to use, 20 + copy, merge, publish, distribute, and/or sell copies of the Font 21 + Software, and to permit persons to whom the Font Software is furnished 22 + to do so, subject to the following conditions: 23 + 24 + The above copyright and trademark notices and this permission notice 25 + shall be included in all copies of one or more of the Font Software 26 + typefaces. 27 + 28 + The Font Software may be modified, altered, or added to, and in 29 + particular the designs of glyphs or characters in the Fonts may be 30 + modified and additional glyphs or characters may be added to the 31 + Fonts, only if the fonts are renamed to names not containing either 32 + the words "Bitstream" or the word "Vera". 33 + 34 + This License becomes null and void to the extent applicable to Fonts 35 + or Font Software that has been modified and is distributed under the 36 + "Bitstream Vera" names. 37 + 38 + The Font Software may be sold as part of a larger software package but 39 + no copy of one or more of the Font Software typefaces may be sold by 40 + itself. 41 + 42 + THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 43 + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 44 + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 45 + OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL 46 + BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR 47 + OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, 48 + OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR 49 + OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT 50 + SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. 51 + 52 + Except as contained in this notice, the names of Gnome, the Gnome 53 + Foundation, and Bitstream Inc., shall not be used in advertising or 54 + otherwise to promote the sale, use or other dealings in this Font 55 + Software without prior written authorization from the Gnome Foundation 56 + or Bitstream Inc., respectively. For further information, contact: 57 + fonts at gnome dot org. 58 + 59 + Copyright FAQ 60 + ============= 61 + 62 + 1. I don't understand the resale restriction... What gives? 63 + 64 + Bitstream is giving away these fonts, but wishes to ensure its 65 + competitors can't just drop the fonts as is into a font sale system 66 + and sell them as is. It seems fair that if Bitstream can't make money 67 + from the Bitstream Vera fonts, their competitors should not be able to 68 + do so either. You can sell the fonts as part of any software package, 69 + however. 70 + 71 + 2. I want to package these fonts separately for distribution and 72 + sale as part of a larger software package or system. Can I do so? 73 + 74 + Yes. A RPM or Debian package is a "larger software package" to begin 75 + with, and you aren't selling them independently by themselves. 76 + See 1. above. 77 + 78 + 3. Are derivative works allowed? 79 + Yes! 80 + 81 + 4. Can I change or add to the font(s)? 82 + Yes, but you must change the name(s) of the font(s). 83 + 84 + 5. Under what terms are derivative works allowed? 85 + 86 + You must change the name(s) of the fonts. This is to ensure the 87 + quality of the fonts, both to protect Bitstream and Gnome. We want to 88 + ensure that if an application has opened a font specifically of these 89 + names, it gets what it expects (though of course, using fontconfig, 90 + substitutions could still could have occurred during font 91 + opening). You must include the Bitstream copyright. Additional 92 + copyrights can be added, as per copyright law. Happy Font Hacking! 93 + 94 + 6. If I have improvements for Bitstream Vera, is it possible they might get 95 + adopted in future versions? 96 + 97 + Yes. The contract between the Gnome Foundation and Bitstream has 98 + provisions for working with Bitstream to ensure quality additions to 99 + the Bitstream Vera font family. Please contact us if you have such 100 + additions. Note, that in general, we will want such additions for the 101 + entire family, not just a single font, and that you'll have to keep 102 + both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add 103 + glyphs to the font, they must be stylistically in keeping with Vera's 104 + design. Vera cannot become a "ransom note" font. Jim Lyles will be 105 + providing a document describing the design elements used in Vera, as a 106 + guide and aid for people interested in contributing to Vera. 107 + 108 + 7. I want to sell a software package that uses these fonts: Can I do so? 109 + 110 + Sure. Bundle the fonts with your software and sell your software 111 + with the fonts. That is the intent of the copyright. 112 + 113 + 8. If applications have built the names "Bitstream Vera" into them, 114 + can I override this somehow to use fonts of my choosing? 115 + 116 + This depends on exact details of the software. Most open source 117 + systems and software (e.g., Gnome, KDE, etc.) are now converting to 118 + use fontconfig (see www.fontconfig.org) to handle font configuration, 119 + selection and substitution; it has provisions for overriding font 120 + names and subsituting alternatives. An example is provided by the 121 + supplied local.conf file, which chooses the family Bitstream Vera for 122 + "sans", "serif" and "monospace". Other software (e.g., the XFree86 123 + core server) has other mechanisms for font substitution.
fonts/Vera-Bold-Italic.ttf

This is a binary file and will not be displayed.

fonts/Vera-Bold.ttf

This is a binary file and will not be displayed.

fonts/Vera-Italic.ttf

This is a binary file and will not be displayed.

fonts/Vera.ttf

This is a binary file and will not be displayed.

+6 -4
my_utf8.lua
··· 11 11 return result 12 12 end 13 13 14 - function my_utf8.match_at(s, pos, pat) 14 + function my_utf8.codepoint(s, pos) 15 15 local start_offset = my_utf8.offset(s, pos) 16 16 local end_offset = my_utf8.offset(s, pos+1) 17 - assert(end_offset > start_offset, ('end_offset %d not > start_offset %d'):format(end_offset, start_offset)) 18 - local curr = s:sub(start_offset, end_offset-1) 19 - return curr:match(pat) 17 + return s:sub(start_offset, end_offset-1) 18 + end 19 + 20 + function my_utf8.match_at(s, pos, pat) 21 + return my_utf8.codepoint(s, pos):match(pat) 20 22 end 21 23 22 24 -- create a new iterator for s which provides the index and UTF-8 bytes corresponding to each codepoint
+21
screens/--car-array
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=35}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Car@{Lua Carousel} > functions@{functions} > array', 5 + attrs={ 6 + Car={target='--carousel'}, 7 + functions={target='--car-fn'}, 8 + }}, 9 + }, 10 + {type='text', 'some helpers for operating on arrays without modifying them'}, 11 + {type='text', 'array.find(array, element_or_predicate)', font={size=24}}, 12 + {type='text', 'array.any(array, predicate)', font={size=24}}, 13 + {type='text', 'array.all(array, predicate)', font={size=24}}, 14 + {type='text', 'array.each(array, starting_index)', font={size=24}}, 15 + {type='text', 'array.join(array1, array2)', font={size=24}}, 16 + {type='text', 'array.map(array, f)', font={size=24}}, 17 + {type='text', 'array.reduce(array, init, f)', font={size=24}}, 18 + {type='text', 'array.keep(array, f)', font={size=24}}, 19 + {type='text', 'array.remove(array, f)', font={size=24}}, 20 + } 21 + }
+17
screens/--car-fn
··· 1 + {type='rows', default={font={size=21}, line_height=35}, 2 + {type='text', font={size=28}, line_height=50, 3 + {'Car@{Lua Carousel} > variables', 4 + attrs={ 5 + Car={target='--carousel'}, 6 + }}, 7 + }, 8 + {type='filler', height=15}, 9 + {type='rows', width=350, default={border='grey'}, 10 + {type='text', 'miscellaneous utilities', target='--car-utils'}, 11 + {type='text', 'array', target='--car-array'}, 12 + {type='text', 'Unicode', target='--car-utf8'}, 13 + { 14 + {type='text', 'save_wav()', target='--car-wav'}, 15 + }, 16 + } 17 + }
+25
screens/--car-handler
··· 1 + { 2 + {type='rows', width=500, default={font={size=21}, line_height=35}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Car@{Lua Carousel} > handlers', 5 + attrs={ 6 + Car={target='--carousel'}, 7 + }}, 8 + }, 9 + {type='filler', height=15}, 10 + {type='rows', 11 + {type='text', 'Define these functions to have them called many times a second:'}, 12 + {type='filler', height=45}, 13 + {type='rows', default={font={size=24}}, 14 + {type='text', 'function car.draw()'}, 15 + {type='text', 'function car.update(dt)', target='--car-update'}, 16 + }, 17 + }, 18 + {type='filler', height=10}, 19 + {type='rows', 20 + {type='text', 'on input events:'}, 21 + {type='text', border='grey', 'mouse/touch handlers', target='--car-touch'}, 22 + {type='text', border='grey', 'keyboard handlers', target='--car-kbd'}, 23 + }, 24 + } 25 + }
+20
screens/--car-kbd
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=35}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Car@{Lua Carousel} > handlers@{handlers} > keyboard', 5 + attrs={ 6 + Car={target='--carousel'}, 7 + handlers={target='--car-handler'}, 8 + }}, 9 + }, 10 + {type='filler', height=15}, 11 + {type='text', 'Define these functions to have them called on keyboard events:'}, 12 + {type='text', 'function car.keychord_press(chord, key)', font={size=24}}, 13 + {type='text', " - chord: combinations of modifiers and key separated by '-'", line_height=20}, 14 + {type='text', " modifiers: 'C': ctrl; 'M': alt; 'S': shift; 's': super/cmd/windows in that order", line_height=20}, 15 + {type='text', " e.g. C-z (ctrl + z); M-b (alt + b); C-M-k (ctrl + alt + k)"}, 16 + {type='text', 'function car.text_input(t)', font={size=24}}, 17 + {type='text', ' - t: Unicode text of keypress (including shift, etc.)'}, 18 + {type='text', 'function car.key_release(key)', font={size=24}}, 19 + } 20 + }
+22
screens/--car-touch
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=35}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Car@{Lua Carousel} > handlers@{handlers} > touch', 5 + attrs={ 6 + Car={target='--carousel'}, 7 + handlers={target='--car-handler'}, 8 + }}, 9 + }, 10 + {type='filler', height=15}, 11 + {type='text', 'Define these functions to have them called on touch events:'}, 12 + {type='text', 'function car.mouse_press(x,y, mouse_button)', font={size=24}}, 13 + {type='text', ' called before car.touch_press on mobile devices'}, 14 + {type='text', 'function car.mouse_release(x,y, mouse_button)', font={size=24}}, 15 + {type='text', ' called before car.touch_release on mobile devices'}, 16 + {type='text', 'function car.mouse_move(x,y, dx,dy)', font={size=24}}, 17 + {type='text', 'function car.mouse_wheel_move(dx,dy)', font={size=24}}, 18 + {type='text', 'function car.touch_press(id, x,y, dx,dy, pressure)', font={size=24}}, 19 + {type='text', 'function car.touch_release(id, x,y, dx,dy, pressure)', font={size=24}}, 20 + {type='text', 'function car.touch_move(id, x,y, dx,dy, pressure)', font={size=24}}, 21 + } 22 + }
+14
screens/--car-update
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=35}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Car@{Lua Carousel} > handlers@{handlers} > car.update(dt)', 5 + attrs={ 6 + Car={target='--carousel'}, 7 + handlers={target='--car-handler'}, 8 + }}, 9 + }, 10 + {type='filler', height=15}, 11 + {type='text', 'arguments:'}, 12 + {type='text', '- dt: time in seconds since previous call to car.update'}, 13 + } 14 + }
+27
screens/--car-utf8
··· 1 + { 2 + {type='rows', width=800, default={font={size=21}, line_height=25}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Car@{Lua Carousel} > functions@{functions} > Unicode', 5 + attrs={ 6 + Car={target='--carousel'}, 7 + functions={target='--car-fn'}, 8 + }}, 9 + }, 10 + {type='filler', height=15}, 11 + {type='text', 12 + {"Characters are complex. Lua strings are arrays of bytes, but to manage text in a variety of languages it's more convenient to work with em@{Unicode codepoints}.", 13 + attrs={ 14 + em={font={italic=true}}, 15 + }}}, 16 + {type='filler', height=15}, 17 + {type='text', 'my_utf8.offset(text, index)', font={size=24}}, 18 + {type='text', ' Like utf8.offset, converts index in code points to index in bytes.'}, 19 + {type='text', ' Unlike utf8.offset, throws an error if index is out of bounds.'}, 20 + {type='filler', height=10}, 21 + {type='text', 'my_utf8.match_at(text, pos, pattern)', font={size=24}}, 22 + {type='text', ' Checks if pattern matches text at index pos (counting codepoints)'}, 23 + {type='filler', height=10}, 24 + {type='text', 'my_utf8.chars(s, startpos)', font={size=24}}, 25 + {type='text', ' Returns an iterator over codepoints in s'}, 26 + } 27 + }
+23
screens/--car-utils
··· 1 + { 2 + {type='rows', width=800, default={font={size=21}, line_height=25}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Car@{Lua Carousel} > functions@{functions} > misc utilities', 5 + attrs={ 6 + Car={target='--carousel'}, 7 + functions={target='--car-fn'}, 8 + }}, 9 + }, 10 + {type='filler', height=15}, 11 + {type='text', 'utils.deepcopy(obj)', font={size=24}}, 12 + {type='filler', height=10}, 13 + {type='text', 'utils.set_color_from_table({r=.., g=.., b=..})', font={size=24}}, 14 + {type='text', 15 + {' sets color using a table; compare setColor@{love.graphics.setColor}', 16 + attrs={ 17 + setColor={target='--love-graphics-settings'}, 18 + }}}, 19 + {type='filler', height=10}, 20 + {type='text', 'utils.width(text)', font={size=24}}, 21 + {type='text', ' returns the width occupied by text in the current font'}, 22 + } 23 + }
+17
screens/--car-var
··· 1 + { 2 + {type='rows', width=500, default={font={size=21}, line_height=25}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Car@{Lua Carousel} > variables', 5 + attrs={ 6 + Car={target='--carousel'}, 7 + functions={target='--car-fn'}, 8 + }}, 9 + }, 10 + {type='filler', height=15}, 11 + {type='text', 'Safe_width, Safe_height', font={size=24}}, 12 + {type='text', ' window dimensions'}, 13 + {type='filler', height=10}, 14 + {type='text', 'Menu_bottom', font={size=24}}, 15 + {type='text', ' how far down the green menu extends'}, 16 + } 17 + }
+25
screens/--car-wav
··· 1 + { 2 + {type='rows', width=800, default={font={size=21}, line_height=25}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Car@{Lua Carousel} > functions@{functions} > save_wav', 5 + attrs={ 6 + Car={target='--carousel'}, 7 + functions={target='--car-fn'}, 8 + }}, 9 + }, 10 + {type='filler', height=15}, 11 + {type='text', 'A little helper for saving .wav sound files from LÖVE Recording Devices.'}, 12 + {type='filler', height=35}, 13 + {type='text', 'save_wav(filename, sounddata)', font={size=24}}, 14 + {type='filler', height=15}, 15 + {type='text', 'example usage:'}, 16 + {type='text', ' local inputs = love.audio.getRecordingDevices()'}, 17 + {type='text', ' local recording_device = inputs[1]'}, 18 + {type='text', ' recording_device:start()'}, 19 + {type='text', ' ... say something ...'}, 20 + {type='text', ' recording_device:stop()'}, 21 + {type='text', " save_wav('filename1', recording_device:getData())"}, 22 + {type='filler', height=15}, 23 + {type='text', 'On phones, filename1 may only be accessible within this app.'}, 24 + } 25 + }
+21
screens/--help
··· 1 + { 2 + {type='rows', x=50, y=60, 3 + {type='text', 'Your context', height=35, font_size=28}, 4 + {type='filler', height=15}, 5 + {type='rows', width=500, 6 + {type='text', '---> you are here <---', height=35}, 7 + {type='text', border='grey', 'Lua Carousel', height=35, target='--carousel'}, 8 + {type='text', border='grey', 'LÖVE', height=35, target='--love'}, 9 + {type='text', border='grey', 'Lua', height=35, target='--lua'}, 10 + {type='text', border='grey', 'Operating System', height=35}, 11 + {type='text', border='grey', 'The nature of human beings', height=35}, 12 + {type='text', border='grey', 'Reality', height=35}, 13 + }, 14 + {type='filler', height=35}, 15 + {type='rows', width=500, font_size=24, 16 + {type='text', 'This is likely confusing. Sorry!', height=35}, 17 + {type='text', 'Ask me for help: akkartik.name/contact', height=35}, 18 + {type='text', 'You can do this!', height=35}, 19 + }, 20 + } 21 + }
+13
screens/--love
··· 1 + { 2 + {type='rows', width=500, default={font={size=21}, line_height=35}, 3 + {type='text', 'LÖVE game engine', font={size=28}}, 4 + {type='text', 'love2d.org (2008)'}, 5 + {type='rows', width=200, 6 + {type='text', border='grey', 'effects', target='--love-output'}, 7 + {type='text', border='grey', 'sensors', target='--love-input'}, 8 + {type='text', border='grey', 'utf8', target='--love-utf8'}, 9 + {type='text', border='grey', 'multitasking', target='--love-thread'}, 10 + {type='text', border='grey', 'random', target='--love-random'}, 11 + }, 12 + } 13 + }
+26
screens/--love-audio
··· 1 + { 2 + {type='rows', width=600, default={font={size=21}, line_height=35}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > effects@{effects} > sound', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + effects={target='--love-output'}, 8 + }}, 9 + }, 10 + {type='text', 'source = love.audio.newSource(filename, type)', font={size=24}}, 11 + {type='text', ' create a new "source" you can send to speakers', line_height=20}, 12 + {type='text', ' type: "static" or "streaming"'}, 13 + {type='text', 'love.audio.play(source)', font={size=24}}, 14 + {type='text', ' you can play multiple sources at once'}, 15 + {type='text', 'sources table = love.audio.pause()', font={size=24}}, 16 + {type='text', 'love.audio.stop()', font={size=24}}, 17 + {type='filler', height=10}, 18 + {type='text', 'devices table = love.audio.getRecordingDevices()', font={size=24}}, 19 + {type='text', ' list microphones available for recording audio from'}, 20 + {type='text', 'recordingDevice:start()', font={size=24}}, 21 + {type='text', ' begin recording from a specific microphone'}, 22 + {type='text', 'recordingDevice:stop()', font={size=24}}, 23 + {type='text', ' stop recording from a specific microphone'}, 24 + {type='text', 'see save_wav() for saving recordings', target='--car-wav'}, 25 + } 26 + }
+18
screens/--love-graphics
··· 1 + { 2 + {type='rows', width=600, default={font={size=21}, line_height=35}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > effects@{effects} > graphics', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + effects={target='--love-output'}, 8 + }}, 9 + }, 10 + {type='filler', height=15}, 11 + {type='rows', width=200, default={border='grey'}, 12 + {type='text', 'shapes', height=35, target='--love-shapes'}, 13 + {type='text', 'settings', height=35, target='--love-graphics-settings'}, 14 + {type='text', 'transforms', height=35, target='--love-transforms'}, 15 + {type='text', 'window', height=35, target='--love-window'}, 16 + }, 17 + } 18 + }
+29
screens/--love-graphics-settings
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=35}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > effects@{effects} > graphics@{graphics} > settings', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + effects={target='--love-output'}, 8 + graphics={target='--love-graphics'}, 9 + }}, 10 + }, 11 + {type='filler', height=15}, 12 + {type='text', 'red, green, blue, transparency = love.graphics.getColor()', font={size=24}}, 13 + {type='text', 'love.graphics.setColor(red, green, blue, transparency)', font={size=24}}, 14 + {type='text', " red, green, blue, transparency: between 0.0 and 1.0"}, 15 + 16 + {type='filler', height=10}, 17 + {type='text', 'font = love.graphics.getFont()', font={size=24}}, 18 + {type='text', 'font = love.graphics.newFont(filename)', font={size=24}}, 19 + {type='text', 'love.graphics.setFont(font)', font={size=24}}, 20 + 21 + {type='filler', height=10}, 22 + {type='text', 'size = love.graphics.getPointSize()', font={size=24}}, 23 + {type='text', 'love.graphics.setPointSize(size)', font={size=24}}, 24 + 25 + {type='filler', height=10}, 26 + {type='text', 'width = love.graphics.getLineWidth()', font={size=24}}, 27 + {type='text', 'love.graphics.setLineWidth(width)', font={size=24}}, 28 + } 29 + }
+17
screens/--love-input
··· 1 + { 2 + {type='rows', width=600, default={font={size=21}, line_height=35}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > sensors', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + }}, 8 + }, 9 + {type='filler', height=15}, 10 + {type='text', border='grey', 'touch (mouse/screen)', target='--love-touch'}, 11 + {type='text', border='grey', 'keyboard', target='--love-kbd'}, 12 + 13 + {type='filler', height=10}, 14 + {type='text', 'love.timer.getTime()', font={size=24}}, 15 + {type='text', ' returns time elapsed since some arbitrary point'}, 16 + } 17 + }
+17
screens/--love-kbd
··· 1 + { 2 + {type='rows', width=600, default={font={size=21}, line_height=35}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > sensors@{sensors} > keyboard', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + sensors={target='--love-input'}, 8 + }}, 9 + }, 10 + {type='filler', height=15}, 11 + {type='text', 'love.keyboard.setTextInput(enable)', font={size=24}}, 12 + {type='text', ' shows/hides keyboard on touchscreens'}, 13 + 14 + {type='filler', height=10}, 15 + {type='text', 'love.keyboard.isDown(key)', font={size=24}}, 16 + } 17 + }
+13
screens/--love-output
··· 1 + { 2 + {type='rows', width=600, default={font={size=21}, line_height=35}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > effects', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + }}, 8 + }, 9 + {type='filler', height=15}, 10 + {type='text', border='grey', 'graphics', target='--love-graphics'}, 11 + {type='text', border='grey', 'sound', target='--love-audio'}, 12 + } 13 + }
+28
screens/--love-random
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=25}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > random', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + }}, 8 + }, 9 + {type='filler', height=15}, 10 + {type='text', 'seed = love.math.getRandomSeed()', font={size=24}}, 11 + {type='filler', height=10}, 12 + {type='text', 'love.math.setRandomSeed(seed)', font={size=24}}, 13 + {type='text', ' seed: a single number that perfectly affects all future random numbers generated by love.math.random()'}, 14 + {type='filler', height=10}, 15 + {type='text', 'number = love.math.random()', font={size=24}}, 16 + {type='text', ' returns a seemingly random number uniformly distributed between 0 (included) and 1 (excluded)'}, 17 + {type='filler', height=10}, 18 + {type='text', 'number = love.math.random(max)', font={size=24}}, 19 + {type='text', ' returns a seemingly random integer uniformly distributed between 1 and max (both included)'}, 20 + {type='filler', height=10}, 21 + {type='text', 'number = love.math.random(min, max)', font={size=24}}, 22 + {type='text', ' returns a seemingly random integer uniformly distributed between min and max (both included)'}, 23 + {type='filler', height=10}, 24 + {type='text', 'number = love.math.randomNormal(stddev, mean)', font={size=24}}, 25 + {type='text', " returns a seemingly random integer normally distributed with the standard deviation 'stddev' and the average 'mean'"}, 26 + {type='text', " This function doesn't use the random seed above."}, 27 + } 28 + }
+36
screens/--love-shapes
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=25}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > effects@{effects} > graphics@{graphics} > shapes', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + effects={target='--love-output'}, 8 + graphics={target='--love-graphics'}, 9 + }}, 10 + }, 11 + {type='filler', height=15}, 12 + {type='text', 'love.graphics.points(x,y, x,y, ...)', font={size=24}}, 13 + {type='filler', height=10}, 14 + {type='text', 'love.graphics.line(x,y, x,y, ...)', font={size=24}}, 15 + {type='filler', height=10}, 16 + {type='text', 'love.graphics.rectangle(mode, x,y, width, height)', font={size=24}}, 17 + {type='text', " mode: 'line' or 'fill'"}, 18 + {type='filler', height=10}, 19 + {type='text', 'love.graphics.circle(mode, center x, center y, radius)', font={size=24}}, 20 + {type='filler', height=10}, 21 + {type='text', 'love.graphics.ellipse(mode, center x, center y, radiusx, radiusy)', font={size=24}}, 22 + {type='filler', height=10}, 23 + {type='text', 'love.graphics.arc(mode, center x, center y, radius, start angle, end angle)', font={size=24}}, 24 + {type='filler', height=10}, 25 + {type='text', 'love.graphics.polygon(mode, x,y, x,y, ...)', font={size=24}}, 26 + {type='filler', height=10}, 27 + {type='text', 'love.graphics.polygon(mode, table of vertices)', font={size=24}}, 28 + {type='filler', height=10}, 29 + {type='text', 'love.math.isConvex(vertices)', font={size=24}}, 30 + {type='text', ' returns true if the given polygon is convex'}, 31 + {type='filler', height=10}, 32 + {type='text', 'beziercurve = love.math.newBezierCurve(vertices)', font={size=24}}, 33 + {type='text', ' constructs a Bezier curve with the given control polygon'}, 34 + {type='text', ' to draw the curve, use love.graphics.line(beziercurve:render())'}, 35 + } 36 + }
+16
screens/--love-thread
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=25}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > multitasking', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + }}, 8 + }, 9 + {type='filler', height=15}, 10 + {type='text', 'thread = love.thread.newThread(data)', font={size=24}}, 11 + {type='text', ' create a thread from a string or file containing Lua code'}, 12 + {type='filler', height=10}, 13 + {type='text', 'love.timer.sleep(seconds)', font={size=24}}, 14 + {type='text', ' block the current thread for the given duration'}, 15 + } 16 + }
+17
screens/--love-timer
··· 1 + { 2 + {type='rows', width=600, default={font={size=21}, line_height=25}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > sensors@{sensors} > timer', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + sensors={target='--love-input'}, 8 + }}, 9 + }, 10 + {type='filler', height=15}, 11 + {type='text', 'love.timer.getTime()', font={size=24}}, 12 + {type='text', ' returns time elapsed since some arbitrary point'}, 13 + {type='filler', height=10}, 14 + {type='text', 'love.timer.sleep(seconds)', font={size=24}}, 15 + {type='text', ' block the current thread for the given duration'}, 16 + } 17 + }
+27
screens/--love-touch
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=20}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > sensors@{sensors} > mouse/touch', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + sensors={target='--love-input'}, 8 + }}, 9 + }, 10 + {type='filler', height=15}, 11 + {type='text', 'love.touch.getTouches()', font={size=24}}, 12 + {type='text', ' returns an array of ids of all currently active touches on the touchscreen'}, 13 + {type='filler', height=10}, 14 + {type='text', 'x, y = love.touch.getPosition(id)', font={size=24}}, 15 + {type='text', " returns the coordinates of the currently active touch 'id'"}, 16 + {type='filler', height=10}, 17 + {type='text', 'pressure = love.touch.getPressure(id)', font={size=24}}, 18 + {type='text', " returns the pressure of the currently active touch 'id'"}, 19 + {type='text', " on non-pressure-sensitive devices the pressure will always be 1"}, 20 + {type='filler', height=10}, 21 + {type='text', 'x, y = love.mouse.getPosition()', font={size=24}}, 22 + {type='text', " returns the current coordinates of the mouse pointer"}, 23 + {type='filler', height=10}, 24 + {type='text', 'bool = love.mouse.isDown(mouse_button)', font={size=24}}, 25 + {type='text', " returns true if 'mouse_button' is pressed"}, 26 + } 27 + }
+30
screens/--love-transforms
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=25}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > effects@{effects} > graphics@{graphics} > transforms', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + effects={target='--love-output'}, 8 + graphics={target='--love-graphics'}, 9 + }}, 10 + }, 11 + {type='filler', height=15}, 12 + {type='text', 'love.graphics.origin()', font={size=24}}, 13 + {type='text', ' reset the current transform that affects all future shapes'}, 14 + {type='filler', height=10}, 15 + {type='text', 'love.graphics.push()', font={size=24}}, 16 + {type='text', ' save the current transform to a stack'}, 17 + {type='filler', height=10}, 18 + {type='text', 'love.graphics.pop()', font={size=24}}, 19 + {type='text', ' restore the previous transform from the stack'}, 20 + {type='filler', height=10}, 21 + {type='text', 'love.graphics.translate(dx, dy)', font={size=24}}, 22 + {type='text', ' modifies the current transform to move the origin by dx,dy'}, 23 + {type='filler', height=10}, 24 + {type='text', 'love.graphics.rotate(angle)', font={size=24}}, 25 + {type='text', ' modifies the current transform to rotate the axes by angle'}, 26 + {type='filler', height=10}, 27 + {type='text', 'love.graphics.scale(sx, sy)', font={size=24}}, 28 + {type='text', ' modifies the current transform to multiply x, y coordinates in shapes by sx, sy respectively'}, 29 + } 30 + }
+23
screens/--love-utf8
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=20}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > utf8', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + }}, 8 + }, 9 + {type='filler', height=15}, 10 + {type='text', 'some helpers for working with Unicode text'}, 11 + {type='filler', height=15}, 12 + {type='text', 'utf8.codes(text)', font={size=24}}, 13 + {type='text', ' returns an iterator over byte positions and codepoints in text'}, 14 + {type='filler', height=10}, 15 + {type='text', 'utf8.len(text, i, j)', font={size=24}}, 16 + {type='text', ' returns the number of codepoints between bytes i and j, both included'}, 17 + {type='text', ' i and j are optional. i defaults to 1 and j to end of text'}, 18 + {type='text', ' so len(text) returns the number of codepoints in all of text'}, 19 + {type='filler', height=10}, 20 + {type='text', 'utf8.offset(text, n)', font={size=24}}, 21 + {type='text', ' returns the initial byte position of the nth codepoint in text'}, 22 + } 23 + }
+14
screens/--love-window
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=25}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'LOVE@{LÖVE} > effects@{effects} > graphics@{graphics} > window', 5 + attrs={ 6 + LOVE={target='--love'}, 7 + effects={target='--love-output'}, 8 + graphics={target='--love-graphics'}, 9 + }}, 10 + }, 11 + {type='filler', height=15}, 12 + {type='text', 'width, height = love.graphics.getDimensions()', font={size=24}}, 13 + } 14 + }
+13
screens/--lua
··· 1 + { 2 + {type='rows', width=500, default={font={size=21}, line_height=35}, 3 + {type='text', 'Lua programming language', font={size=28}}, 4 + {type='text', 'lua.org (1993)'}, 5 + {type='rows', default={border='grey'}, width=200, 6 + {type='text', 'top-level', target='--lua-def'}, 7 + {type='text', 'values and types', target='--lua-type'}, 8 + {type='text', 'statements', target='--lua-stmt'}, 9 + {type='text', 'expressions', target='--lua-expr'}, 10 + {type='text', 'tables', target='--lua-table'}, 11 + }, 12 + } 13 + }
+25
screens/--lua-def
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=35}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Lua@{Lua} > top-level', 5 + attrs={ 6 + Lua={target='--lua'}, 7 + }}, 8 + }, 9 + {type='filler', height=15}, 10 + {type='text', 'All Lua programs are made of a few kinds of things.'}, 11 + {type='filler', height=15}, 12 + {type='text', 'x = 34', font={size=24}}, 13 + {type='text', ' Variable definitions'}, 14 + {type='filler', height=10}, 15 + {type='text', 'function add(a, b) return a+b end', font={size=24}}, 16 + {type='text', ' Function definitions'}, 17 + {type='text', " (Can contain multiple statements before the 'end'. Including variable definitions.)"}, 18 + {type='filler', height=10}, 19 + {type='text', 'x = add(3, 4)', font={size=24}}, 20 + {type='text', ' Variable definitions can call functions (defined above/earlier)'}, 21 + {type='filler', height=10}, 22 + {type='text', 'add = function(a, b) return a+b end', font={size=24}}, 23 + {type='text', ' Just an alternative way to define the above function. A function name is just a kind of variable name.'}, 24 + } 25 + }
+46
screens/--lua-expr
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=35}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Lua@{Lua} > expressions', 5 + attrs={ 6 + Lua={target='--lua'}, 7 + }}, 8 + }, 9 + {type='filler', height=15}, 10 + {type='text', 11 + {'Anywhere in a statement you might use (not define) a variable, you can use an em@{expression} in its place. Some examples:', 12 + attrs={ 13 + em={font={italic=true}}}}}, 14 + {type='text', '5', font={size=24}}, 15 + {type='text', ' All values (numbers, strings, tables, etc.) are types.'}, 16 + {type='filler', height=10}, 17 + {type='text', '5+3', font={size=24}}, 18 + {type='text', ' You can add (+), subtract (-), multiply (*), divide (/), compute the remainder (%) of two numbers.'}, 19 + {type='filler', height=10}, 20 + {type='text', '4+3*2', font={size=24}}, 21 + {type='text', ' Expressions can contain other expressions, and they follow the precedence rules you learned in school.'}, 22 + {type='text', ' Multiplication, division and remainder happen before addition and subtraction.'}, 23 + {type='filler', height=10}, 24 + {type='text', '(4+3) * 2'}, 25 + {type='text', ' To ensure one expression is calculated before another, use parentheses ()'}, 26 + {type='filler', height=10}, 27 + {type='text', 'x == y'}, 28 + {type='text', " You can compare two expressions to determine if they're equal (==), not equal (~=), greater (>), lesser (<), greater or equal (>=), lesser or equal (<=)."}, 29 + {type='text', " These expressions are handy in the conditions of 'if' statements and loops."}, 30 + {type='filler', height=10}, 31 + {type='text', 'x == 3 and y > 4', font={size=24}}, 32 + {type='text', ' Comparisons return _booleans_ true or false, which can be combined using and/or.'}, 33 + {type='filler', height=10}, 34 + {type='text', 'not x == 3', font={size=24}}, 35 + {type='text', ' Turn a boolean into its opposite.'}, 36 + {type='filler', height=10}, 37 + {type='text', 'nil or 3', font={size=24}}, 38 + {type='text', ' All non-boolean values act like true in and/or except for nil.'}, 39 + {type='filler', height=10}, 40 + {type='text', '"hello ".."world"', font={size=24}}, 41 + {type='text', ' String values can be combined using ..'}, 42 + {type='filler', height=10}, 43 + {type='text', 'add(2, 3)', font={size=24}}, 44 + {type='text', ' Function calls can act as expressions as well as statements.'}, 45 + } 46 + }
+66
screens/--lua-stmt
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=25}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Lua@{Lua} > statements', 5 + attrs={ 6 + Lua={target='--lua'}, 7 + }}, 8 + }, 9 + {type='filler', height=15}, 10 + {type='text', 'Statements do things. Functions contain statements. Some examples of statements:'}, 11 + {type='filler', height=15}, 12 + {type='text', 'x = 5', font={size=24}}, 13 + {type='text', ' storing values in variables'}, 14 + {type='filler', height=10}, 15 + {type='text', 'x, y = 3, 4', font={size=24}}, 16 + {type='text', ' storing values in multiple variables at once'}, 17 + {type='filler', height=10}, 18 + {type='text', 'print("hello")', font={size=24}}, 19 + {type='text', ' calling functions'}, 20 + {type='filler', height=10}, 21 + {type='text', 'x = add(3, 4)', font={size=24}}, 22 + {type='text', ' storing the result of a function in a variable'}, 23 + {type='filler', height=10}, 24 + {type='text', 'return 34'}, 25 + {type='text', ' returning a result from a function'}, 26 + {type='filler', height=10}, 27 + {type='text', 'return 34, 35'}, 28 + {type='text', ' returning multiple results from a function'}, 29 + {type='filler', height=10}, 30 + {type='text', 'if x == 3 then print("equal") end', font={size=24}}, 31 + {type='text', ' doing something only if a condition is met'}, 32 + {type='text', ' This statement can contain other statements between the \'then\' and \'end\'.'}, 33 + {type='filler', height=10}, 34 + {type='text', 'if x == 3 then print("equal") else print("not equal") end', font={size=24}}, 35 + {type='text', ' choosing between two (sets of) statements depending on a condition'}, 36 + {type='filler', height=10}, 37 + {type='text', 'if x == 3 then print("x is 3")', font={size=24}}, 38 + {type='text', 'else print("x is not 3") end', font={size=24}}, 39 + {type='text', ' You can split up a statement into multiple lines anywhere you like.'}, 40 + {type='filler', height=10}, 41 + {type='text', 'if x == 3 then print("x is 3")', font={size=24}}, 42 + {type='text', 'elseif x > 3 then print("x is greater than 3")', font={size=24}}, 43 + {type='text', 'else print("x is less than 3") end', font={size=24}}, 44 + {type='text', ' A more complex condition with more than 2 conditions. You can have any number of them.'}, 45 + {type='filler', height=10}, 46 + {type='text', 'while x == 3 do print("hello") end', font={size=24}}, 47 + {type='text', ' repeatedly doing something as long as a condition is met'}, 48 + {type='filler', height=10}, 49 + {type='text', 'repeat print("hello") until x == 3', font={size=24}}, 50 + {type='text', ' repeatedly doing something as long as a condition is _not_ met'}, 51 + {type='text', ' This kind of _loop_ always runs at least once before it gets to the condition.'}, 52 + {type='filler', height=10}, 53 + {type='text', 'for i=1,3 do print(i) end', font={size=24}}, 54 + {type='text', ' run 3 times, setting i to 1, 2 and 3 once each'}, 55 + {type='text', ' (The keyword \'for\' comes from mathematicians, who say things like "for every number from 1 to 3..")'}, 56 + {type='filler', height=10}, 57 + {type='text', 'for i=0,4,2 do print(i) end', font={size=24}}, 58 + {type='text', ' run 3 times, setting i to 0, 2 and 4 once each'}, 59 + {type='filler', height=10}, 60 + {type='text', 'for i=10,1,-1 do print(i) end', font={size=24}}, 61 + {type='text', ' run 10 times, setting i to 10 at the start and decreasing it by 1 each time'}, 62 + {type='filler', height=10}, 63 + {type='text', 'break', font={size=24}}, 64 + {type='text', ' stop repeatedly running some loop surrounding this statement'}, 65 + } 66 + }
+63
screens/--lua-table
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=25}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Lua@{Lua} > tables', 5 + attrs={ 6 + Lua={target='--lua'}, 7 + }}, 8 + }, 9 + {type='filler', height=15}, 10 + {type='text', "Lua's tables are a versatile way to group data together in ways that make it easy to slice and dice."}, 11 + {type='filler', height=15}, 12 + {type='text', '{} is an empty table value/expression.'}, 13 + {type='filler', height=10}, 14 + {type='text', 'h = {11, 12, 13} creates a table that can be looked up by number _keys_.'}, 15 + {type='text', ' h[1] is now 11, h[2] is 12 and so on.'}, 16 + {type='filler', height=10}, 17 + {type='text', 'h = {a=34} creates a table that can be looked up by string keys.'}, 18 + {type='text', " h['a'] is now 34, h['b'] is 35."}, 19 + {type='text', ' You can also shorten constant string keys using periods. h.a is 34, h.b is 35.'}, 20 + {type='filler', height=10}, 21 + {type='text', 'if h is an array. #h is an expression that returns the length of h'}, 22 + {type='text', " String keys don't count towards the length of an array."}, 23 + {type='text', ' Arrays also stop at the first missing/nil slot. The length of {1, 2, nil, 4} is 2.'}, 24 + {type='filler', height=15}, 25 + {type='text', 'Some useful functions operating on tables:', font_size=28}, 26 + {type='filler', height=15}, 27 + {type='text', 'pairs(h)', font={size=24}}, 28 + {type='text', ' Returns an _iterator_, a kind of function, that lets you conveniently loop over all the keys of a table.'}, 29 + {type='text', ' For example, this program will print out the contents of a table: for key, value in pairs(h) do print(key, value) end'}, 30 + {type='filler', height=15}, 31 + {type='text', 'Some useful functions operating on arrays:', font_size=28}, 32 + {type='filler', height=15}, 33 + {type='text', 'ipairs(h)', font={size=24}}, 34 + {type='text', " Returns an iterator that lets you conveniently loop over indexes and elements of an array."}, 35 + {type='text', ' For example, this program will print out the contents of an array: for _, x in ipairs(h) do print(x) end'}, 36 + {type='filler', height=10}, 37 + {type='text', 'table.insert(h, value)', font={size=24}}, 38 + {type='text', ' Appends a value to the end of array h.'}, 39 + {type='filler', height=10}, 40 + {type='text', 'table.insert(h, index, value)', font={size=24}}, 41 + {type='text', ' Inserts a value at index in the middle of array h.'}, 42 + {type='filler', height=10}, 43 + {type='text', 'table.remove(h)', font={size=24}}, 44 + {type='text', ' Removes the value at the final index of array h.'}, 45 + {type='filler', height=10}, 46 + {type='text', 'table.remove(h, index)', font={size=24}}, 47 + {type='text', ' Removes the value at index in the middle of array h, compacting later values up.'}, 48 + {type='filler', height=10}, 49 + {type='text', 'table.sort(h)', font={size=24}}, 50 + {type='text', ' Sorts the elements in array h.'}, 51 + {type='filler', height=10}, 52 + {type='text', 'table.sort(h, f)', font={size=24}}, 53 + {type='text', ' Sorts the elements in array h by a custom comparison function f.'}, 54 + {type='text', ' f needs to accept 2 arguments and return true if the first is less than the second.'}, 55 + {type='filler', height=10}, 56 + {type='text', 'table.reverse(h)', font={size=24}}, 57 + {type='text', ' Flips the order of the elements of array h so the final element becomes the first, etc.'}, 58 + {type='filler', height=10}, 59 + {type='text', 'table.concat(h)', font={size=24}}, 60 + {type='text', ' Appends all the elements of array h into a single string and returns it.'}, 61 + {type='text', ' All the elements must be strings.'}, 62 + } 63 + }
+21
screens/--lua-type
··· 1 + { 2 + {type='rows', default={font={size=21}, line_height=25}, 3 + {type='text', font={size=28}, line_height=50, 4 + {'Lua@{Lua} > values and types', 5 + attrs={ 6 + Lua={target='--lua'}, 7 + }}, 8 + }, 9 + {type='filler', height=15}, 10 + {type='text', 'Variables in Lua can hold a few kinds of things, including:'}, 11 + {type='text', '- numbers like 0, 3, -5 or 2.5'}, 12 + {type='text', '- strings like "abc" or \'hello there\''}, 13 + {type='text', '- booleans true or false'}, 14 + {type='text', 15 + {'- fn@{functions}', attrs={fn={target='--lua-fn'}}}, 16 + target='--lua-def'}, 17 + {type='text', 18 + {'- tables@{tables} like {1, 2, 3} or {dogs=1}', attrs={tables={target='--lua-table'}}}}, 19 + {type='text', '- nil, which represents the absence of other things'}, 20 + } 21 + }