diff options
Diffstat (limited to 'texmf-dist/tex/generic/harftex/harf-load.lua')
-rw-r--r-- | texmf-dist/tex/generic/harftex/harf-load.lua | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/texmf-dist/tex/generic/harftex/harf-load.lua b/texmf-dist/tex/generic/harftex/harf-load.lua new file mode 100644 index 00000000..c467cedd --- /dev/null +++ b/texmf-dist/tex/generic/harftex/harf-load.lua @@ -0,0 +1,309 @@ +local hb = require("harf-base") + +local hbfonts = hb.fonts +local hbfonts = hbfonts or {} + +local cfftag = hb.Tag.new("CFF ") +local cff2tag = hb.Tag.new("CFF2") +local os2tag = hb.Tag.new("OS/2") +local posttag = hb.Tag.new("post") +local glyftag = hb.Tag.new("glyf") + +local function trim(str) + return str:gsub("^%s*(.-)%s*$", "%1") +end + +local function split(str, sep) + if str then + local result = string.explode(str, sep.."+") + for i, s in next, result do + result[i] = trim(result[i]) + end + return result + end +end + +local function parse(str, size) + local name, options = str:match("%s*(.*)%s*:%s*(.*)%s*") + local spec = { + specification = str, + size = size, + variants = {}, features = {}, options = {}, + } + + name = trim(name or str) + + local filename = name:match("%[(.*)%]") + if filename then + -- [file] + -- [file:index] + filename = string.explode(filename, ":+") + spec.file = filename[1] + spec.index = tonumber(filename[2]) or 0 + else + -- name + -- name/variants + local fontname, variants = name:match("(.-)%s*/%s*(.*)") + spec.name = fontname or name + spec.variants = split(variants, "/") + end + if options then + options = split(options, ";+") + for _, opt in next, options do + if opt:find("[+-]") == 1 then + local feature = hb.Feature.new(opt) + spec.features[#spec.features + 1] = feature + elseif opt ~= "" then + local key, val = opt:match("(.*)%s*=%s*(.*)") + if key == "language" then val = hb.Language.new(val) end + spec.options[key or opt] = val or true + end + end + end + return spec +end + +local function loadfont(spec) + local path, index = spec.path, spec.index + if not path then + return nil + end + + local key = string.format("%s:%d", path, index) + local data = hbfonts[key] + if data then + return data + end + + local hbface = hb.Face.new(path, index) + local tags = hbface and hbface:get_table_tags() + -- If the face has no table tags then it isn’t a valid SFNT font that + -- HarfBuzz can handle. + if tags then + local hbfont = hb.Font.new(hbface) + local upem = hbface:get_upem() + + -- All LuaTeX seem to care about in font type is whether it has CFF table + -- or not, so we check for that here. + local fonttype = nil + local hasos2 = false + local haspost = false + for i = 1, #tags do + local tag = tags[i] + if tag == cfftag or tag == cff2tag then + fonttype = "opentype" + elseif tag == glyftag then + fonttype = "truetype" + elseif tag == os2tag then + hasos2 = true + elseif tag == posttag then + haspost = true + end + end + + local fontextents = hbfont:get_h_extents() + local ascender = fontextents and fontextents.ascender or upem * .8 + local descender = fontextents and fontextents.descender or upem * .2 + + local gid = hbfont:get_nominal_glyph(0x0020) + local space = gid and hbfont:get_glyph_h_advance(gid) or upem / 2 + + local xheight, capheight = nil, nil + if hasos2 then + local os2 = hbface:get_table(os2tag) + local length = os2:get_length() + local data = os2:get_data() + if length >= 96 and string.unpack(">H", data) > 1 then + -- We don’t need much of the table, so we read from hard-coded offsets. + xheight = string.unpack(">H", data, 87) + capheight = string.unpack(">H", data, 89) + end + end + + xheight = xheight or ascender / 2 + capheight = capheight or ascender + + local slant = 0 + if haspost then + local post = hbface:get_table(posttag) + local length = post:get_length() + local data = post:get_data() + if length >= 32 and string.unpack(">i4", data) <= 0x00030000 then + local italicangle = string.unpack(">i4", data, 5) / 2^16 + if italicangle ~= 0 then + slant = -math.tan(italicangle * math.pi / 180) * 65536.0 + end + end + end + + -- Load CPAL palettes if avialable in the font. + local palettes = nil + if hbface:ot_color_has_palettes() and hbface:ot_color_has_layers() then + local count = hbface:ot_color_palette_get_count() + palettes = {} + for i = 1, count do + palettes[#palettes + 1] = hbface:ot_color_palette_get_colors(i) + end + end + + -- Load glyph metrics for all glyphs in the font. We used to do this on + -- demand to make loading fonts faster, but hit many limitations inside + -- LuaTeX (mainly with shared backend fonts, where LuaTeX would assume all + -- fonts it decides to share load the same set of glyphs). + -- + -- Getting glyph advances is fast enough, but glyph extents are slower + -- especially in CFF fonts. We might want to have an option to ignore exact + -- glyph extents and use font ascender and descender if this proved to be + -- too slow. + local glyphcount = hbface:get_glyph_count() + local glyphs = {} + for gid = 0, glyphcount - 1 do + local width = hbfont:get_glyph_h_advance(gid) + local height, depth, italic = nil, nil, nil + local extents = hbfont:get_glyph_extents(gid) + if extents then + height = extents.y_bearing + depth = extents.y_bearing + extents.height + if extents.x_bearing < 0 then + italic = -extents.x_bearing + end + end + glyphs[gid] = { + width = width, + height = height or ascender, + depth = -(depth or descender), + italic = italic or 0, + } + end + + local unicodes = hbface:collect_unicodes() + local characters = {} + for _, uni in next, unicodes do + characters[uni] = hbfont:get_nominal_glyph(uni) + end + + data = { + face = hbface, + font = hbfont, + upem = upem, + fonttype = fonttype, + space = space, + xheight = xheight, + capheight = capheight, + slant = slant, + glyphs = glyphs, + unicodes = characters, + psname = hbface:get_name(hb.ot.NAME_ID_POSTSCRIPT_NAME), + fullname = hbface:get_name(hb.ot.NAME_ID_FULL_NAME), + palettes = palettes, + haspng = hbface:ot_color_has_png(), + loaded = {}, -- Cached loaded glyph data. + } + + hbfonts[key] = data + return data + end +end + +local function scalefont(data, spec) + local size = spec.size + local options = spec.options + local hbfont = data.font + local upem = data.upem + local space = data.space + local capheight = data.capheight + + if size < 0 then + size = -655.36 * size + end + + -- We shape in font units (at UPEM) and then scale output with the desired + -- sfont size. + local scale = size / upem + hbfont:set_scale(upem, upem) + + -- Populate font’s characters table. + local glyphs = data.glyphs + local characters = {} + for gid, glyph in next, glyphs do + characters[hb.CH_GID_PREFIX + gid] = { + index = gid, + width = glyph.width * scale, + height = glyph.height * scale, + depth = glyph.depth * scale, + italic = glyph.italic * scale, + } + end + + local unicodes = data.unicodes + for uni, gid in next, unicodes do + characters[uni] = characters[hb.CH_GID_PREFIX + gid] + end + + -- Select font palette, we support `palette=index` option, and load the first + -- one otherwise. + local palettes = data.palettes + local palette = palettes and palettes[tonumber(options.palette) or 1] + + local letterspace = tonumber(options.letterspace or 0) / 100 * upem + space = space + letterspace + + local slantfactor = tonumber(options.slant or 0) + + return { + name = spec.specification, + filename = spec.path, + designsize = size, + psname = data.psname, + fullname = data.fullname, + index = spec.index, + size = size, + type = "real", + embedding = "subset", + tounicode = 1, + nomath = true, + format = data.fonttype, + slant = slantfactor * 1000, + characters = characters, + parameters = { + slant = data.slant, + space = space * scale, + space_stretch = space * scale / 2, + space_shrink = space * scale / 3, + x_height = data.xheight * scale, + quad = size, + extra_space = space * scale / 3, + [8] = capheight * scale, -- for XeTeX compatibility. + }, + hb = { + scale = scale, + spec = spec, + palette = palette, + shared = data, + letterspace = letterspace, + }, + } +end + +local function define_font(name, size) + local spec = type(name) == "string" and parse(name, size) or name + if spec.file then + spec.path = kpse.find_file(spec.file, "truetype fonts") or + kpse.find_file(spec.file, "opentype fonts") + else + -- XXX support font names + end + + if spec.specification == "" then return nil end + + local tfmdata = nil + local hbdata = loadfont(spec) + if hbdata then + tfmdata = scalefont(hbdata, spec) + else + tfmdata = font.read_tfm(spec.specification, spec.size) + end + return tfmdata +end + +return define_font |