diff options
Diffstat (limited to 'tex/context/base/util-dim.lua')
-rw-r--r-- | tex/context/base/util-dim.lua | 898 |
1 files changed, 449 insertions, 449 deletions
diff --git a/tex/context/base/util-dim.lua b/tex/context/base/util-dim.lua index bbfeae7d4..47b2706b7 100644 --- a/tex/context/base/util-dim.lua +++ b/tex/context/base/util-dim.lua @@ -1,449 +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
+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 |