Hi Hans,
The internal "pack_result_tagged" function that you added for potraceing
seems to be useful beyond bitmapped fonts, because it lets you
manipulate arbitrary CFF outlines from Lua. As far as I'm aware, this
makes ConTeXt the only program that can modify and create non-Type 3
fonts while generating a document.
This seems to be quite useful for a better fake bold. The traditional
PDF method of applying a thin stroke to characters can have some
rendering issues and doesn't give you very much control, but with
"pack_result_tagged", we can make a "real" fake bold font where the font
itself has been emboldened (versus just applying a PDF effect). This
also gives us unlimited control, so it should theoretically be possible
to replicate FontForge's very good bold/italicizing algorithms to get
much better results than you can get from PDF effects.
I've attached a very hacky demonstration that needs to be compiled with
MkIV (despite using LMTX-only features). The offsetting algorithm isn't
quite correct around straight lines, but it's still a fairly neat demo.
Thanks,
-- Max
#!/usr/bin/env -S context --luatex --forcecld
local insert = table.insert
local append = table.append
do
local data = io.loaddata(resolvers.findfile("font-cff.lmt"))
data = data:gsub("pack_result_tagged =", "fonts.handlers.otf.pack_result_tagged ="):gsub("<const>", "")
load(data)()
end
local font_id, _ = fonts.definers.define {
-- The path to the font file
lookup = "file",
name = "texgyrepagella-regular.otf",
-- The size of the font
size = tex.sp("12pt"),
-- Use the default features
method = "featureset",
detail = "default",
}
local tfmdata = font.getcopy(font_id)
tfmdata.fullname = nil
tfmdata.psname = nil
tfmdata.streamprovider = 1
tfmdata.name = "fakebold"
local shapes = fonts.hashes.shapes[font_id].glyphs
local streams = fonts.hashes.streams[font_id].streams
local function subdivide_segments(inputs)
local outputs = {}
local x0, y0 = 0, 0
for i, input in ipairs(inputs) do
local command = input[#input]
if command == "l" then
--[[ -- Convert line to cubic bezier
local x1, y1 = input[1], input[2]
local slope_x, slope_y = (x1 - x0) / 3, (y1 - y0) / 3
outputs[#outputs + 1] = {
x0 + slope_x, y0 + slope_y,
x1 - slope_x, y1 - slope_y,
x1, y1,
"c"
} --]]
-- Leave lines as-is
outputs[#outputs + 1] = input
elseif command == "c" then
-- Divide the cubic bezier into two cubic beziers
local x1, y1 = input[1], input[2]
local x2, y2 = input[3], input[4]
local x3, y3 = input[5], input[6]
local x01, y01 = (x0 + x1) / 2, (y0 + y1) / 2
local x12, y12 = (x1 + x2) / 2, (y1 + y2) / 2
local x23, y23 = (x2 + x3) / 2, (y2 + y3) / 2
local x012, y012 = (x01 + x12) / 2, (y01 + y12) / 2
local x123, y123 = (x12 + x23) / 2, (y12 + y23) / 2
local x0123, y0123 = (x012 + x123) / 2, (y012 + y123) / 2
local xa1, ya1 = x01, y01
local xa2, ya2 = x012, y012
local xa3, ya3 = x0123, y0123
local xb1, yb1 = x123, y123
local xb2, yb2 = x23, y23
local xb3, yb3 = x3, y3
outputs[#outputs + 1] = { xa1, ya1, xa2, ya2, xa3, ya3, "c" }
outputs[#outputs + 1] = { xb1, yb1, xb2, yb2, xb3, yb3, "c" }
elseif command == "m" or command == "move" then
outputs[#outputs + 1] = input
else
error("Unknown segment command: " .. tostring(command))
end
x0, y0 = input[#input - 2], input[#input - 1]
end
return outputs
end
local function get_xy(segments, segment_index, point_index, offset)
local segment = segments[segment_index]
point_index = point_index + 2 * offset
local x, y
while true do
x, y = segment[point_index], segment[point_index + 1]
if x and y then
break
end
if point_index > #segment - 1 then
segment_index = segment_index + 1
local prev_length = #segment
segment = segments[segment_index]
if not segment then
segment = segments[1]
x, y = segment[1], segment[2]
return x, y
end
point_index = point_index - prev_length + 1
end
if point_index <= 0 then
segment_index = segment_index - 1
segment = segments[segment_index]
if not segment then
segment = segments[#segments]
x, y = segment[#segment - 2], segment[#segment - 1]
return x, y
end
point_index = #segment - 1 + point_index
end
end
return x, y
end
local offset_distance = -17.5
local function process(character)
local sections = { {} }
for _, segment in ipairs(character.segments) do
local type = segment[#segment]
if type == "move" then
insert(sections, {})
else
insert(sections[#sections], segment)
end
end
for i, section in ipairs(sections) do
local section = subdivide_segments(section)
local new = table.copy(section)
for j, segment in ipairs(section) do
for k = 1, #segment - 2, 2 do
local x_prev, y_prev = get_xy(section, j, k, -1)
local x_next, y_next = get_xy(section, j, k, 1)
local x, y = segment[k], segment[k + 1]
local dx, dy = x_next - x_prev, y_next - y_prev
local length = math.sqrt(dx^2 + dy^2)
local normal_x, normal_y = -dy / length, dx / length
if normal_x ~= normal_x then normal_x = 0 end
if normal_y ~= normal_y then normal_y = 0 end
x = x + (normal_x * offset_distance)
y = y + (normal_y * offset_distance)
new[j][k] = x
new[j][k + 1] = y
end
end
sections[i] = new
end
character.segments = {}
for _, section in ipairs(sections) do
append(character.segments, section)
insert(character.segments, { "move", "close" })
end
character.tounicode = ("%X"):format(character.unicode or 0)
local stream = fonts.handlers.otf.pack_result_tagged(
character.segments,
character.width,
0,
0
)
return stream
end
for index, character in pairs(shapes) do
streams[index] = process(character)
end
local font_id = font.define(tfmdata)
fonts.hashes.streams[font_id] = { streams = streams }
token.set_macro("newfakebold", ([[\setfontid%d]]):format(font_id))
context[[
\setupbodyfont[pagella, 12pt]
\define\SampleText{The quick brown fox jumps over the lazy dog.}
\definefontfeature[oldfakebold][effect={width=0.35}]
\definefont[oldfakebold][file:texgyrepagella-regular.otf*default,oldfakebold @ 12pt]
\startTEXpage[offset=1ex]
\setupTABLE[column][1][align=flushright, style=tt]
\setupTABLE[column][2][align=flushleft, loffset=1em]
\startTABLE[frame=off]
\NC Regular \NC \SampleText \NC\NR
\NC Real Bold \NC \bf \SampleText \NC\NR
\NC Old Fake Bold \NC \oldfakebold \SampleText \NC\NR
\NC New Fake Bold \NC \newfakebold \SampleText \NC\NR
\stopTABLE
\stopTEXpage
]]
___________________________________________________________________________________
If your question is of interest to others as well, please add an entry to the
Wiki!
maillist : [email protected] /
https://mailman.ntg.nl/mailman3/lists/ntg-context.ntg.nl
webpage : https://www.pragma-ade.nl / https://context.aanhet.net (mirror)
archive : https://github.com/contextgarden/context
wiki : https://wiki.contextgarden.net
___________________________________________________________________________________