Last active
January 9, 2016 13:37
-
-
Save Utsira/9ba147647374a6bd2661 to your computer and use it in GitHub Desktop.
Markdown-like text formatting in Codea
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 | |
-- by Yojimbo2000 | |
displayMode(FULLSCREEN) | |
function setup() | |
setText() | |
y,vel = 0,0 | |
scrollY={} --store deltas for smooth scrolling upon finger release | |
end | |
function setText() | |
fontSize(25) --use the regular fontsize command to set the size of the body level text | |
textImage = Markdown{ | |
-- debug = true, --debug print | |
width = WIDTH *0.9, --wrap width | |
text = testString | |
} | |
end | |
function draw() | |
background(214, 212, 203, 255) | |
if y<0 then vel = math.abs(vel) * 0.5 --scroll bounce | |
elseif y>HEIGHT then vel = -math.abs(vel) * 0.5 | |
end | |
if not touching then vel = vel * 0.95 end --friction upon touch end | |
y = y + vel | |
sprite(textImage, WIDTH*0.5, y) | |
end | |
function touched(t) | |
if t.state==ENDED then | |
local av = 0 --calculate average deltas over last 10 | |
for i=1, #scrollY do | |
av = av + scrollY[i] | |
end | |
vel = av / #scrollY | |
scrollY = {} --reset delta table | |
touching = false | |
else | |
if #scrollY>10 then --store last 10 deltas | |
table.remove(scrollY, 1) | |
else | |
scrollY[#scrollY+1]=t.deltaY | |
end | |
vel = t.deltaY | |
touching = true | |
end | |
end | |
function orientationChanged() | |
setText() | |
end | |
--# Markdown | |
--markdown-esque string formatting | |
local style = { --simple cascading styles | |
body = { --body format | |
font = "IowanOldStyle", --"Baskerville", --"Optima", | |
col = color(91, 73, 51, 255) | |
}, | |
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 = color(95, 89, 135, 255) | |
} | |
}, | |
block = { --block quotes | |
size = 0.9, | |
font = "Optima", --"Verdana", -- "HelveticaNeue", | |
col = color(89, 78, 66, 255)}, | |
bullet = { --bullet points | |
size = 0.9, | |
col = color(133, 97, 72, 255), | |
font = "Optima" | |
}, | |
--non-standard names for font families. add entries here if they dont conform to suffix pattern of "", "-Italic", "-Bold", "-BoldItalic", (eg -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 | |
function Markdown(t) | |
textMode(CORNER) | |
local _, baseHeight = textSize("dummy") --defines paragraph separation | |
size = fontSize() --base size of body text | |
local img = image(t.width, HEIGHT * 2) --height | |
setContext(img) | |
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(0,HEIGHT * 2) | |
local italic = false | |
local bold = false | |
-- local tightList = false --remove paragraph separation for bullets | |
local indent = 0 --for block quotations | |
local parSep = baseHeight | |
for paragraph,sep in string.gmatch(testString, "(.-)(\n%s*)") do | |
print (paragraph) | |
--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) | |
cursor.x = 0 | |
indent = 0 | |
fontSize(size) | |
paragraph = paragraph.."\n" --add return (this also allows final part of line to be captured, as return is a whitespace character) | |
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 = size * 3 --indent paragraph | |
cursor.x = indent | |
style.set(style.block) | |
end | |
--BULLETS | |
local bu | |
paragraph, bu = string.gsub(paragraph, "^%- ", "", 1) | |
if bu>0 then | |
cursor.y = cursor.y - (parSep + baseHeight) | |
--[[ | |
if not tightList then | |
cursor.y = cursor.y - parSep | |
end | |
]] | |
-- tightList = true | |
style.set(style.bullet) | |
text("\u{2022}", size * 1.75, cursor.y) | |
cursorSet = true | |
indent = size * 3 | |
cursor.x = indent | |
-- paragraph = "\u{2022} "..paragraph | |
--[[ | |
else | |
tightList = false | |
]] | |
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 | |
--PARSE WORDS | |
for element, control in string.gmatch(paragraph, "(.-)([%s*]+)") do --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+parSep) | |
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 | |
--CHECK PARAGRAPH SEPARATION (NUMBER OF RETURNS) FOR TIGHT/LOOSE LISTS | |
local _,returns = string.gsub(sep, "\n", "") | |
if returns==1 then | |
parSep = 0 --tight list | |
else | |
parSep = baseHeight --loose list | |
end | |
end --of paragraph | |
setContext() | |
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(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
Nice