if not modules then modules = { } end modules ['typo-itc'] = { version = 1.001, comment = "companion to typo-itc.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local tonumber = tonumber local tand = math.tand local trace_italics = false trackers.register("typesetters.italics", function(v) trace_italics = v end) local report_italics = logs.reporter("nodes","italics") typesetters.italics = typesetters.italics or { } local italics = typesetters.italics typesetters.corrections = typesetters.corrections or { } local corrections = typesetters.corrections local variables = interfaces.variables local settings_to_hash = utilities.parsers.settings_to_hash local nodecodes = nodes.nodecodes local glyph_code = nodecodes.glyph local kern_code = nodecodes.kern local glue_code = nodecodes.glue local disc_code = nodecodes.disc local math_code = nodecodes.math local end_math_code = nodes.mathcodes.endmath local font_kern_code = nodes.kerncodes.fontkern local italic_kern_code = nodes.kerncodes.italiccorrection local left_kern_code = nodes.kerncodes.leftcorrectionkern local right_kern_code = nodes.kerncodes.rightcorrectionkern local spaceskip_code = nodes.gluecodes.spaceskip local xspaceskip_code = nodes.gluecodes.xspaceskip local enableaction = nodes.tasks.enableaction local nuts = nodes.nuts local tonut = nodes.tonut local nodepool = nuts.pool local getprev = nuts.getprev local getnext = nuts.getnext local getid = nuts.getid local getsubtype = nuts.getsubtype local getchar = nuts.getchar local getdisc = nuts.getdisc local setattrlist = nuts.setattrlist local setdisc = nuts.setdisc local isglyph = nuts.isglyph local isnextglyph = nuts.isnextglyph local issimilarglyph = nuts.issimilarglyph local isitalicglyph = nuts.isitalicglyph local firstitalicglyph = nuts.firstitalicglyph local hasglyphoption = nuts.hasglyphoption local hasdiscoption = nuts.hasdiscoption local setkern = nuts.setkern local getkern = nuts.getkern local setglue = nuts.setglue local getglue = nuts.getglue local getheight = nuts.getheight local getoptions = nuts.getoptions local getslant = nuts.getslant local getcornerkerns = nuts.getcornerkerns local xscaled = nuts.xscaled local yscaled = nuts.yscaled local insertnodeafter = nuts.insertafter local insertnodebefore = nuts.insertbefore local endofmath = nuts.endofmath local findnode = nuts.findnode local traverseitalic = nuts.traverseitalic local new_correction_kern = nodepool.italickern ----- new_correction_glue = nodepool.glue local fonthashes = fonts.hashes local fontdata = fonthashes.identifiers ----- italicsdata = fonthashes.italics local exheights = fonthashes.exheights local chardata = fonthashes.characters local ispunctuation = characters.is_punctuation local implement = interfaces.implement local no_correction_code = tex.glyphoptioncodes.noitaliccorrection local math_check_italic_code = tex.userglyphoptioncodes.mathcheckitalic local text_check_italic_code = tex.userglyphoptioncodes.textcheckitalic local disc_check_italic_code = tex.userdiscoptioncodes .textcheckitalic local mathokay = false local textokay = false local kernokay = false -- We don't drive this by attributes because most likely it is a document -- property. local exfactormath = 1.25 -- these need checking because not we go unscaled local exfactortext = 0.50 -- these need checking because not we go unscaled local spacefactor = 0 directives.register("typesetters.italics.threshold", function(v) exfactortext = v == true and 0.50 or tonumber(v) end) directives.register("typesetters.italics.threshold.math", function(v) exfactormath = v == true and 1.25 or tonumber(v) end) -- function typesetters.italics.forcevariant(variant) -- end -- We use the same key as the tex font handler. So, if a value has already be set, we -- use that one. Contrary to \MKIV\ we don't cache the kern value because we share the -- character data in compact mode as well as in slant mode. We do however cache the -- intermediate (partial) italic value. Instead of attributes we now set bits in the -- glyph and discretionary options which is more efficient and we had these anyway. local function getmultiplier(tfmdata,current) local multiplier local slant = getslant(current) if slant and slant ~= 0 then multiplier = slant/1000 else local properties = tfmdata.properties local italicangle = properties.useditalicangle or 0 if italicangle == 0 then slant = properties.usedslant or 0 if slant and slant ~= 0 then multiplier = slant/1000 else -- We take the median of all valid italic/slanted on my disk. italicangle = -12 multiplier = tand(12) end else multiplier = tand(-italicangle) end end return multiplier end local function getleftitalic(font,char,current) local tfmdata = fontdata[font] local character = tfmdata.characters[char] if character then local italic = character.italic if not italic then local multiplier = getmultiplier(tfmdata,current) if multiplier ~= 0 then local lsb = character._lsb_ if lsb == false then return 0, 0 end local description = tfmdata.descriptions[char] if not lsb then if description then local boundingbox = description.boundingbox if boundingbox then lsb = boundingbox[1] character._lsb_ = lsb else character._lsb_ = false return 0, 0 end else character._lsb_ = false return 0, 0 end end if lsb ~= 0 then local factor = tfmdata.parameters.hfactor local italic = xscaled(current,lsb * factor) local extra = xscaled(current,description.height * multiplier * spacefactor * factor) if trace_italics then report_italics("setting left italic correction of %C of font %a to %p",char,font,italic) end return -italic, extra end end end end return 0, 0 end -- bb3 > width : shape sticks out : we we need to add -- bb3 < width : we could have a negative correction local function getrightitalic(font,char,current) local tfmdata = fontdata[font] local character = tfmdata.characters[char] if character then local italic = character.italic if italic then return italic, 0 else local multiplier = getmultiplier(tfmdata,current) if multiplier ~= 0 then local rsb = character._rsb_ if rsb == false then return 0, 0 end local description = tfmdata.descriptions[char] if not rsb then if description then local boundingbox = description.boundingbox if boundingbox then rsb = boundingbox[3] - description.width character._rsb_ = rsb else character._rsb_ = false return 0, 0 end else character._rsb_ = false return 0, 0 end end if rsb > 0 then local factor = tfmdata.parameters.hfactor local italic = xscaled(current,rsb * factor) local extra = xscaled(current,description.height * multiplier * spacefactor * factor) if trace_italics then report_italics("setting right italic correction of %C of font %a to %p",char,font,italic) end return italic, extra else -- < 0 indicates no overshoot or a very small auto italic end end return 0, 0 end else return 0, 0 end end -- The left and right correction kerns will be done in a second pass because we have -- to deal with the left side anyway due to lack of a next glyph at injection time. -- callback.register("italic_correction", function(node,kern,subtype) -- if node and subtype == italic_kern_code then -- node = tonut(node) -- local char, id = isglyph(node) -- if char then -- kern = getrightitalic(id,char,node) -- elseif id == disc_code then -- -- for now we forget about it -- end -- end -- return kern -- end) callback.register("italic_correction", function(node,kern,subtype) if not kernokay then enableaction("processors","typesetters.corrections.handler") kernokay = true if trace_italics then report_italics("enabling kern italics") end end return kern end) local function report_ignored(previous,current,why) report_italics("%s correction between %C and %C, %s","ignoring",getchar(previous),getchar(current),why) end local function report_obeyed(previous,current,why) report_italics("%s correction between %C and %C, %s","inserting",getchar(previous),getchar(current),why) end local function report_math(kern,mathchar,char,why) report_italics("%s italic %p between math %C and punctuation %C, %s",kern,mathchar,char,why) end local function injection_okay(previous,current) if hasglyphoption(current,no_correction_code) then if trace_italics then report_ignored(previous,current,"disabled") end return false else if exfactortext and exfactortext > 0 then -- hm, check previous .. probably not ok yet while current and getid(current) ~= glyph_code do current = getprev(current) end if current then local char, id = isglyph(current) if char then local height = getheight(current) -- chardata[id][char].height or 0 local exheight = yscaled(current, exheights[id]) if height <= exfactortext*exheight then if trace_italics then report_ignored(previous,current,"threshold") end return false end end end end if trace_italics then report_obeyed(previous,current,"enabled") end return true end end local function correction_kern(kern,n) local k = new_correction_kern(kern) if n then setattrlist(k,n) end return k end -- The code below now assumes the non-italic math model to be used so instead of -- nilling a kern (italic correction) we now compensate for the right corner kern. -- -- [math: ] is now just [math: ] ----- function domath(head,last) local function domath(head,current) if exfactormath and exfactormath > 0 then -- local current = endofmath(last) local next = getnext(current) if not hasglyphoption(next,no_correction_code) then -- check node and id too local char, id = isglyph(next) if char and ispunctuation[char] then local glyph = getprev(current) if hasglyphoption(glyph,math_check_italic_code) then -- check node and id too local ll, lr = getcornerkerns(glyph) if lr ~= 0 then -- unscaled local height = chardata[id][char].height or 0 local exheight = yscaled(current, exheights[id]) if height < exfactormath*exheight then insertnodeafter(head,current,correction_kern(lr,glyph)) if trace_italics then report_math(lr,c,char,"injecting") end end end end end end end return current end local function mathhandler(head) local current = head while current do current = findnode(current,math_code,begin_math_code) if current then current = domath(head,current) current = getnext(current) end end return head end local function injectafter(head,glyph,font,char,where) local italic, extra = getrightitalic(font,char,glyph) if italic ~= 0 then -- we could have a .01 pt threshold but correction doesn't happen often local kern = correction_kern(italic,glyph) insertnodeafter(head,glyph,kern) if extra > 0 then -- todo helper: isspace local next = getnext(kern) if next and getid(next) == glue_code then local subtype = getsubtype(next) if subtype == spaceskip_code or subtype == xspaceskip_code then local width, stretch, shrink = getglue(next) setglue(next,width+extra,stretch,shrink) end end end if trace_italics then report_italics("inserting %p between %s italic %C ",italic,where,char) end end end local function fixleftitalic(current) local prev = getnext(current) local char, id = isglyph(prev) if char then local italic, extra = getleftitalic(id,char,prev) setkern(current,italic) end return current end local function fixrightitalic(current) local prev = getprev(current) local char, id = isglyph(prev) if char then local italic, extra = getrightitalic(id,char,prev) setkern(current,italic) end return current end local function texthandler(head) -- yes : slantedglyph end -- nop : slantedglyph kern -- nop : slantedglyph similarglyph -- nop : slantedglyph samefont -- nop : slantedglyph glue similarglyph -- nop : slantedglyph glue samefont -- yes : slantedglyph glue -- local current = head -- local current = firstitalicglyph(head,true) -- no discretionaries (yet) as we assume a leading glyph local current = firstitalicglyph(head) if not current then return head end local currentdisc = nil local prevglyph = nil local prevchar = nil local prevfont = nil local prehead = nil local pretail = nil local postglyph = nil local posthead = nil local posttail = nil local postfont = nil local postchar = nil local replaceglyph = nil local replacehead = nil local replacetail = nil local replacefont = nil local replacechar = nil local function flush() if prevglyph then injectafter(head,prevglyph,prevfont,prevchar,"glyph") prevglyph = nil replaceglyph = nil postglyph = nil else local updatedisc = false if replaceglyph then injectafter(replacehead,replaceglyph,replacefont,replacechar,"replace") replaceglyph = nil updatedisc = true end if postglyph then injectafter(posthead,postglyph,postfont,postchar,"post") postglyph = nil updatedisc = true end if updatedisc then setdisc(currentdisc,prehead,posthead,replacehead) end end end if false then -- if true then local p = getprev(current) if not p or getid(p) ~= glue_code then local char, id = isglyph(current) -- isitalicglyph(current) if char then local italic = getleftitalic(id,char,current) if italic ~= 0 and current == head then head = insertnodebefore(head,current,correction_kern(italic,current)) end end end end while current do local nxt, char, id = isnextglyph(current) if char then if not hasglyphoption(current,text_check_italic_code) then prevglyph = nil replaceglyph = nil postglyph = nil elseif not prevglyph then -- nothing to do local data = isitalicglyph(current) if data then prevglyph = current prevchar = char prevfont = id replaceglyph = nil postglyph = nil end elseif issimilarglyph(current,prevglyph) then -- nothing to do prevglyph = current prevchar = char prevfont = id replaceglyph = nil postglyph = nil else local fine = injection_okay(prevglyph,current) if fine then flush() else prevglyph = nil replaceglyph = nil postglyph = nil end -- local data = fine and (italicsdata[id] or getslant(current)) local data = isitalicglyph(current) if data then prevglyph = current prevchar = char prevfont = id end end elseif id == disc_code then prevglyph = nil replaceglyph = nil postglyph = nil if hasdiscoption(current,disc_check_italic_code) then -- if not hasglyphoption(current,no_correction_code) then -- checked in the glyph itself prehead, posthead, replacehead, pretail, posttail, replacetail = getdisc(current,true) if replacetail then local char, id = isglyph(replacetail) if char then local data = isitalicglyph(replacetail) if data then replaceglyph = replacetail replacechar = char replacefont = id end end end if posttail then local char, id = isglyph(posttail) if char then local data = isitalicglyph(replacetail) if data then postglyph = posttail postchar = char postfont = id end end end currentdisc = current -- end end elseif id == kern_code then local subtype = getsubtype(current) -- if subtype == italic_kern_code or subtype == right_kern_code then -- fixrightitalic(current) -- prevglyph = nil -- replaceglyph = nil -- postglyph = nil -- elseif subtype == left_kern_code then -- fixleftitalic(current) -- prevglyph = nil -- replaceglyph = nil -- postglyph = nil -- end if subtype == italic_kern_code or subtype == right_kern_code or subtype == left_kern_code then prevglyph = nil replaceglyph = nil postglyph = nil end elseif id == glue_code then -- selective correction elseif id == math_code then flush() current = endofmath(current) if mathokay then current = domath(head,current) -- else -- current = endofmath(current) end prevglyph = nil replaceglyph = nil postglyph = nil else flush() end current = nxt end -- if lastattr and lastattr > 1 then -- more control is needed here flush() return head end function corrections.handler(head) if kernokay then for current, subtype in traverseitalic(head) do if subtype == italic_kern_code or subtype == right_kern_code then current = fixrightitalic(current) elseif subtype == left_kern_code then current = fixleftitalic(current) end end end return head end function italics.handler(head) if textokay then return texthandler(head) else -- fast loop for math if mathokay then return mathhandler(head) end return head end end local enabletext = function() if enable then enable() end if trace_italics then report_italics("enabling text/text italics") end enabletext = false textokay = true end local enablemath = function() if enable then enable() end if trace_italics then report_italics("enabling math/text italics") end enablemath = false mathokay = true end function italics.enabletext() -- math and text if enabletext then enabletext() end end function italics.enablemath() -- only math if enablemath then enablemath() end end -- maybe a table and also thresholds local function setitaliccorrection(factor) factor = tonumber(factor) or 0 if enabletext then enabletext() end spacefactor = factor end implement { name = "setitaliccorrection", actions = setitaliccorrection, arguments = "argument", }