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 trace_italics = false trackers.register("typesetters.italics", function(v) trace_italics = v end) local report_italics = logs.reporter("nodes","italics") local threshold = 0.5 trackers.register("typesetters.threshold", function(v) threshold = v == true and 0.5 or tonumber(v) end) typesetters.italics = typesetters.italics or { } local italics = typesetters.italics 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 enableaction = nodes.tasks.enableaction local nuts = nodes.nuts local nodepool = nuts.pool local getprev = nuts.getprev local getnext = nuts.getnext local getid = nuts.getid local getchar = nuts.getchar local getdisc = nuts.getdisc local getattr = nuts.getattr local setattr = nuts.setattr local getattrlist = nuts.getattrlist local setattrlist = nuts.setattrlist local setdisc = nuts.setdisc local isglyph = nuts.isglyph local setkern = nuts.setkern local getkern = nuts.getkern local getheight = nuts.getheight local insertnodeafter = nuts.insertafter local remove_node = nuts.remove local endofmath = nuts.endofmath local texgetattribute = tex.getattribute local texsetattribute = tex.setattribute local a_italics = attributes.private("italics") local a_mathitalics = attributes.private("mathitalics") local unsetvalue = attributes.unsetvalue local new_correction_kern = nodepool.italickern local new_correction_glue = nodepool.glue local fonthashes = fonts.hashes local fontdata = fonthashes.identifiers local italicsdata = fonthashes.italics local exheights = fonthashes.exheights local chardata = fonthashes.characters local is_punctuation = characters.is_punctuation local implement = interfaces.implement local forcedvariant = false function typesetters.italics.forcevariant(variant) forcedvariant = variant end -- We use the same key as the tex font handler. So, if a valua has already be set, we -- use that one. local function setitalicinfont(font,char) local tfmdata = fontdata[font] local character = tfmdata.characters[char] if character then local italic = character.italic if not italic then local autoitalicamount = tfmdata.properties.autoitalicamount or 0 if autoitalicamount ~= 0 then local description = tfmdata.descriptions[char] if description then italic = description.italic if not italic then local boundingbox = description.boundingbox italic = boundingbox[3] - description.width + autoitalicamount if italic < 0 then -- < 0 indicates no overshoot or a very small auto italic italic = 0 end end if italic ~= 0 then italic = italic * tfmdata.parameters.hfactor end end end if trace_italics then report_italics("setting italic correction of %C of font %a to %p",char,font,italic) end if not italic then italic = 0 end character.italic = italic end return italic else return 0 end end -- todo: clear attribute local function okay(data,current,font,prevchar,previtalic,char,what) if data then if trace_italics then report_italics("ignoring %p between %s italic %C and italic %C",previtalic,what,prevchar,char) end return false end if threshold then -- if getid(current) == glyph_code then while current and getid(current) ~= glyph_code do current = getprev(current) end if current then local ht = getheight(current) local ex = exheights[font] local th = threshold * ex if ht <= th then if trace_italics then report_italics("ignoring correction between %s italic %C and regular %C, height %p less than threshold %p",prevchar,what,char,ht,th) end return false end else -- maybe backtrack to glyph end end if trace_italics then report_italics("inserting %p between %s italic %C and regular %C",previtalic,what,prevchar,char) end return true end -- maybe: with_attributes(current,n) : -- -- local function correction_kern(kern,n) -- return with_attributes(new_correction_kern(kern),n) -- end local function correction_kern(kern,n) local k = new_correction_kern(kern) if n then local a = getattrlist(n) if a then -- maybe not setattrlist(k,a) -- can be a marked content (border case) end end return k end local function correction_glue(glue,n) local g = new_correction_glue(glue) if n then local a = getattrlist(n) if a then -- maybe not setattrlist(g,a) -- can be a marked content (border case) end end return g end local mathokay = false local textokay = false local enablemath = false local enabletext = false local function domath(head,current) current = endofmath(current) local next = getnext(current) if next then local char, id = isglyph(next) if char then -- we can have an old font where italic correction has been applied -- or a new one where it hasn't been done local kern = getprev(current) if kern and getid(kern) == kern_code then local glyph = getprev(kern) if glyph and getid(glyph) == glyph_code then -- [math: ] : we remove the correction when we have -- punctuation if is_punctuation[char] then local a = getattr(glyph,a_mathitalics) if a and (a < 100 or a > 100) then if a > 100 then a = a - 100 else a = a + 100 end local i = getkern(kern) local c, f = isglyph(glyph) if getheight(next) < 1.25*exheights[f] then if i == 0 then if trace_italics then report_italics("%s italic %p between math %C and punctuation %C","ignoring",i,c,char) end else if trace_italics then report_italics("%s italic between math %C and punctuation %C","removing",i,c,char) end setkern(kern,0) -- or maybe a small value or half the ic end elseif i == 0 then local d = chardata[f][c] local i = d.italic if i == 0 then if trace_italics then report_italics("%s italic %p between math %C and punctuation %C","ignoring",i,c,char) end else setkern(kern,i) if trace_italics then report_italics("%s italic %p between math %C and punctuation %C","setting",i,c,char) end end elseif trace_italics then report_italics("%s italic %p between math %C and punctuation %C","keeping",k,c,char) end end end end else local glyph = kern if glyph and getid(glyph) == glyph_code then -- [math: ] : we add the correction when we have -- no punctuation if not is_punctuation[char] then local a = getattr(glyph,a_mathitalics) if a and (a < 100 or a > 100) then if a > 100 then a = a - 100 else a = a + 100 end if trace_italics then report_italics("%s italic %p between math %C and non punctuation %C","adding",a,getchar(glyph),char) end insertnodeafter(head,glyph,correction_kern(a,glyph)) end end end end end end return current end local function mathhandler(head) local current = head while current do if getid(current) == math_code then current = domath(head,current) end current = getnext(current) end return head end local function texthandler(head) local prev = nil local prevchar = nil local prevhead = head local previtalic = 0 local previnserted = nil local pre = nil local pretail = nil local post = nil local posttail = nil local postchar = nil local posthead = nil local postitalic = 0 local postinserted = nil local replace = nil local replacetail = nil local replacechar = nil local replacehead = nil local replaceitalic = 0 local replaceinserted = nil local current = prevhead local lastfont = nil local lastattr = nil while current do local char, id = isglyph(current) if char then local font = id local data = italicsdata[font] if font ~= lastfont then if previtalic ~= 0 then if okay(data,current,font,prevchar,previtalic,char,"glyph") then insertnodeafter(prevhead,prev,correction_kern(previtalic,current)) end elseif previnserted and data then if trace_italics then report_italics("deleting last correction before %s %C",char,"glyph") end remove_node(prevhead,previnserted,true) else -- if replaceitalic ~= 0 then if okay(data,replace,font,replacechar,replaceitalic,char,"replace") then insertnodeafter(replacehead,replace,correction_kern(replaceitalic,current)) end replaceitalic = 0 elseif replaceinserted and data then if trace_italics then report_italics("deleting last correction before %s %C","replace",char) end remove_node(replacehead,replaceinserted,true) end -- if postitalic ~= 0 then if okay(data,post,font,postchar,postitalic,char,"post") then insertnodeafter(posthead,post,correction_kern(postitalic,current)) end postitalic = 0 elseif postinserted and data then if trace_italics then report_italics("deleting last correction before %s %C","post",char) end remove_node(posthead,postinserted,true) end end -- lastfont = font end if data then local attr = forcedvariant or getattr(current,a_italics) if attr and attr > 0 then local cd = data[char] if not cd then -- this really can happen previtalic = 0 else previtalic = cd.italic if not previtalic then previtalic = setitalicinfont(font,char) -- calculated once -- previtalic = 0 end if previtalic ~= 0 then lastfont = font lastattr = attr prev = current -- prevhead = head prevchar = char end end else previtalic = 0 end else previtalic = 0 end previnserted = nil replaceinserted = nil postinserted = nil elseif id == disc_code then previnserted = nil previtalic = 0 replaceinserted = nil replaceitalic = 0 postinserted = nil postitalic = 0 updated = false replacefont = nil postfont = nil pre, post, replace, pretail, posttail, replacetail = getdisc(current,true) if replace then local current = replacetail while current do local char, id = isglyph(current) if char then local font = id if font ~= lastfont then local data = italicsdata[font] if data then local attr = forcedvariant or getattr(current,a_italics) if attr and attr > 0 then local cd = data[char] if not cd then -- this really can happen replaceitalic = 0 else replaceitalic = cd.italic if not replaceitalic then replaceitalic = setitalicinfont(font,char) -- calculated once -- replaceitalic = 0 end if replaceitalic ~= 0 then lastfont = font lastattr = attr replacechar = char replacehead = replace updated = true end end end end replacefont = font end break else current = getprev(current) end end end if post then local current = posttail while current do local char, id = isglyph(current) if char then local font = id if font ~= lastfont then local data = italicsdata[font] if data then local attr = forcedvariant or getattr(current,a_italics) if attr and attr > 0 then local cd = data[char] if not cd then -- this really can happen -- postitalic = 0 else postitalic = cd.italic if not postitalic then postitalic = setitalicinfont(font,char) -- calculated once -- postitalic = 0 end if postitalic ~= 0 then lastfont = font lastattr = attr postchar = char posthead = post updated = true end end end end postfont = font end break else current = getprev(current) end end end if replacefont or postfont then lastfont = replacefont or postfont end if updated then setdisc(current,pre,post,replace) end elseif id == kern_code then -- how about fontkern ? previnserted = nil previtalic = 0 replaceinserted = nil replaceitalic = 0 postinserted = nil postitalic = 0 elseif id == glue_code then if previtalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and glue",previtalic,"glyph",prevchar) end previnserted = correction_glue(previtalic,current) -- maybe just add ? else problem with penalties previtalic = 0 insertnodeafter(prevhead,prev,previnserted) else if replaceitalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and glue",replaceitalic,"replace",replacechar) end replaceinserted = correction_kern(replaceitalic,current) -- needs to be a kern replaceitalic = 0 insertnodeafter(replacehead,replace,replaceinserted) end if postitalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and glue",postitalic,"post",postchar) end postinserted = correction_kern(postitalic,current) -- needs to be a kern postitalic = 0 insertnodeafter(posthead,post,postinserted) end end elseif id == math_code then -- is this still needed ... the current engine implementation has been redone previnserted = nil previtalic = 0 replaceinserted = nil replaceitalic = 0 postinserted = nil postitalic = 0 if mathokay then current = domath(head,current) else current = endofmath(current) end else if previtalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and whatever",previtalic,"glyph",prevchar) end insertnodeafter(prevhead,prev,correction_kern(previtalic,current)) previnserted = nil previtalic = 0 replaceinserted = nil replaceitalic = 0 postinserted = nil postitalic = 0 else if replaceitalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and whatever",replaceitalic,"replace",replacechar) end insertnodeafter(replacehead,replace,correction_kern(replaceitalic,current)) previnserted = nil previtalic = 0 replaceinserted = nil replaceitalic = 0 postinserted = nil postitalic = 0 end if postitalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and whatever",postitalic,"post",postchar) end insertnodeafter(posthead,post,correction_kern(postitalic,current)) previnserted = nil previtalic = 0 replaceinserted = nil replaceitalic = 0 postinserted = nil postitalic = 0 end end end current = getnext(current) end if lastattr and lastattr > 1 then -- more control is needed here if previtalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and end of list",previtalic,"glyph",prevchar) end insertnodeafter(prevhead,prev,correction_kern(previtalic,current)) else if replaceitalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and end of list",replaceitalic,"replace",replacechar) end insertnodeafter(replacehead,replace,correction_kern(replaceitalic,current)) end if postitalic ~= 0 then if trace_italics then report_italics("inserting %p between %s italic %C and end of list",postitalic,"post",postchar) end insertnodeafter(posthead,post,correction_kern(postitalic,current)) end end end return head end function italics.handler(head) if textokay then return texthandler(head) elseif mathokay then return mathhandler(head) else return head, false end end enabletext = function() enableaction("processors","typesetters.italics.handler") if trace_italics then report_italics("enabling text/text italics") end enabletext = false textokay = true end enablemath = function() enableaction("processors","typesetters.italics.handler") if trace_italics then report_italics("enabling math/text italics") end enablemath = false mathokay = true end function italics.enabletext() if enabletext then enabletext() end end function italics.enablemath() if enablemath then enablemath() end end function italics.set(n) if enabletext then enabletext() end if n == variables.reset then texsetattribute(a_italics,unsetvalue) else texsetattribute(a_italics,tonumber(n) or unsetvalue) end end function italics.reset() texsetattribute(a_italics,unsetvalue) end implement { name = "setitaliccorrection", actions = italics.set, arguments = "string" } implement { name = "resetitaliccorrection", actions = italics.reset, } local variables = interfaces.variables local settings_to_hash = utilities.parsers.settings_to_hash local function setupitaliccorrection(option) -- no grouping ! if enabletext then enabletext() end local options = settings_to_hash(option) local variant = unsetvalue if options[variables.text] then variant = 1 elseif options[variables.always] then variant = 2 end -- maybe also keywords for threshold if options[variables.global] then forcedvariant = variant texsetattribute(a_italics,unsetvalue) else forcedvariant = false texsetattribute(a_italics,variant) end if trace_italics then report_italics("forcing %a, variant %a",forcedvariant or "-",variant ~= unsetvalue and variant) end end implement { name = "setupitaliccorrection", actions = setupitaliccorrection, arguments = "string" } -- for manuals: local stack = { } implement { name = "pushitaliccorrection", actions = function() table.insert(stack,{forcedvariant, texgetattribute(a_italics) }) end } implement { name = "popitaliccorrection", actions = function() local top = table.remove(stack) forcedvariant = top[1] texsetattribute(a_italics,top[2]) end }