Skip to content

Commit

Permalink
Merge pull request #5 from simoncozens/master
Browse files Browse the repository at this point in the history
Implement more todo items
  • Loading branch information
jvgaultney authored Oct 24, 2018
2 parents 7ee5bdc + 2fdbecb commit bd3ff7f
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 22 deletions.
48 changes: 32 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ FontProof requires [SILE](https://github.com/simoncozens/sile). You'll need to i

_Note that SILE is changing rapidly. The current class was developed for unreleased version 0.9.4 but also works with the also unreleased 0.9.5 current master. I will try to keep the class up to date for future released versions._

To install FontProof download this project (or better yet, _git clone_) into any folder on your drive. From within that directory you should then be able to compile the basic FontProof test doc:
To install FontProof on SILE 0.9.5, run:

sile -e 'installPackage("fontproof");os.exit()'

On older versions, download this project (or better yet, _git clone_) into any folder on your drive. From within that directory you should then be able to compile the basic FontProof test doc:

$ sile fpTest.sil

Expand Down Expand Up @@ -115,6 +119,16 @@ This is the main FontProof command, and can be used to set both simple test text
\proof[size="10pt,11pt,12pt,16pt"]{This is basic proof text in a range of sizes}
\proof[size="10,11,12,13,20"]{This is basic proof text in a range of sizes with numbers only}
\proof[features="Ligatures=Rare, Ligatures=Discretionary"]{This is a proof with rare ligatures turned on}
\proof[features="+dlig,+hlig"]{This is a proof with features manipulated manually}
\proof[language="grk",direction = "rtl"]{Here are some options which are passed on to the font command}
\proof[color="green"]{Here are some options which are passed on to the color command}
\proof[shapers="ot,fallback"]{Here we pass a list of subshapers to Harfbuzz}
```

#### __\pattern[]{}__
Expand Down Expand Up @@ -259,6 +273,22 @@ This produces a table that shows every encoded character in the font, formatted

This produces the same, except only showing the given range of USVs.

#### __\gutenberg__

This downloads and typesets a text from Project Gutenberg.

```
\gutenberg[id=100] % The complete works of Shakespeare
```

#### __\pi__

Typesets some digits of pi:

```
\pi[digits=500]
```

## Commands provided natively by SILE

These work in SILE even without FontProof, although you would then need to load them with `\script[src=packages/specimen]`.
Expand Down Expand Up @@ -286,25 +316,11 @@ We'd love to see even more potential parameters to `\proof`:
```
\proof[
size = "10, 11/13, 12/16",
features = "Ligatures=Rare, Ligatures=Discretionary"
featuresraw = "+dlig,+hlig",
language = "grk",
direction = "rtl",
columns = 3,
lines = 12,
color = #999999,
shaper = "OT"
lines = 12
]{Text}
```

#### More preset texts in different languages

These would be like `\pangrams` but be configurable by language. They would also contain pages and pages of real (public domain) texts.

#### Tests for numbers and punctuation

`\pattern[chars="@",reps="0123456789",format="list"]{0@1@2@3@4@5@6@7@8@9@0}` is already useful. `\pi[digits=100]` would be fun.

#### Support for multiple fonts with a single test type

Oooooh!
Expand Down
57 changes: 51 additions & 6 deletions classes/fontproof.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ function fontproof:init()
self:loadPackage("lorem")
self:loadPackage("specimen")
self:loadPackage("rebox")
self:loadPackage("features")
self:loadPackage("color")
self:loadPackage("fontprooftexts")
self:loadPackage("fontproofgroups")
self:loadPackage("gutenberg-client")
SILE.settings.set("document.parindent",SILE.nodefactory.zeroGlue)
SILE.settings.set("document.spaceskip")
self.pageTemplate.firstContentFrame = self.pageTemplate.frames["content"]
Expand Down Expand Up @@ -174,13 +177,38 @@ SILE.registerCommand("proof", function (options, content)
end
if options.size then proof.sizes = sizesplit(options.size)
else proof.sizes = {SILE.scratch.fontproof.testfont.size} end
proof.family, proof.filename = fontsource(options.family, options.filename)
for i = 1, #proof.sizes do
SILE.settings.temporarily(function()
SILE.Commands["font"]({ family = proof.family, filename = proof.filename, size = proof.sizes[i] }, {})
SILE.call("raggedright",{},procontent)
end)
if options.shapers then
if SILE.settings.declarations["harfbuzz.subshapers"] then
SILE.settings.set("harfbuzz.subshapers", options.shapers)
else SU.warn("Can't use shapers on this version of SILE; upgrade!") end
end
proof.family, proof.filename = fontsource(options.family, options.filename)
SILE.call("color", options, function ()
for i = 1, #proof.sizes do
SILE.settings.temporarily(function()
local fontoptions ={ family = proof.family, filename = proof.filename, size = proof.sizes[i] }
-- Pass on some options from \proof to \font.
local tocopy = { "language"; "direction"; "script" }
for i = 1,#tocopy do
if options[tocopy[i]] then fontoptions[tocopy[i]] = options[tocopy[i]] end
end
-- Add feature options
if options.featuresraw then fontoptions.features = options.featuresraw end
if options.features then
for i in SU.gtoke(options.features, ",") do
if i.string then
local feat = {}
_,_,k,v = i.string:find("(%w+)=(.*)")
feat[k] = v
SILE.call("add-font-feature", feat, {})
end
end
end
SILE.Commands["font"](fontoptions, {})
SILE.call("raggedright",{},procontent)
end)
end
end)
end)

SILE.registerCommand("pattern", function(options, content)
Expand Down Expand Up @@ -295,6 +323,23 @@ local hasGlyph = function(g)
return false
end

SILE.registerCommand("pi", function (options, content)
local digits = tonumber(options.digits) or 100
local url = "https://uploadbeta.com/api/pi/?n="..(digits+4)
if not pcall(function() http = require("ssl.https") end) then
SU.error("Install luasec from luarocks")
end
local result, statuscode, content = http.request(url)
if statuscode ~= 200 then
SU.error("Could not read pi from "..url..": "..statuscode)
end
digits = "3."..result:sub(4,-2)
for i = 1,#digits do
SILE.typesetter:typeset(digits:sub(i,i))
SILE.typesetter:pushPenalty({}) -- Ugly
end
end)

SILE.registerCommand("unicharchart", function (options, content)
local type = options.type or "all"
local rows = tonumber(options.rows) or 16
Expand Down
8 changes: 8 additions & 0 deletions fpFull.sil
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ Text under subsection head

Adhesion: \adhesion[characters=adhesion]

\bigskip

Here are some digits of pi: \pi[digits=500]

\setTestFont[family="EB Garamond"]
\proof[features = "Ligatures=Discretionary, Ligatures=Historic",language = "grk",direction = "rtl",shapers = "ot", color="green"]{This Turns on Historical Features}
\setTestFont[family="Georgia"]

\section{Built-in SILE commands}

\pangrams
Expand Down
9 changes: 9 additions & 0 deletions fpGutenberg.sil
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
\begin[class=fontproof]{document}
\setTestFont[family="Gentium"]
\set[parameter=document.parskip,value=12pt plus 2pt minus 2pt]
\gutenberg[id=32549]

\set[parameter=document.parskip,value=2pt]
\setTestFont[family="Murty Telugu"]
\gutenberg[id=39561]
\end{document}
189 changes: 189 additions & 0 deletions packages/gutenberg-client.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
local json = {}
-- Internal functions.

local function kind_of(obj)
if type(obj) ~= 'table' then return type(obj) end
local i = 1
for _ in pairs(obj) do
if obj[i] ~= nil then i = i + 1 else return 'table' end
end
if i == 1 then return 'table' else return 'array' end
end

local function escape_str(s)
local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'}
local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'}
for i, c in ipairs(in_char) do
s = s:gsub(c, '\\' .. out_char[i])
end
return s
end

-- Returns pos, did_find; there are two cases:
-- 1. Delimiter found: pos = pos after leading space + delim; did_find = true.
-- 2. Delimiter not found: pos = pos after leading space; did_find = false.
-- This throws an error if err_if_missing is true and the delim is not found.
local function skip_delim(str, pos, delim, err_if_missing)
pos = pos + #str:match('^%s*', pos)
if str:sub(pos, pos) ~= delim then
if err_if_missing then
error('Expected ' .. delim .. ' near position ' .. pos)
end
return pos, false
end
return pos + 1, true
end

-- Expects the given pos to be the first character after the opening quote.
-- Returns val, pos; the returned pos is after the closing quote character.
local function parse_str_val(str, pos, val)
val = val or ''
local early_end_error = 'End of input found while parsing string.'
if pos > #str then error(early_end_error) end
local c = str:sub(pos, pos)
if c == '"' then return val, pos + 1 end
if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end
-- We must have a \ character.
local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}
local nextc = str:sub(pos + 1, pos + 1)
if not nextc then error(early_end_error) end
return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc))
end

-- Returns val, pos; the returned pos is after the number's final character.
local function parse_num_val(str, pos)
local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
local val = tonumber(num_str)
if not val then error('Error parsing number at position ' .. pos .. '.') end
return val, pos + #num_str
end


-- Public values and functions.

function json.stringify(obj, as_key)
local s = {} -- We'll build the string as an array of strings to be concatenated.
local kind = kind_of(obj) -- This is 'array' if it's an array or type(obj) otherwise.
if kind == 'array' then
if as_key then error('Can\'t encode array as key.') end
s[#s + 1] = '['
for i, val in ipairs(obj) do
if i > 1 then s[#s + 1] = ', ' end
s[#s + 1] = json.stringify(val)
end
s[#s + 1] = ']'
elseif kind == 'table' then
if as_key then error('Can\'t encode table as key.') end
s[#s + 1] = '{'
for k, v in pairs(obj) do
if #s > 1 then s[#s + 1] = ', ' end
s[#s + 1] = json.stringify(k, true)
s[#s + 1] = ':'
s[#s + 1] = json.stringify(v)
end
s[#s + 1] = '}'
elseif kind == 'string' then
return '"' .. escape_str(obj) .. '"'
elseif kind == 'number' then
if as_key then return '"' .. tostring(obj) .. '"' end
return tostring(obj)
elseif kind == 'boolean' then
return tostring(obj)
elseif kind == 'nil' then
return 'null'
else
error('Unjsonifiable type: ' .. kind .. '.')
end
return table.concat(s)
end

json.null = {} -- This is a one-off table to represent the null value.

function json.parse(str, pos, end_delim)
pos = pos or 1
if pos > #str then error('Reached unexpected end of input.') end
local pos = pos + #str:match('^%s*', pos) -- Skip whitespace.
local first = str:sub(pos, pos)
if first == '{' then -- Parse an object.
local obj, key, delim_found = {}, true, true
pos = pos + 1
while true do
key, pos = json.parse(str, pos, '}')
if key == nil then return obj, pos end
if not delim_found then error('Comma missing between object items.') end
pos = skip_delim(str, pos, ':', true) -- true -> error if missing.
obj[key], pos = json.parse(str, pos)
pos, delim_found = skip_delim(str, pos, ',')
end
elseif first == '[' then -- Parse an array.
local arr, val, delim_found = {}, true, true
pos = pos + 1
while true do
val, pos = json.parse(str, pos, ']')
if val == nil then return arr, pos end
if not delim_found then error('Comma missing between array items.') end
arr[#arr + 1] = val
pos, delim_found = skip_delim(str, pos, ',')
end
elseif first == '"' then -- Parse a string.
return parse_str_val(str, pos + 1)
elseif first == '-' or first:match('%d') then -- Parse a number.
return parse_num_val(str, pos)
elseif first == end_delim then -- End of an object or array.
return nil, pos + 1
else -- Parse true, false, or null.
local literals = {['true'] = true, ['false'] = false, ['null'] = json.null}
for lit_str, lit_val in pairs(literals) do
local lit_end = pos + #lit_str - 1
if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end
end
local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
error('Invalid json syntax starting at ' .. pos_info_str)
end
end

function getGutenberg(id)
local url = "http://gutendex.com/books/"..id
local http
if not pcall(function() http = require("socket.http") end) then
SU.error("Install luasocket from luarocks")
end
local result, statuscode, content = http.request(url)
if statuscode ~= 200 then
SU.error("Could not load catalogue from "..url..": "..statuscode)
end
local parsed = json.parse(result)
if not parsed.formats then
SU.error("Couldn't parse the JSON")
end
local tryFormats = {"text/plain"; "text/plain; charset=utf-8"}
local gotformat
for i = 1,#tryFormats do local format = tryFormats[i]
if parsed.formats[format] then gotformat = format end
end
if not gotformat then
SU.error("Couldn't find a suitable format")
end
io.stderr:write("Downloading "..parsed.title.."... ")
url = parsed.formats[gotformat]
local result, statuscode, content = http.request(url)
if statuscode ~= 200 then
SU.error("Could not load file from "..url..": "..statuscode)
end
io.stderr:write("Done\n")
lines = {}
local go = false
for token in SU.gtoke(result,"\n") do
local s = token.string or token.separator
if s:match("***[ ]*END") then break end
if go then table.insert(lines, s) end
if s:match("***[ ]*START OF") then go = true end
end
return table.concat(lines,"")
end

SILE.registerCommand("gutenberg", function (options,content)
SU.required(options, "id")
local text = getGutenberg(options.id)
SILE.typesetter:typeset(text)
end)

0 comments on commit bd3ff7f

Please sign in to comment.