mirror of https://github.com/odrling/Aegisub
Somewhat working furigana layouting
Originally committed to SVN as r1181.
This commit is contained in:
parent
9ae7be4443
commit
02c18a3380
|
@ -39,17 +39,20 @@ end
|
|||
function karaskel.collect_head(subs, generate_furigana)
|
||||
local meta = { res_x = 0, res_y = 0 }
|
||||
local styles = { n = 0 }
|
||||
local toinsert = {}
|
||||
local first_style_line = nil
|
||||
|
||||
if not karaskel.furigana_scale then
|
||||
karaskel.furigana_scale = 0.5
|
||||
end
|
||||
|
||||
local i = 1
|
||||
while i < #subs do
|
||||
-- First pass: collect all existing styles and get resolution info
|
||||
for i = 1, #subs do
|
||||
if aegisub.progress.is_cancelled() then error("User cancelled") end
|
||||
local l = subs[i]
|
||||
|
||||
if l.class == "style" then
|
||||
if not first_style_line then first_style_line = i end
|
||||
-- Store styles into the style table
|
||||
styles.n = styles.n + 1
|
||||
styles[styles.n] = l
|
||||
|
@ -65,26 +68,35 @@ function karaskel.collect_head(subs, generate_furigana)
|
|||
fs.shadow = l.shadow * karaskel.furigana_scale
|
||||
fs.name = l.name .. "-furigana"
|
||||
|
||||
styles.n = styles.n + 1
|
||||
styles[styles.n] = fs
|
||||
styles[fs.name] = fs
|
||||
-- TODO: also actually insert into file
|
||||
table.insert(toinsert, fs) -- queue to insert in file
|
||||
end
|
||||
|
||||
elseif l.class == "info" then
|
||||
-- Also look for script resolution
|
||||
local k = l.key:lower()
|
||||
if k == "playresx" then
|
||||
meta.res_x = math.floor(l.value)
|
||||
elseif k == "playresy" then
|
||||
meta.res_y = math.floor(l.value)
|
||||
end
|
||||
meta[k] = l.value
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
-- Fix missing resolution data
|
||||
-- Second pass: insert all toinsert styles that don't already exist
|
||||
for i = 1, #toinsert do
|
||||
if not styles[toinsert[i].name] then
|
||||
-- Insert into styles table
|
||||
styles.n = styles.n + 1
|
||||
styles[styles.n] = toinsert[i]
|
||||
styles[toinsert[i].name] = toinsert[i]
|
||||
-- And subtitle file
|
||||
subs[-first_style_line] = toinsert[i]
|
||||
end
|
||||
end
|
||||
|
||||
-- Fix resolution data
|
||||
if meta.playresx then
|
||||
meta.res_x = math.floor(meta.playresx)
|
||||
end
|
||||
if meta.playresy then
|
||||
meta.res_y = math.floor(meta.playresy)
|
||||
end
|
||||
if meta.res_x == 0 and meta_res_y == 0 then
|
||||
meta.res_x = 384
|
||||
meta.res_y = 288
|
||||
|
@ -116,17 +128,10 @@ function karaskel.preproc_line_text(meta, styles, line)
|
|||
line.kara = { n = 0 }
|
||||
line.furi = { n = 0 }
|
||||
|
||||
if styles[line.style] then
|
||||
line.styleref = styles[line.style]
|
||||
else
|
||||
aegisub.debug.out(2, "WARNING: Style not found: " .. line.style .. "\n")
|
||||
line.styleref = styles[1]
|
||||
end
|
||||
|
||||
line.text_stripped = ""
|
||||
line.duration = line.end_time - line.start_time
|
||||
|
||||
local worksyl = { highlights = {n=0} }
|
||||
local worksyl = { highlights = {n=0}, furi = {n=0} }
|
||||
local cur_inline_fx = ""
|
||||
for i = 0, #kara do
|
||||
local syl = kara[i]
|
||||
|
@ -146,7 +151,7 @@ function karaskel.preproc_line_text(meta, styles, line)
|
|||
if prefix ~= "#" and prefix ~= "#" and i > 0 then
|
||||
line.kara[line.kara.n] = worksyl
|
||||
line.kara.n = line.kara.n + 1
|
||||
worksyl = { highlights = {n=0} }
|
||||
worksyl = { highlights = {n=0}, furi = {n=0} }
|
||||
end
|
||||
|
||||
-- Detect furigana (both regular and fullwidth pipes work)
|
||||
|
@ -167,8 +172,8 @@ function karaskel.preproc_line_text(meta, styles, line)
|
|||
-- (Furi is always allowed to spill over the right edge of main text.)
|
||||
local prefix = furitext:sub(1,unicode.charwidth(furitext,1))
|
||||
if prefix == "!" or prefix == "!" then
|
||||
furi.isbreak = true -- Don't join with furi in previous syllable
|
||||
furi.spillback = false -- Allow to "spill" furi over the left edge of main text
|
||||
furi.isbreak = true
|
||||
furi.spillback = false
|
||||
elseif prefix == "<" or prefix == "<" then
|
||||
furi.isbreak = true
|
||||
furi.spillback = true
|
||||
|
@ -188,6 +193,8 @@ function karaskel.preproc_line_text(meta, styles, line)
|
|||
|
||||
line.furi.n = line.furi.n + 1
|
||||
line.furi[line.furi.n] = furi
|
||||
worksyl.furi.n = worksyl.furi.n + 1
|
||||
worksyl.furi[worksyl.furi.n] = furi
|
||||
end
|
||||
|
||||
-- Always add highlight data
|
||||
|
@ -216,8 +223,10 @@ function karaskel.preproc_line_text(meta, styles, line)
|
|||
|
||||
-- And add new data to worksyl
|
||||
worksyl.i = line.kara.n
|
||||
worksyl.text_stripped = syltext
|
||||
worksyl.text_stripped = prespace .. syltext .. postspace
|
||||
worksyl.inline_fx = cur_inline_fx
|
||||
worksyl.prespace = prespace
|
||||
worksyl.postspace = postspace
|
||||
else
|
||||
-- This is just an extra highlight
|
||||
worksyl.duration = worksyl.duration + syl.duration
|
||||
|
@ -231,9 +240,259 @@ function karaskel.preproc_line_text(meta, styles, line)
|
|||
end
|
||||
|
||||
|
||||
-- Pre-calculate positioning information for the given line, also layouting furigana text if needed
|
||||
-- Pre-calculate sizing information for the given line, no layouting is done
|
||||
-- Modifies the object passed for line
|
||||
function karaskel.preproc_line_size(meta, styles, line)
|
||||
if not line.kara then
|
||||
karaskel.preproc_line_text(meta, styles, line)
|
||||
end
|
||||
|
||||
-- Add style information
|
||||
if styles[line.style] then
|
||||
line.styleref = styles[line.style]
|
||||
else
|
||||
aegisub.debug.out(2, "WARNING: Style not found: " .. line.style .. "\n")
|
||||
line.styleref = styles[1]
|
||||
end
|
||||
|
||||
-- Calculate whole line sizing
|
||||
line.width, line.height, line.descent, line.extlead = aegisub.text_extents(line.styleref, line.text_stripped)
|
||||
|
||||
-- Calculate syllable sizing
|
||||
for s = 0, line.kara.n do
|
||||
local syl = line.kara[s]
|
||||
syl.style = line.styleref
|
||||
syl.width, syl.height = aegisub.text_extents(syl.style, syl.text_stripped)
|
||||
syl.prespacewidth = aegisub.text_extents(syl.style, syl.prespace)
|
||||
syl.postspacewidth = aegisub.text_extents(syl.style, syl.postspace)
|
||||
end
|
||||
|
||||
-- Calculate furigana sizing
|
||||
if styles[line.style .. "-furigana"] then
|
||||
line.furistyle = styles[line.style .. "-furigana"]
|
||||
else
|
||||
aegisub.debug.out(4, "No furigana style defined for style '%s'\n", line.style)
|
||||
line.furistyle = false
|
||||
end
|
||||
if line.furistyle then
|
||||
for f = 1, line.furi.n do
|
||||
local furi = line.furi[f]
|
||||
furi.style = line.furistyle
|
||||
furi.width, furi.height = aegisub.text_extents(furi.style, furi.text)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Layout a line, including furigana layout
|
||||
-- Modifies the object passed for line
|
||||
function karaskel.preproc_line_pos(meta, styles, line)
|
||||
if not line.styleref then
|
||||
karaskel.preproc_line_size(meta, styles, line)
|
||||
end
|
||||
|
||||
-- Syllable layouting must be done before the rest, since furigana layout may change the total width of the line
|
||||
if line.furistyle then
|
||||
karaskel.do_furigana_layout(meta, styles, line)
|
||||
else
|
||||
karaskel.do_basic_layout(meta, styles, line)
|
||||
end
|
||||
|
||||
-- Effective margins
|
||||
line.margin_v = line.margin_t
|
||||
line.eff_margin_l = ((line.margin_l > 0) and line.margin_l) or line.styleref.margin_l
|
||||
line.eff_margin_r = ((line.margin_r > 0) and line.margin_r) or line.styleref.margin_r
|
||||
line.eff_margin_t = ((line.margin_t > 0) and line.margin_t) or line.styleref.margin_t
|
||||
line.eff_margin_b = ((line.margin_b > 0) and line.margin_b) or line.styleref.margin_b
|
||||
line.eff_margin_v = ((line.margin_v > 0) and line.margin_v) or line.styleref.margin_v
|
||||
-- And positioning
|
||||
if line.styleref.align == 1 or line.styleref.align == 4 or line.styleref.align == 7 then
|
||||
-- Left aligned
|
||||
line.left = line.eff_margin_l
|
||||
line.center = line.left + line.width / 2
|
||||
line.right = line.left + line.width
|
||||
line.x = line.left
|
||||
line.halign = "left"
|
||||
elseif line.styleref.align == 2 or line.styleref.align == 5 or line.styleref.align == 8 then
|
||||
-- Centered
|
||||
line.left = (meta.res_x - line.eff_margin_l - line.eff_margin_r - line.width) / 2 + line.eff_margin_l
|
||||
line.center = line.left + line.width / 2
|
||||
line.right = line.left + line.width
|
||||
line.x = line.center
|
||||
line.halign = "center"
|
||||
elseif line.styleref.align == 3 or line.styleref.align == 6 or line.styleref.align == 9 then
|
||||
-- Right aligned
|
||||
line.left = meta.res_x - line.eff_margin_r - line.width
|
||||
line.center = line.left + line.width / 2
|
||||
line.right = line.left + line.width
|
||||
line.x = line.right
|
||||
line.halign = "right"
|
||||
end
|
||||
line.hcenter = line.center
|
||||
if line.styleref.align >=1 and line.styleref.align <= 3 then
|
||||
-- Bottom aligned
|
||||
line.bottom = meta.res_y - line.eff_margin_b
|
||||
line.middle = line.bottom - line.height / 2
|
||||
line.top = line.bottom - line.height
|
||||
line.y = line.bottom
|
||||
line.valign = "bottom"
|
||||
elseif line.styleref.align >= 4 and line.styleref.align <= 6 then
|
||||
-- Mid aligned
|
||||
line.top = (meta.res_y - line.eff_margin_t - line.eff_margin_b) / 2 + line.eff_margin_t
|
||||
line.middle = line.top + line.height / 2
|
||||
line.bottom = line.top + line.height
|
||||
line.y = line.middle
|
||||
line.valign = "middle"
|
||||
elseif line.styleref.align >= 7 and line.styleref.align <= 9 then
|
||||
-- Top aligned
|
||||
line.top = line.eff_margin_t
|
||||
line.middle = line.top + line.height / 2
|
||||
line.bottom = line.top + line.height
|
||||
line.y = line.top
|
||||
line.valign = "top"
|
||||
end
|
||||
line.vcenter = line.middle
|
||||
end
|
||||
|
||||
|
||||
-- Do simple syllable layouting (no furigana)
|
||||
function karaskel.do_basic_layout(meta, styles, line)
|
||||
local curx = 0
|
||||
for i = 0, line.kara.n do
|
||||
local syl = line.kara[i]
|
||||
syl.left = curx + syl.prespacewidth
|
||||
syl.center = syl.left + syl.width / 2
|
||||
syl.right = syl.left + syl.width
|
||||
curx = curx + syl.prespacewidth + syl.width + syl.postspacewidth
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Do advanced furigana layout algorithm
|
||||
function karaskel.do_furigana_layout(meta, styles, line)
|
||||
-- Start by building layout groups
|
||||
-- Two neighboring syllables with furigana that join together are part of the same layout group
|
||||
-- A forced split creates a new layout group
|
||||
local lgroups = {}
|
||||
-- Start-sentinel
|
||||
local lgsentinel = {basewidth=0, furiwidth=0, syls={}, furi={}, spillback=false, left=0, right=0}
|
||||
table.insert(lgroups, lgsentinel)
|
||||
-- Create groups
|
||||
local last_had_furi = false
|
||||
local lg = { basewidth=0, furiwidth=0, syls={}, furi={}, spillback=false }
|
||||
for s = 0, line.kara.n do
|
||||
local syl = line.kara[s]
|
||||
-- Furigana-less syllables always generate a new layout group
|
||||
-- So do furigana-endowed syllables that are marked as split
|
||||
-- But if current lg has no width (usually only first) don't create a new
|
||||
if (syl.furi.n == 0 or syl.furi[1].issplit) and lg.basewidth > 0 then
|
||||
table.insert(lgroups, lg)
|
||||
lg = { basewidth=0, furiwidth=0, syls={}, furi={}, spillback=false }
|
||||
end
|
||||
|
||||
-- Add this syllable to lg
|
||||
lg.basewidth = lg.basewidth + syl.prespacewidth + syl.width + syl.postspacewidth
|
||||
table.insert(lg.syls, syl)
|
||||
|
||||
-- Add this syllable's furi to lg
|
||||
for f = 1, syl.furi.n do
|
||||
local furi = syl.furi[f]
|
||||
lg.furiwidth = lg.furiwidth + furi.width
|
||||
lg.spillback = lg.spillback or furi.spillback
|
||||
table.insert(lg.furi, furi)
|
||||
end
|
||||
end
|
||||
-- Insert last lg
|
||||
table.insert(lgroups, lg)
|
||||
-- And end-sentinel
|
||||
table.insert(lgroups, lgsentinel)
|
||||
|
||||
-- Layout the groups at macro-level
|
||||
-- Skip sentinel at ends in loop
|
||||
local curx = 0
|
||||
for i = 2, #lgroups-1 do
|
||||
local lg = lgroups[i]
|
||||
local prev = lgroups[i-1]
|
||||
|
||||
-- Three cases: No furigana, furigana smaller than base and furigana larger than base
|
||||
if lg.furiwidth == 0 then
|
||||
-- Here wa can basically just place the base text
|
||||
lg.left = curx
|
||||
lg.right = lg.left + lg.basewidth
|
||||
-- If there was any spillover from a previous group, add it to here
|
||||
if prev.rightspill and prev.rightspill > 0 then
|
||||
lg.leftspill = 0
|
||||
lg.rightspill = lg.basewidth - prev.rightspill
|
||||
prev.rightspill = 0
|
||||
end
|
||||
curx = curx + lg.basewidth
|
||||
elseif lg.furiwidth <= lg.basewidth then
|
||||
-- If there was any rightspill from previous group, we have to stay 100% clear of that
|
||||
if prev.rightspill and prev.rightspill > 0 then
|
||||
curx = curx + prev.rightspill
|
||||
prev.rightspill = 0
|
||||
end
|
||||
lg.left = curx
|
||||
lg.right = lg.left + lg.basewidth
|
||||
curx = curx + lg.basewidth
|
||||
-- Negative spill here
|
||||
lg.leftspill = (lg.furiwidth - lg.basewidth) / 2
|
||||
lg.rightspill = lg.leftspill
|
||||
else
|
||||
-- Furigana is wider than base, we'll have to spill in some direction
|
||||
if prev.rightspill and prev.rightspill > 0 then
|
||||
curx = curx + prev.rightspill
|
||||
prev.rightspill = 0
|
||||
end
|
||||
-- Do we spill only to the right or in both directions?
|
||||
if lg.spillback then
|
||||
-- Both directions
|
||||
lg.leftspill = (lg.furiwidth - lg.basewidth) / 2
|
||||
lg.rightspill = lg.leftspill
|
||||
-- If there was any furigana or spill on previous syllable we can't overlap it
|
||||
if prev.rightspill then
|
||||
lg.left = curx + lg.leftspill
|
||||
else
|
||||
lg.left = curx
|
||||
end
|
||||
else
|
||||
-- Only to the right
|
||||
lg.leftspill = 0
|
||||
lg.rightspill = lg.furiwidth - lg.basewidth
|
||||
lg.left = curx
|
||||
end
|
||||
lg.right = lg.left + lg.basewidth
|
||||
curx = lg.right
|
||||
end
|
||||
end
|
||||
|
||||
-- Now the groups are layouted, so place the individual syllables/furigana
|
||||
for i, lg in ipairs(lgroups) do
|
||||
local basecenter = (lg.left + lg.right) / 2 -- centered furi is centered over this
|
||||
local curx = lg.left -- base text is placed from here on
|
||||
-- Place base syllables
|
||||
for s, syl in ipairs(lg.syls) do
|
||||
syl.left = curx + syl.prespacewidth
|
||||
syl.center = syl.left + syl.width/2
|
||||
syl.right = syl.left + syl.width
|
||||
curx = syl.right + syl.postspacewidth
|
||||
end
|
||||
line.width = curx
|
||||
-- Place furigana
|
||||
if lg.furiwidth < lg.basewidth or lg.spillback then
|
||||
-- Center over group
|
||||
curx = lg.left + (lg.basewidth - lg.furiwidth) / 2
|
||||
else
|
||||
-- Left aligned
|
||||
curx = lg.left
|
||||
end
|
||||
for f, furi in ipairs(lg.furi) do
|
||||
furi.left = curx
|
||||
furi.center = furi.left + furi.width/2
|
||||
furi.right = furi.left + furi.width
|
||||
curx = furi.right
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
script_name = "Test furigana parsing"
|
||||
script_description = "Tests the Auto4/Lua karaskel furigana and multi-highlight parsing code by running it and dumping the result"
|
||||
script_name = "Test furigana"
|
||||
script_description = "Tests the Auto4/Lua karaskel furigana and multi-highlight code"
|
||||
script_author = "jfs"
|
||||
|
||||
include "karaskel.lua"
|
||||
|
||||
function test_furi(subs)
|
||||
function dump_furi(subs)
|
||||
aegisub.progress.task("Collecting header data")
|
||||
local meta, styles = karaskel.collect_head(subs, true) -- make sure to create furigana styles
|
||||
|
||||
aegisub.progress.task("Preprocessing lines")
|
||||
aegisub.progress.task("Processing lines")
|
||||
for i = 1, #subs do
|
||||
local l = subs[i]
|
||||
if l.class == "dialogue" then
|
||||
|
@ -39,4 +39,36 @@ function test_furi(subs)
|
|||
aegisub.debug.out(4, "Done dumping!")
|
||||
end
|
||||
|
||||
aegisub.register_macro(script_name, script_description, test_furi)
|
||||
function layout_furi(subs)
|
||||
aegisub.progress.task("Collecting header data")
|
||||
local meta, styles = karaskel.collect_head(subs, true) -- make sure to create furigana styles
|
||||
|
||||
aegisub.progress.task("Processing lines")
|
||||
for i = 1, #subs do
|
||||
local l = subs[i]
|
||||
if l.class == "dialogue" then
|
||||
aegisub.progress.task(l.text)
|
||||
karaskel.preproc_line_pos(meta, styles, l)
|
||||
aegisub.progress.task("Line layouting done, rendering...")
|
||||
-- First all syllables
|
||||
for s = 0, l.kara.n do
|
||||
local syl = l.kara[s]
|
||||
local lc = table.copy(l)
|
||||
lc.text = string.format("{\\pos(%.1f,%.1f)\\k%d\\k%d\\an5}%s", l.left+syl.center, l.middle, syl.start_time/10, syl.kdur, syl.text_stripped)
|
||||
subs.append(lc)
|
||||
end
|
||||
-- Then all furigana
|
||||
for f = 1, l.furi.n do
|
||||
local furi = l.furi[f]
|
||||
local lc = table.copy(l)
|
||||
lc.text = string.format("{\\pos(%.1f,%.1f)\\k%d\\k%d\\an5}%s", l.left+furi.center, l.top-l.height/2, furi.start_time/10, furi.duration/10, furi.text)
|
||||
lc.style = furi.style.name
|
||||
subs.append(lc)
|
||||
end
|
||||
end
|
||||
end
|
||||
aegisub.set_undo_point("Furigana layout test")
|
||||
end
|
||||
|
||||
aegisub.register_macro("Test furi parsing", "Run the furigana parsing code and dump the result", dump_furi)
|
||||
aegisub.register_macro("Test furi layout", "Run the furigana layout code and render the result", layout_furi)
|
||||
|
|
Loading…
Reference in New Issue