> > So please, don't be shy, show me an example of this format :-) > > SteveT >
It's really hard to find some examples! Seems thy need a better marketing ;) But there is a German blog with some code snips http://www.luatex.de/ And find attached a luatex file from http://code.google.com/p/mingyue Peter
% De http://luatex.bluwiki.com/go/Use_a_TrueType_font % Environnement : max_print_line=2048 % Changes TODO: feature (and script, lang) indexes shouldn't depend on the font. % Addition TODO: all the other lookups, and script-specific shaping ... \catcode`\~=11 \def\lua{\directlua0} \lua{ dofile('l-table.lua') function message(...) n = select('#', ...) for i = 1, n do texio.write('term and log', tostring(select(i, ...))) if i == n then texio.write_nl('term and log', '') else texio.write('term and log', ' ') end end if n == 0 then texio.write('term and log', '\string\n') end end function message_ln(...) n = select('#', ...) for i = 1, n do texio.write('term and log', tostring(select(i, ...))) if i ~= n then texio.write('term and log', ' ') end end end verbose = 52134 function beverbose(n) return node.has_attribute(n, verbose) end function beverboser(n) return node.has_attribute(n, verbose) and node.has_attribute(n, verbose) >=2 end function beverbosest(n) return node.has_attribute(n, verbose) and node.has_attribute(n, verbose) >=3 end function table.nentries(t) local n = 0 for _, _ in pairs(t) do n = n + 1 end return n end } \attributedef\verbose=52134 \def\beverbose{\verbose=1\relax}% \relax needed because of the digit! \def\beverboser{\verbose=2\relax} \def\beverbosest{\verbose=3\relax} \def\bequiet{\verbose=-1\relax} \def\dumpnatfont{\lua{ message(table.serialize( fontforge.to_table(fontforge.open(font.fonts[font.current()].filename)) )) }} \def\dumptexfont{\lua{message(table.serialize(font.fonts[font.current()]))}} \directlua0{ function loadtruetype(name, size) fonttype = nil filename = kpse.find_file(name,"opentype fonts") if (filename) then fonttype = 'opentype' else filename = kpse.find_file(name, "truetype fonts") end if filename and not fonttype then fonttype = 'truetype' end if fonttype then if (size < 0) then size = (- 655.36) * size end ttffont = fontforge.to_table(fontforge.open(filename)) if ttffont then % HACK HACK HACK local tweak_size % if name == 'AdobeArabic-Regular' then tweak_size = true end if ttffont.units_per_em == 2048 and fonttype == 'opentype' then tweak_size = true end if tweak_size then size = size / 2.048 end f = { } f.name = ttffont.fontname f.fullname = ttffont.names[1].names.fullname f.parameters = { } f.designsize = size f.size = size direction = 0 f.parameters.slant = 0 f.parameters.space = size * 0.25 f.parameters.space_stretch = 0.3 * size f.parameters.space_shrink = 0.1 * size f.parameters.x_height = 0.4 * size f.parameters.quad = 1.0 * size f.parameters.extra_space = 0 f.characters = { } local mag = size / ttffont.units_per_em local scriptindex = { } local langindex = { } local featindex = { } f.map = ttffont.map f.features = { } for _, otable in ipairs({ "gsub", "gpos" }) do if ttffont[otable] then for _, feat in ipairs(ttffont[otable]) do if feat.features then for _, ft in ipairs(feat.features) do if ft.tag then local tag = ft.tag if not featindex[tag] then featindex[tag] = table.nentries(featindex) + 1 end f.features[tag] = f.features[tag] or { } local f_tag = f.features[tag] f_tag.table = otable local scripts = ft.scripts for _, screcord in ipairs(ft.scripts) do local script = screcord.script if not scriptindex[script] then scriptindex[script] = table.nentries(scriptindex) + 1 end f_tag[script] = f_tag[script] or { } for _, lang in ipairs(screcord.langs) do if not langindex[lang] then langindex[lang] = table.nentries(langindex) + 1 end f_tag[script][lang] = f_tag[script][lang] or { } f_tag[script][lang].subtables = f_tag[script][lang].subtables or { } local f_st = f_tag[script][lang].subtables for _, st in ipairs(feat.subtables) do table.insert(f_st, st.name) end f_tag[script][lang].flags = feat.flags f_tag[script][lang].name = feat.name f_tag[script][lang].type = feat.type end end end end end end end end local scriptlist = { } local langlist = { } local featlist = { } for tag, index in pairs(scriptindex) do scriptlist[index] = tag end for tag, index in pairs(langindex) do langlist[index] = tag end for tag, index in pairs(featindex) do featlist[index] = tag end f.scriptindex = scriptindex f.langindex = langindex f.featindex = featindex f.scriptlist = scriptlist f.langlist = langlist f.featlist = featlist f.lookups = ttffont.lookups names_of_char = { } for char, glyph in pairs(ttffont.map.map) do names_of_char[ttffont.glyphs[glyph].name] = ttffont.map.backmap[glyph] end names_of_glyph = { } for char, glyph in pairs(ttffont.map.map) do names_of_glyph[ttffont.glyphs[glyph].name] = glyph end f.map.names_of_char = names_of_char for char, glyph in pairs(ttffont.map.map) do glyph_table = ttffont.glyphs[glyph] local w = glyph_table.width * mag if tweak_size then w = w * 2.048 end f.characters[char] = { index = glyph, width = w, name = glyph_table.name, lookups = glyph_table.lookups } if glyph_table.boundingbox[4] then f.characters[char].height = glyph_table.boundingbox[4] * mag end if glyph_table.boundingbox[2] then f.characters[char].depth = -glyph_table.boundingbox[2] * mag end if glyph_table.kerns then local kerns = { } for _, kern in pairs(glyph_table.kerns) do kerns[names_of_char[kern.char]] = kern.off * mag end f.characters[char].kerns = kerns end end f.filename = filename f.type = 'real' f.format = fonttype f.embedding = "subset" f.cidinfo = { registry = "Adobe", ordering = "Identity", supplement = 0, version = 1 } end else f = font.read_tfm(name, size) end return f end } \def\loadtruetype{\lua{callback.register('define_font', loadtruetype)}} \loadtruetype \lua{ local cfont = fontforge.to_table(fontforge.open("CronosPro-Regular.otf")) function display_otable(ofont, tname) local otable = ofont[tname:lower()] message(tname, "features") for _, feat in ipairs(otable) do if not feat.features then feat.features = { } end for _, ft in ipairs(feat.features) do for _, sc in ipairs(ft.scripts) do message_ln(sc.script .. ', ') for _, lg in ipairs(sc.langs) do message_ln(lg, '; ') end end message_ln(ft.tag, ':') end for _, st in ipairs(feat.subtables) do message_ln('', st.name) end message(',', feat.type) end message() end } \lua{ function pad_right(str) local l = string.len(str) if l == 4 then return str else for n = l, 3 do str = str .. " " end return str end end --[===[ Scripts and languages are attribute n. 512 and 513, respectively. The attribute value is the index in scriptlist and langlist, and depends on the current font. --]===] function setscriptindex(scripttag) local scripttag = pad_right(scripttag:lower()) local currfont = font.fonts[font.current()] if currfont.scriptindex then if currfont.scriptindex[scripttag] then return currfont.scriptindex[scripttag] else return -1 end end end function setlangindex(langtag) local langtag = pad_right(langtag:upper()) local currfont = font.fonts[font.current()] if currfont.langindex then if currfont.langindex[langtag] then return currfont.langindex[langtag] else return -1 end end end % function setfeatureindex(feattag) function setfeatindex(feattag) local feattag = pad_right(feattag) local currfont = font.fonts[font.current()] if currfont.featindex then if currfont.featindex[feattag] then return currfont.featindex[feattag] else return -1 end end end } \attributedef\scripttag=512 \attributedef\langtag=513 \lua{ scripttag = 512 ; langtag = 513 ; feattagstart = 514 % TODO: merge the next three functions into a single one % Even the four ... function getscript(n) local si = node.has_attribute(n, scripttag) if si and si >=0 then local currfont = font.fonts[font.current()] if currfont.scriptlist then return currfont.scriptlist[si] else return end else return end end function getlang(n) local li = node.has_attribute(n, langtag) if li and li >=0 then local currfont = font.fonts[font.current()] if currfont.langlist then return currfont.langlist[li] else return end else return end end % Hmm ... added 2008-10-18 PM because # gave error messages % (attempt to get length of a number value) function tl(t) local n = 0 for _, _ in ipairs(t) do n = n + 1 end return n end function getfeats(n) local currfont = font.fonts[font.current()] if currfont.featlist then local fl = { } % for fi = 1, tl(currfont.featlist) do for fi = 1, #currfont.featlist do local fv = node.has_attribute(n, feattagstart + fi) if fv and fv > 0 then table.insert(fl, currfont.featlist[fi]) end end return fl else return end end function setlookups(n) local currscript = getscript(n) local currlang = getlang(n) local currfeats = getfeats(n) local lookups = find_lookups(currscript, currlang, currfeats) return lookups end function find_lookups(sname, lname, ftable) local cfont = font.fonts[font.current()] local feats = cfont.features local script = sname or "" local lang = lname or "dflt" local ftable = ftable or { } local lookups = { } if feats then for _, fname in ipairs(ftable) do local feat = feats[fname] if feat then local fscript = feat[pad_right(script)] if fscript then local flang = fscript[pad_right(lang)] or fscript.dflt if flang then local sts = flang.subtables or { } for _, st in ipairs(sts) do table.insert(lookups, st) end end end end end end return lookups end } \lua{ --[===[ We operate directly on the node list. Knows only about GSUB lookup type 1 for the moment! --]===] opentype = opentype or { } function opentype.mainengine(head) local currfont = font.fonts[head.font] local names_of_char = currfont.map.names_of_char if beverbose(head) then message_ln('', unicode.utf8.char(head.char), "font", currfont.filename) end if beverboser(head) then message('', 'subtype', head.subtype, 'xoffset', head.xoffset, 'left', head.left, 'right', head.right) elseif beverbose(head) then message() end % Clearly there must be a much more efficient way than to query the font % each time, but stick to that for the moment. lookups = setlookups(head) for _, ft in ipairs(lookups) do --[===[ Glyph table does not need exist, we need to check for it explicitely. --]===] if currfont.characters[head.char] then local gl = currfont.characters[head.char].lookups if gl then if beverbose(head) then message(table.serialize(gl, "glyph lookups")) end local fa = gl[ft] if fa then for _, f in ipairs(fa) do if f.type == 'substitution' then head.char = names_of_char[f.specification.variant] end end end end end end return head end function dothingswithglyphs(head, groupcode) local glyph_id = node.id("glyph") if beverbose(head) then message() end for n in node.traverse(head) do if beverbose(n) then message_ln(node.type(n.id)) end if n.id == glyph_id then n = opentype.mainengine(n) elseif n.id == node.id('hlist') then local nl = n.list if nl then if nl.id == glyph_id then for nlc in node.traverse(nl) do if nlc.id == glyph_id then nlc = opentype.mainengine(nlc) end end n.list = opentype.mainengine(n.list) end end if beverbose(n) then message() end else if beverbose(n) then message() end end end return head end --[===[ We can insert kerns readily in the node list, by simply creating a new node. --]===] function dothingswithglyphs2(head, groupcode) local glyph_id = node.id('glyph') for n in node.traverse(head) do if n.id == glyph_id and n.char == 0x54 then message("Found char 'T'") local kern = node.new('kern') kern.kern = -30000 % -29601.791999999998? for f, _ in pairs(pdf) do message(f) end message() head = node.insert_after(head, n, kern) end end return head end function list_nodes(head) local glyph_id = node.id('glyph') for n in node.traverse(head) do message_ln(node.type(n.id)) if n.id == glyph_id then local currfont = font.fonts[n.font] message('', unicode.utf8.char(n.char), currfont.filename) message(table.serialize(currfont.characters[n.char], "glyph table")) else message() end end return head end } \def\listnodes{\lua{callback.register('pre_linebreak_filter', list_nodes)}} \def\dothingswithglyphs{% \lua{callback.register("pre_linebreak_filter", dothingswithglyphs)} } \def\donothingwithglyphs{% \lua{callback.register("pre_linebreak_filter", nil)} } \def\setscript#1{\scripttag=\lua{tex.print(setscriptindex("#1"))}\relax} \def\unsetscript{\scripttag=-1\relax} \def\setlang#1{\langtag=\lua{tex.print(setlangindex("#1"))}\relax} \def\unsetlang{\langtag=-1\relax} % TODO: enable setting list (comma-separated, whatever). % ... and unsetting for all the tags! \def\setfeat#1{% \attribute\lua{ local fi = setfeatindex("#1") if fi < 0 then fi = 0 end tex.print(feattagstart + fi)}=1\relax} \def\unsetfeat#1{% \attribute\lua{ local fi = setfeatindex("#1") if fi < 0 then fi = 0 end tex.print(feattagstart + fi)}=-1\relax} \font\cronos=CronosPro-Regular\cronos REUTENAUER To 1729 And now, for something completely different: % Setting script, language, and two OpenType features \setscript{latn} \setlang{CRT} \setfeat{sups} \setfeat{sinf} % Starting the layout engine \dothingswithglyphs EN TOUTES CAPITALES {\setfeat{c2sc} ET EN PETITES CAPITALES :-)} ET DE NOUVEAU EN (GRANDES) CAPITALES. % Now, toggle c2sc for good. \setfeat{c2sc} To REUTENAUER Gábor BELLA Ŗ Ř 1729 % Some more tests. \lua{ --[===[ The table returned by FontForge, when reading an OpenType font, contains a 'kerns' entry which is the exact same as the one associated with the 'kern' feature in the 'gpos' entry. Here the relevant entries for CronosPro-Regular. --]===] local tfont = font.fonts[font.current()] local ofont = fontforge.to_table(fontforge.open(tfont.filename)) local kerns_feat = ofont.gpos[2].subtables[2].kernclass local kerns_table = ofont.kerns } HELLO WORLD RE-HELLO WORLD \unsetfeat{c2sc} HW % Unset c2sc, which had just been unset. Even without an explicit lang, it % works, since Cronos has a default language. \setfeat{c2sc} \unsetlang HW % On the other hand, if no script is explicitely set, the layout engine cannot % find any lookup. \unsetscript HW % Thus, set the script back, and also add smcp in order to have all-small caps. \setscript{LATN} \unsetfeat{sups} % Feature sinf makes punctuation rather horrible :-) \setfeat{smcp} \input knuth % TODO: strange space at the end of the hbox (glyph 357223?) T\kern-.1667em\lower.5ex\hbox{EEEEEE}\kern-.125emX \unsetfeat{c2sc} \unsetfeat{smcp} \unsetfeat{sinf} 1{\setfeat{sups}st} experiments with {\setfeat{smcp}OpenType}! % TODO: unset everything M{\setfeat{sups}r} le Président, je vous fais une lettre, etc. \font\lib=LinLibertine_Re-2.8.14.otf {\lib 1{\setfeat{sups}str} experiments with {\setfeat{smcp}OpenType}! % TODO: unset everything % What happens below is very strange ... % Language is correctly unset (no FRA language in the font), % and things work better now that \set* and \unset* are followed by \relax, % But the sups behaviour is just too strange ... {\setlang{FRA} M{\setfeat{sups}\relax r} le Président, je vous fais une lettre, etc. 1{\setfeat{sups}str} experiments with {\setfeat{smcp}OpenType}! } } % \beverbose 3{\setfeat{zero}0} \setfeat{zero} 30 aoeusneoatuaeo % \listnodes \font\doulos=DoulosSILR\doulos Hello, world! {\setfeat{smcp} Hello world} % \dumpnatfont \font\geeza={Geeza Pro}\geeza ABCDE 覺Թى! \font\thonburi=Thonburi\thonburi ℞ \font\fpl=FPLNeu-Italic\fpl thdih ABCDE 覺Թى! % \beverboser \font\trebuchet=trebuc\trebuchet thdih ABCDE 覺Թى! \font\lm=lmroman12-regular\lm Hello, Latin Modern! 42 {\setfeat{onum} 1729} \setlang{arab} % ARGH! ARGH! ARGH! % \donothingwithglyphs \font\adobearabic=AdobeArabic-Regular\adobearabic {\textdir TRT {\setfeat{init}ع}% {\setfeat{fina}ر}{\setfeat{init}ب}{\setfeat{medi}ي}% {\setfeat{fina}ة}% } ع \font\myriad=MyriadPro-Regular\myriad Myriad \font\arabtype=arabtype\arabtype {\textdir TRT {\setfeat{init}ع}% %\setfeat{medi}ربي% {\setfeat{fina}ر}{\setfeat{init}ب}{\setfeat{medi}ي}% {\setfeat{fina}ة}% } \font\minion=MinionPro-Capt\minion \unsetfeat{sups} Minion \bye