Created
July 12, 2015 15:41
-
-
Save Utsira/5db6e0eccb68c70d3670 to your computer and use it in GitHub Desktop.
Markdown Codea with paragraph stitching
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# Main | |
-- Markdown Codea | |
-- paragraph-stitching version | |
-- by Yojimbo2000 | |
displayMode(OVERLAY) | |
displayMode(FULLSCREEN) | |
function setup() | |
state = Stitch | |
state.init() | |
end | |
function draw() | |
background(130, 158, 189, 255) | |
state.draw() | |
end | |
function touched(t) | |
state.touched(t) | |
end | |
function orientationChanged() | |
state.orientation() | |
end | |
--# Stitch | |
Stitch = {} | |
function Stitch.init() | |
para = 1 | |
trackY = HEIGHT * 0.6 | |
paragraphs = {} | |
paragraphs[1] = Paragraph(para, trackY) | |
trackY = trackY - (paragraphs[1].img.height * 0.5) | |
scrollY = trackY | |
parameter.watch("para") | |
fontSize(20) | |
Notify{ | |
delay = 2, | |
recurring = 4, | |
pos = vec2(WIDTH*0.5, HEIGHT * 0.25), | |
condition = function() return not endOfText end, | |
img = Markdown{ | |
width = WIDTH * 0.4, | |
palette = "dark", | |
paragraph = [[## Touch the lower half of the screen to move down]] | |
} | |
} | |
Notify { | |
delay = 6, | |
pos = vec2(WIDTH*0.5, HEIGHT * 0.75), | |
img = Markdown{ | |
width = WIDTH * 0.4, | |
palette = "dark", | |
paragraph = [[## Touch the top half of the screen to move back up]] | |
} | |
} | |
end | |
function Stitch.draw() | |
pushMatrix() | |
scrollY = scrollY + (trackY - scrollY) * 0.1 | |
translate(0, -scrollY+HEIGHT*0.3) | |
for i,v in ipairs(paragraphs) do | |
if v.kill then | |
table.remove(paragraphs, i) | |
else | |
v:draw() | |
end | |
end | |
popMatrix() | |
Notify.updateAll() | |
end | |
function Stitch.touched(t) | |
if t.state==ENDED then | |
Notify.touchAll() | |
if t.y < HEIGHT * 0.5 then | |
para = para + 1 | |
-- local lastPara = #paragraphs --remember the last non-new para (to animate sliding up) | |
if para>#paragraphs then --see if there's a new para | |
local new = Paragraph(para, trackY) | |
if new.kill then --no more paragraphs | |
para = para - 1 | |
endOfText = true | |
return | |
end | |
paragraphs[#paragraphs+1] = new | |
else | |
trackY = trackY - (paragraphs[para-1].img.height * 0.5) | |
end | |
paragraphs[para-1]:fade(128) --fade animations (paragraph focus) | |
paragraphs[para]:fade(255) | |
-- local h = (paragraphs[para].img.height + paragraphs[para-1].img.height)*0.5 | |
trackY = trackY - (paragraphs[para].img.height * 0.5) | |
--[[ | |
for i=1,lastPara do | |
paragraphs[i]:fade(255-(math.abs(i-para)*40)) | |
end | |
]] | |
elseif para>1 then | |
local h = (paragraphs[para].img.height + paragraphs[para-1].img.height)*0.5 | |
trackY = trackY + h | |
para = para - 1 | |
endOfText = false | |
--[[ | |
for i,v in ipairs(paragraphs) do | |
paragraphs[i]:fade(255-(math.abs(i-para)*40)) | |
end | |
]] | |
paragraphs[para]:fade(255) | |
paragraphs[para+1]:fade(128) | |
end | |
end | |
end | |
function Stitch.orientation() | |
for i,v in ipairs(paragraphs) do | |
v:setText() --rerender the text | |
v.m.texture = v.img | |
v.w = v.img.width --reset width/ height | |
v.h = v.img.height | |
if i>1 then | |
local u = paragraphs[i-1] | |
v.y = u.y - ((v.h + u.h) * 0.5) --recalculate y position | |
end | |
end | |
trackY = paragraphs[para].y - paragraphs[para].h * 0.5 --recalculate focus/ insertion point. This is sometimes slightly off. | |
end | |
--# Paragraph | |
Paragraph = class() | |
local size = 25 | |
function Paragraph:init(para) | |
self.text = parseParagraph(testString, para) | |
if not self.text then self.kill=true return end | |
self:setText() | |
self.m = mesh() | |
self.m.texture = self.img | |
self.m:addRect(0,0,0,0) | |
trackY = trackY - (self.img.height * 0.5) | |
self.y = trackY | |
self.w, self.h = self.img.width * 0.5, self.img.height * 0.5 | |
self.alpha = 0 | |
self.targetAlpha = 255 | |
tween(1, self, {w = self.img.width, h=self.img.height}, tween.easing.sineOut) | |
end | |
function Paragraph:setText() | |
fontSize(size) --use the regular fontsize command to set the size of the body level text | |
self.img = Markdown{ | |
-- debug = true, --debug print | |
palette = "solarized", | |
width = WIDTH *0.8, --wrap width | |
paragraph = self.text | |
} | |
end | |
function Paragraph:draw() | |
self.alpha = self.alpha + (self.targetAlpha - self.alpha) * 0.1 | |
self.m:setRect(1,WIDTH*0.5, self.y, self.w, self.h) | |
self.m:setRectColor(1, 255, 255, 255, self.alpha) | |
self.m:draw() | |
end | |
function Paragraph:fade(alpha) | |
self.targetAlpha = alpha | |
end | |
--# Notify | |
Notify = class() | |
local notifications = {} --store notifications | |
function Notify:init(t) | |
local del = t.delay or 0 | |
self.timer = ElapsedTime + del --time until notification should appear | |
self.pos = t.pos or (vec2(WIDTH, HEIGHT)*0.5) | |
self.img = t.img --the only mandatory input for this class, the image that will be displayed | |
self.alpha = 0 | |
self.targetAlpha = 255 | |
self.recurring = t.recurring --set this if the notification should reoccur, set to seconds delay for subsequent notifications | |
self.condition = t.condition --condition which must be met for recurrance | |
self.duration = t.duration --seconds until auto dismissal. omit this if notification should stay until a touch event | |
notifications[#notifications+1] = self | |
end | |
function Notify:update() | |
if self.condition and not self.condition() then return end | |
if ElapsedTime > self.timer then | |
self.alpha = self.alpha + (self.targetAlpha - self.alpha) * 0.1 --fade in or out | |
tint(255, self.alpha) | |
sprite(self.img, self.pos.x, self.pos.y, self.img.width) | |
tint() | |
if self.targetAlpha == 0 and self.alpha<0.1 then --if fade-out has completed | |
if self.recurring then | |
-- if self.condition() then | |
self.timer = ElapsedTime + self.recurring --reset ready for next fade-in | |
self.recurring = self.recurring * 2 --delay gradually gets longer | |
self.targetAlpha = 255 | |
-- end | |
else | |
self.kill = true | |
end | |
end | |
if self.duration and ElapsedTime > self.timer + self.duration then | |
self.targetAlpha = 0 --auto-dismissal of notification (if this is set.) | |
end | |
end | |
end | |
function Notify:touched() | |
if ElapsedTime > self.timer + 0.5 then --split second delay | |
self.targetAlpha = 0 --before dismssal | |
end | |
end | |
--functions below are not part of the class | |
function Notify.updateAll() | |
for i,v in ipairs(notifications) do | |
if v.kill then | |
table.remove(notifications, i) | |
else | |
v:update() | |
end | |
end | |
end | |
function Notify.touchAll() | |
for i,v in ipairs(notifications) do | |
v:touched() | |
end | |
end | |
--# Markdown | |
--markdown-esque string formatting | |
local style = { --simple cascading styles | |
palette = { | |
solarized = {color(214, 212, 203, 255), | |
color(91, 73, 51, 255), | |
color(95, 89, 135, 255), | |
color(89, 78, 66, 255), | |
color(133, 97, 72, 255) | |
}, | |
dark = {color(28, 41, 60, 200), | |
color(210, 208, 181, 255), | |
color(0, 255, 225, 255), | |
color(223, 202, 159, 255), | |
color(215, 223, 171, 255) | |
} | |
}, | |
body = { --body format | |
font = "IowanOldStyle", --"Baskerville", --"Optima", | |
col = 2, | |
}, | |
heading = { --the array part of style.heading contains styles for heading1, heading2, etc (overrides global) | |
{--font = "HelveticaNeueUltraLight", | |
size=2.5}, --Heading1. Size is proportion of body size | |
{size=1.5}, --Heading2 | |
{size=1.2}, --Heading 3 | |
all = { --global headings settings | |
font = "Avenir", --"HelveticaNeueLight", | |
col = 3, | |
} | |
}, | |
block = { --block quotes | |
size = 0.9, | |
font = "Optima", --"Verdana", -- "HelveticaNeue", | |
col = 4, }, | |
bullet = { --bullet points | |
size = 0.9, | |
col = 5, | |
font = "Optima" | |
}, | |
--non-standard names for font families. add entries here if family doesnt conform to suffix pattern of "[no suffix]" for regular, "-Italic" for italic, "-Bold", "-BoldItalic", (eg if the family has, say, "-Roman" for regular, "-Black" for bold, "-Oblique" for italic etc) | |
Palatino = { | |
regular = "Palatino-Roman" | |
}, | |
HoeflerText = { | |
regular = "HoeflerText-Regular", | |
bold = "HoeflerText-Black", | |
boldItalic = "HoeflerText-BlackItalic" | |
}, | |
Optima = { | |
regular = "Optima-Regular" | |
}, | |
HelveticaNeueUltraLight = { | |
regular = "HelveticaNeue-UltraLight", | |
italic = "HelveticaNeue-UltraLightItalic", | |
bold = "HelveticaNeue-Light", | |
boldItalic = "HelveticaNeue-LightItalic" | |
}, | |
HelveticaNeueLight = { | |
regular = "HelveticaNeue-Light", | |
italic = "HelveticaNeue-LightItalic", | |
bold = "HelveticaNeue", | |
boldItalic = "HelveticaNeue-Italic" | |
}, | |
IowanOldStyle = { | |
regular = "IowanOldStyle-Roman" | |
}, | |
Avenir = { | |
regular = "Avenir-Roman", | |
italic = "Avenir-Oblique", | |
bold = "Avenir-Heavy", | |
boldItalic = "Avenir-HeavyOblique" | |
} | |
} | |
--font("Didot-Bold") | |
local face --name of base font currently being used | |
local size --size of base font | |
local palette --color palette in use | |
function parseParagraph(tex, paraNo) | |
tex = tex.."\n\n" | |
local paraCount = 0 | |
for paragraph in string.gmatch(tex, "(.-)\n\n") do | |
paraCount = paraCount + 1 | |
if paraCount == paraNo then | |
return paragraph | |
end | |
end | |
end | |
function Markdown(t) | |
local paragraph = t.paragraph | |
pushStyle() | |
local _, baseHeight = textSize("dummy") --defines paragraph separation | |
size = fontSize() --base size of body text | |
palette = style.palette[t.palette] --color scheme in use | |
local gutter = size * 0.5 --border around the text | |
--PRE-PROCESS TYPOGRAPHY | |
paragraph = string.gsub(paragraph, "(%S+)'", "%1\u{2019}") --right single quote. Do this first in order to catch apostrophes | |
paragraph = string.gsub(paragraph, "'(%S+)", "\u{2018}%1") --left single quote | |
paragraph = string.gsub(paragraph, "%-%-%-", "\u{2014}") --em-dash | |
paragraph = string.gsub(paragraph, "%-%-", "\u{2013}") --en-dash | |
paragraph = string.gsub(paragraph, "\"(%S+)", "\u{201C}%1") --left double quote | |
paragraph = string.gsub(paragraph, "(%S+)\"", "%1\u{201D}") --right double quote | |
--RESET TO DEFAULT BODY FONT FOR NEW PARAGRAPH | |
style.set(style.body) | |
local indent = gutter | |
local cursorSet = false --set to true once initial cursor position for paragraph is set according to font size, paragraph separation | |
--CHECK ELEMENTS THAT ONLY COME AT START OF PARAGRAPH | |
--BLOCK | |
local bl | |
paragraph, bl = string.gsub(paragraph, "^> ", "", 1) | |
if bl>0 then | |
indent = gutter + (size * 3) --indent paragraph | |
style.set(style.block) | |
end | |
--HEADINGS | |
local hBegin, hEnd = string.find(paragraph, "^%#+") --look for number of hashes at start of para | |
if hBegin then | |
local headLevel = hEnd + 1 - hBegin | |
paragraph = string.gsub(paragraph, "^%#+%s?", "") | |
style.set(style.heading.all) --global heading settings | |
style.set(style.heading[headLevel]) --level specific settings | |
end | |
textMode(CORNER) | |
textWrapWidth(t.width - (indent - gutter)) --use built in wrapping to | |
local _, height = textSize(paragraph) --guestimate height of paragraph | |
local img = image(t.width+(gutter*2), height+(gutter*2)) | |
setContext(img) | |
local back = t.background or palette[1] | |
background(back) | |
textWrapWidth(0) --we need to turn off text wrapping and implement our own because the built-in wrapping does not give us control over the point at which the text starts (first line indentation), nor tell us where the last line ends. | |
local cursor = vec2(indent,height+gutter) | |
local italic = false | |
local bold = false | |
paragraph = paragraph.."\n" --add return (this also allows final part of line to be captured) | |
for subpara in string.gmatch(paragraph, "(.-)\n") do --for tight-nested lists | |
subpara = subpara.." " | |
--BULLETS | |
local bu | |
subpara, bu = string.gsub(subpara, "^%- ", "", 1) | |
if bu>0 then | |
style.set(style.bullet) | |
local _, h = textSize("dummy") | |
cursor.y = cursor.y - h | |
text("\u{2022}", gutter + (size * 1.75), cursor.y) | |
cursorSet = true | |
indent = gutter + (size * 3) | |
cursor.x = indent | |
end | |
--PARSE WORDS | |
for element, control in string.gmatch(subpara, "(.-)([%s*]+)") do -- "(.-)(%*+%s+)" separate at white space, * | |
if string.find(control, "%s") then --if whitespace | |
element = element.." " --put spaces back in | |
end | |
local w,h = textSize(element) --find size of word | |
if t.debug then print(element,control) end --debug print | |
if not cursorSet then --place first line of paragraph (paragraph separation etc) | |
cursor.y = cursor.y - h | |
cursorSet = true | |
end | |
--WRAPPING | |
if cursor.x + w > t.width then --if word will take us over edge | |
cursor.x = indent --carriage return | |
cursor.y = cursor.y - h | |
end | |
text(element, cursor.x, cursor.y) --print word | |
cursor.x = cursor.x + w | |
--BOLD AND ITALICS | |
local eBegin, eEnd = string.find(control, "%*+") --count number of asterisks | |
if eBegin then | |
local emph = eEnd + 1 - eBegin | |
if emph==3 then | |
bold = not bold | |
italic = not italic | |
elseif emph==2 then | |
bold = not bold | |
else | |
italic = not italic | |
end | |
if bold and italic then | |
font(style.boldItalic(face)) | |
elseif bold then | |
font(style.bold(face)) | |
elseif italic then | |
font(style.italic(face)) | |
else | |
style.font(face) | |
end | |
end | |
end --of word | |
end --of subpara | |
setContext() | |
popStyle() | |
return img | |
end | |
function style.set(sty) | |
for func, val in pairs(sty) do --set font features for whatever keys are in the style table | |
style[func](val) | |
end | |
end | |
--3 functions to handle non-standard named fonts (eg -Black for bold, -Oblique for italc etc) | |
function style.bold(f) | |
if style[f] and style[f].bold then --check if a nonstandard bold face is specified | |
return style[f].bold | |
end | |
return f.."-Bold" --else just append bold to family name | |
end | |
function style.italic(f) | |
if style[f] and style[f].italic then | |
return style[f].italic | |
end | |
return f.."-Italic" | |
end | |
function style.boldItalic(f) | |
if style[f] and style[f].boldItalic then | |
return style[f].boldItalic | |
end | |
return f.."-BoldItalic" | |
end | |
--the function names below correspond to the bottom level keys in the style table, eg font, col, size | |
function style.font(f) | |
face = f | |
if style[face] and style[face].regular then | |
font(style[face].regular) | |
else | |
font(face) | |
end | |
end | |
function style.col(col) | |
fill(palette[col]) | |
end | |
function style.size(s) | |
fontSize(size * s) | |
end | |
--# Sampletext | |
testString = [[ | |
# *Markdown*-like text formatting --- in **Codea!** | |
Have you ever wanted an easy way to format text --- adding *italic*, **bold**, ***bold-italic,*** different type faces, font sizes and colours, indented block quotes, plus typography features such as "smart quotes" and em-dashes, all of them nestable within one-another --- on the fly? | |
## Well now you can, with **Markdown Codea.** | |
> *Try switching the orientation of your device to test the hand-made text wrapping feature! Touch the screen to scroll the text* | |
### "But --- *what **is** this **Markdown**?!?*" I hear you yell. | |
Markdown is a way of adding rich formatting, such as: | |
- *Emphasis* | |
- **Strong emphasis** | |
- *Really, **really** strong emphasis* | |
- Or **really, *really* strong emphasis** if you prefer | |
- Block quotes, different headings... | |
- Oh, and ***bullet points!*** Bullet points can be displayed in a tight list like this, by only separating each item with one return | |
Or, if you prefer, you can have: | |
- A loose list | |
- Of bullet points | |
- Just separate each bullet with two returns | |
And it's all done using plain text. So it's great for using in plain-text environments such as code editors. As *Markdown's* creator John Gruber said: | |
> The overriding design goal for *Markdown's* formatting syntax is to make it as **readable** as possible. The idea is that a *Markdown*-formatted document should be publishable **as-is, as plain text, *without* looking like it's been marked up with tags** or formatting instructions. | |
**But the best thing about Markdown is --- *you already know how to use it***. It's used on lots of forums, including *Codea Talk.* I've thrown in some nice, *Pandoc*-inspired extras such as typographer's quotes for the apostrophe and for 'single quotation marks' and "double" quote marks, plus en--dash and em---dash | |
]] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment