diff options
| author | Philipp Gesang <phg42.2a@gmail.com> | 2013-04-29 15:00:05 -0700 | 
|---|---|---|
| committer | Philipp Gesang <phg42.2a@gmail.com> | 2013-04-29 15:00:05 -0700 | 
| commit | 5ff06a36a0e82f3350bc955fac3825d7a1969289 (patch) | |
| tree | d02c81d72c38393699bba1cc0ac6152f58aa44a6 /lualibs-util-dim.lua | |
| parent | 69adb7e51c6c082d0e16e45ff9f5ac75c4618056 (diff) | |
| parent | 170fbd1a07aaadace4b976f62e67572c53c8449b (diff) | |
| download | lualibs-5ff06a36a0e82f3350bc955fac3825d7a1969289.tar.gz | |
Merge pull request #1 from phi-gamma/master
import current status
Diffstat (limited to 'lualibs-util-dim.lua')
| -rw-r--r-- | lualibs-util-dim.lua | 449 | 
1 files changed, 449 insertions, 0 deletions
| diff --git a/lualibs-util-dim.lua b/lualibs-util-dim.lua new file mode 100644 index 0000000..47b2706 --- /dev/null +++ b/lualibs-util-dim.lua @@ -0,0 +1,449 @@ +if not modules then modules = { } end modules ['util-dim'] = { +    version   = 1.001, +    comment   = "support for dimensions", +    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "see context related readme files" +} + +--[[ldx-- +<p>Internally <l n='luatex'/> work with scaled point, which are +represented by integers. However, in practice, at east at the +<l n='tex'/> end we work with more generic units like points (pt). Going +from scaled points (numbers) to one of those units can be +done by using the conversion factors collected in the following +table.</p> +--ldx]]-- + +local format, match, gsub, type, setmetatable = string.format, string.match, string.gsub, type, setmetatable +local P, S, R, Cc, C, lpegmatch = lpeg.P, lpeg.S, lpeg.R, lpeg.Cc, lpeg.C, lpeg.match + +local allocate          = utilities.storage.allocate +local setmetatableindex = table.setmetatableindex +local formatters        = string.formatters + +--this might become another namespace + +number = number or { } +local number = number + +number.tonumberf = function(n) return match(format("%.20f",n),"(.-0?)0*$") end -- one zero too much but alas +number.tonumberg = function(n) return       format("%.20g",n)              end + +local dimenfactors = allocate { +    ["pt"] =             1/65536, +    ["in"] = (  100/ 7227)/65536, +    ["cm"] = (  254/ 7227)/65536, +    ["mm"] = ( 2540/ 7227)/65536, +    ["sp"] =                   1, -- 65536 sp in 1pt +    ["bp"] = ( 7200/ 7227)/65536, +    ["pc"] = (    1/   12)/65536, +    ["dd"] = ( 1157/ 1238)/65536, +    ["cc"] = ( 1157/14856)/65536, +    ["nd"] = (20320/21681)/65536, +    ["nc"] = ( 5080/65043)/65536 +} + +--~ print(table.serialize(dimenfactors)) +--~ +--~  %.99g: +--~ +--~  t={ +--~   ["bp"]=1.5201782378580324e-005, +--~   ["cc"]=1.1883696112892098e-006, +--~   ["cm"]=5.3628510057769479e-007, +--~   ["dd"]=1.4260435335470516e-005, +--~   ["em"]=0.000152587890625, +--~   ["ex"]=6.103515625e-005, +--~   ["in"]=2.1113586636917117e-007, +--~   ["mm"]=5.3628510057769473e-008, +--~   ["nc"]=1.1917446679504327e-006, +--~   ["nd"]=1.4300936015405194e-005, +--~   ["pc"]=1.2715657552083333e-006, +--~   ["pt"]=1.52587890625e-005, +--~   ["sp"]=1, +--~  } +--~ +--~  patched %s and tonumber +--~ +--~  t={ +--~   ["bp"]=0.00001520178238, +--~   ["cc"]=0.00000118836961, +--~   ["cm"]=0.0000005362851, +--~   ["dd"]=0.00001426043534, +--~   ["em"]=0.00015258789063, +--~   ["ex"]=0.00006103515625, +--~   ["in"]=0.00000021113587, +--~   ["mm"]=0.00000005362851, +--~   ["nc"]=0.00000119174467, +--~   ["nd"]=0.00001430093602, +--~   ["pc"]=0.00000127156576, +--~   ["pt"]=0.00001525878906, +--~   ["sp"]=1, +--~  } + +--[[ldx-- +<p>A conversion function that takes a number, unit (string) and optional +format (string) is implemented using this table.</p> +--ldx]]-- + + +local function numbertodimen(n,unit,fmt) +    if type(n) == 'string' then +        return n +    else +        unit = unit or 'pt' +        if not fmt then +            fmt = "%s%s" +        elseif fmt == true then +            fmt = "%0.5f%s" +        end +        return format(fmt,n*dimenfactors[unit],unit) +     -- if fmt then +     --     return format(fmt,n*dimenfactors[unit],unit) +     -- else +     --     return match(format("%.20f",n*dimenfactors[unit]),"(.-0?)0*$") .. unit +     -- end +    end +end + +--[[ldx-- +<p>We collect a bunch of converters in the <type>number</type> namespace.</p> +--ldx]]-- + +number.maxdimen     = 1073741823 +number.todimen      = numbertodimen +number.dimenfactors = dimenfactors + +function number.topoints      (n,fmt) return numbertodimen(n,"pt",fmt) end +function number.toinches      (n,fmt) return numbertodimen(n,"in",fmt) end +function number.tocentimeters (n,fmt) return numbertodimen(n,"cm",fmt) end +function number.tomillimeters (n,fmt) return numbertodimen(n,"mm",fmt) end +function number.toscaledpoints(n,fmt) return numbertodimen(n,"sp",fmt) end +function number.toscaledpoints(n)     return            n .. "sp"      end +function number.tobasepoints  (n,fmt) return numbertodimen(n,"bp",fmt) end +function number.topicas       (n,fmt) return numbertodimen(n "pc",fmt) end +function number.todidots      (n,fmt) return numbertodimen(n,"dd",fmt) end +function number.tociceros     (n,fmt) return numbertodimen(n,"cc",fmt) end +function number.tonewdidots   (n,fmt) return numbertodimen(n,"nd",fmt) end +function number.tonewciceros  (n,fmt) return numbertodimen(n,"nc",fmt) end + +--[[ldx-- +<p>More interesting it to implement a (sort of) dimen datatype, one +that permits calculations too. First we define a function that +converts a string to scaledpoints. We use <l n='lpeg'/>. We capture +a number and optionally a unit. When no unit is given a constant +capture takes place.</p> +--ldx]]-- + +local amount = (S("+-")^0 * R("09")^0 * P(".")^0 * R("09")^0) + Cc("0") +local unit   = R("az")^1 + +local dimenpair = amount/tonumber * (unit^1/dimenfactors + Cc(1)) -- tonumber is new + +lpeg.patterns.dimenpair = dimenpair + +local splitter = amount/tonumber * C(unit^1) + +function number.splitdimen(str) +    return lpegmatch(splitter,str) +end + +--[[ldx-- +<p>We use a metatable to intercept errors. When no key is found in +the table with factors, the metatable will be consulted for an +alternative index function.</p> +--ldx]]-- + +setmetatableindex(dimenfactors, function(t,s) + -- error("wrong dimension: " .. (s or "?")) -- better a message +    return false +end) + +--[[ldx-- +<p>We redefine the following function later on, so we comment it +here (which saves us bytecodes.</p> +--ldx]]-- + +-- function string.todimen(str) +--     if type(str) == "number" then +--         return str +--     else +--         local value, unit = lpegmatch(dimenpair,str) +--         return value/unit +--     end +-- end +-- +-- local stringtodimen = string.todimen + +local stringtodimen -- assigned later (commenting saves bytecode) + +local amount = S("+-")^0 * R("09")^0 * S(".,")^0 * R("09")^0 +local unit   = P("pt") + P("cm") + P("mm") + P("sp") + P("bp") + P("in")  + +               P("pc") + P("dd") + P("cc") + P("nd") + P("nc") + +local validdimen = amount * unit + +lpeg.patterns.validdimen = validdimen + +--[[ldx-- +<p>This converter accepts calls like:</p> + +<typing> +string.todimen("10") +string.todimen(".10") +string.todimen("10.0") +string.todimen("10.0pt") +string.todimen("10pt") +string.todimen("10.0pt") +</typing> + +<p>With this in place, we can now implement a proper datatype for dimensions, one +that permits us to do this:</p> + +<typing> +s = dimen "10pt" + dimen "20pt" + dimen "200pt" +        - dimen "100sp" / 10 + "20pt" + "0pt" +</typing> + +<p>We create a local metatable for this new type:</p> +--ldx]]-- + +local dimensions = { } + +--[[ldx-- +<p>The main (and globally) visible representation of a dimen is defined next: it is +a one-element table. The unit that is returned from the match is normally a number +(one of the previously defined factors) but we also accept functions. Later we will +see why. This function is redefined later.</p> +--ldx]]-- + +-- function dimen(a) +--     if a then +--         local ta= type(a) +--         if ta == "string" then +--             local value, unit = lpegmatch(pattern,a) +--             if type(unit) == "function" then +--                 k = value/unit() +--             else +--                 k = value/unit +--             end +--             a = k +--         elseif ta == "table" then +--             a = a[1] +--         end +--         return setmetatable({ a }, dimensions) +--     else +--         return setmetatable({ 0 }, dimensions) +--     end +-- end + +--[[ldx-- +<p>This function return a small hash with a metatable attached. It is +through this metatable that we can do the calculations. We could have +shared some of the code but for reasons of speed we don't.</p> +--ldx]]-- + +function dimensions.__add(a, b) +    local ta, tb = type(a), type(b) +    if ta == "string" then a = stringtodimen(a) elseif ta == "table" then a = a[1] end +    if tb == "string" then b = stringtodimen(b) elseif tb == "table" then b = b[1] end +    return setmetatable({ a + b }, dimensions) +end + +function dimensions.__sub(a, b) +    local ta, tb = type(a), type(b) +    if ta == "string" then a = stringtodimen(a) elseif ta == "table" then a = a[1] end +    if tb == "string" then b = stringtodimen(b) elseif tb == "table" then b = b[1] end +    return setmetatable({ a - b }, dimensions) +end + +function dimensions.__mul(a, b) +    local ta, tb = type(a), type(b) +    if ta == "string" then a = stringtodimen(a) elseif ta == "table" then a = a[1] end +    if tb == "string" then b = stringtodimen(b) elseif tb == "table" then b = b[1] end +    return setmetatable({ a * b }, dimensions) +end + +function dimensions.__div(a, b) +    local ta, tb = type(a), type(b) +    if ta == "string" then a = stringtodimen(a) elseif ta == "table" then a = a[1] end +    if tb == "string" then b = stringtodimen(b) elseif tb == "table" then b = b[1] end +    return setmetatable({ a / b }, dimensions) +end + +function dimensions.__unm(a) +    local ta = type(a) +    if ta == "string" then a = stringtodimen(a) elseif ta == "table" then a = a[1] end +    return setmetatable({ - a }, dimensions) +end + +--[[ldx-- +<p>It makes no sense to implement the power and modulo function but +the next two do make sense because they permits is code like:</p> + +<typing> +local a, b = dimen "10pt", dimen "11pt" +... +if a > b then +    ... +end +</typing> +--ldx]]-- + +-- makes no sense: dimensions.__pow and dimensions.__mod + +function dimensions.__lt(a, b) +    return a[1] < b[1] +end + +function dimensions.__eq(a, b) +    return a[1] == b[1] +end + +--[[ldx-- +<p>We also need to provide a function for conversion to string (so that +we can print dimensions). We print them as points, just like <l n='tex'/>.</p> +--ldx]]-- + +function dimensions.__tostring(a) +    return a[1]/65536 .. "pt" -- instead of todimen(a[1]) +end + +--[[ldx-- +<p>Since it does not take much code, we also provide a way to access +a few accessors</p> + +<typing> +print(dimen().pt) +print(dimen().sp) +</typing> +--ldx]]-- + +function dimensions.__index(tab,key) +    local d = dimenfactors[key] +    if not d then +        error("illegal property of dimen: " .. key) +        d = 1 +    end +    return 1/d +end + +--[[ldx-- +<p>In the converter from string to dimension we support functions as +factors. This is because in <l n='tex'/> we have a few more units: +<type>ex</type> and <type>em</type>. These are not constant factors but +depend on the current font. They are not defined by default, but need +an explicit function call. This is because at the moment that this code +is loaded, the relevant tables that hold the functions needed may not +yet be available.</p> +--ldx]]-- + +   dimenfactors["ex"] =  4 * 1/65536 --   4pt +   dimenfactors["em"] = 10 * 1/65536 --  10pt +-- dimenfactors["%"]  =  4 * 1/65536 -- 400pt/100 + +--[[ldx-- +<p>The previous code is rather efficient (also thanks to <l n='lpeg'/>) but we +can speed it up by caching converted dimensions. On my machine (2008) the following +loop takes about 25.5 seconds.</p> + +<typing> +for i=1,1000000 do +    local s = dimen "10pt" + dimen "20pt" + dimen "200pt" +        - dimen "100sp" / 10 + "20pt" + "0pt" +end +</typing> + +<p>When we cache converted strings this becomes 16.3 seconds. In order not +to waste too much memory on it, we tag the values of the cache as being +week which mean that the garbage collector will collect them in a next +sweep. This means that in most cases the speed up is mostly affecting the +current couple of calculations and as such the speed penalty is small.</p> + +<p>We redefine two previous defined functions that can benefit from +this:</p> +--ldx]]-- + +local known = { } setmetatable(known, { __mode = "v" }) + +function dimen(a) +    if a then +        local ta= type(a) +        if ta == "string" then +            local k = known[a] +            if k then +                a = k +            else +                local value, unit = lpegmatch(dimenpair,a) +                if type(unit) == "function" then +                    k = value/unit() +                else +                    k = value/unit +                end +                known[a] = k +                a = k +            end +        elseif ta == "table" then +            a = a[1] +        end +        return setmetatable({ a }, dimensions) +    else +        return setmetatable({ 0 }, dimensions) +    end +end + +function string.todimen(str) -- maybe use tex.sp when available +    if type(str) == "number" then +        return str +    else +        local k = known[str] +        if not k then +            local value, unit = lpegmatch(dimenpair,str) +            if value and unit then +                k = value/unit -- to be considered: round +            else +                k = 0 +            end +            -- print(str,value,unit) +            known[str] = k +        end +        return k +    end +end + +--~ local known = { } + +--~ function string.todimen(str) -- maybe use tex.sp +--~     local k = known[str] +--~     if not k then +--~         k = tex.sp(str) +--~         known[str] = k +--~     end +--~     return k +--~ end + +stringtodimen = string.todimen -- local variable defined earlier + +function number.toscaled(d) +    return format("%0.5f",d/2^16) +end + +--[[ldx-- +<p>In a similar fashion we can define a glue datatype. In that case we +probably use a hash instead of a one-element table.</p> +--ldx]]-- + +--[[ldx-- +<p>Goodie:s</p> +--ldx]]-- + +function number.percent(n,d) -- will be cleaned up once luatex 0.30 is out +    d = d or tex.hsize +    if type(d) == "string" then +        d = stringtodimen(d) +    end +    return (n/100) * d +end + +number["%"] = number.percent | 
