initial released
Some checks are pending
CI / Julia 1.6 - ubuntu-latest - x64 (push) Waiting to run
CI / Julia 1.7 - ubuntu-latest - x64 (push) Waiting to run
CI / Julia pre - ubuntu-latest - x64 (push) Waiting to run

This commit is contained in:
Tan, Kian-ting 2025-01-31 01:11:04 +08:00
parent 27d66b0e51
commit 0bb2a4d7fe
9 changed files with 2700 additions and 37 deletions

View file

@ -4,3 +4,14 @@ another experimential typesetting tool
## Known issues ## Known issues
- Some .ttc file(eg. Noto Sans/Serif CJK) can't be loaded. It seems to be a libharu bug. For those want to use Noto Sans/Serif CJK, please download the .ttf file on Google Fonts. [Example (Trad. Chinese Version)](https://fonts.google.com/noto/specimen/Noto+Sans+TC) - Some .ttc file(eg. Noto Sans/Serif CJK) can't be loaded. It seems to be a libharu bug. For those want to use Noto Sans/Serif CJK, please download the .ttf file on Google Fonts. [Example (Trad. Chinese Version)](https://fonts.google.com/noto/specimen/Noto+Sans+TC)
## How to test
To test with the example, please using the command:
```
cd /path/to/uahgi
julia --project="." src/uahgi.jl example/ex1.ug
```
## Origin of the name
The name is after the Taiwanese/Hokkien word for the movable type, which is ua̍h-gī (活字), in Taichung-Basin accent.

2516
example/ex1.pdf Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,8 @@
{@lang|en} {@lang|en}%
{@def|@font|{@quote|FreeSans|AR PL UKai TW}} %{@def|@font|{@quote|FreeSans|AR PL UKai TW}}%
{@def|@fontsize|12} %{@def|@fontsize|12}%
{@def|@linewidth|200} %in px% %{@def|@linewidth|200} %in px
{@def|@spacing|{@hglue|{@ex|1}|2}} %{@def|@spacing|{@hglue|{@ex|1}|2}}%
{@def|@cjk_spacing|{@hglue|{@ex|0}|0.001}} %{@def|@cjk_spacing|{@hglue|{@ex|0}|0.001}}%
11111 222 3333333 444444 555555 666666 77777 88888 99999 000000 11111 22222 33333 55555 %the processing of typesetting technology is so sophisticated that many people are afraid of
the implementation of typesetting language. However, it is good to give TEXBook a try.測試一段漢字,究竟漢字會不會斷行呢

View file

@ -3,35 +3,160 @@ using Match
u = Main.uahgi u = Main.uahgi
# the cost of make ith to jth char a line total_cost_table = Dict()
function cost(items, i, j, linewidth)
# the cost of make ith to jth item a line
function cost(items, i, j, linewidth, last_of_queue=false)
slice = items[i:j] slice = items[i:j]
cost_list = map(x -> item_cost(x), slice) last = items[j]
sum_of_cost_list = reduce((x, y)-> x+y, cost_list) if typeof(last) == u.ChBox && last_of_queue === false
return Inf
end
sum_of_cost_list = 0
# add disc (after) if it exists
if i > 2 && typeof(items[i-1]) == u.Disc
previous = items[i-1]
sum_of_cost_list += item_width(previous.after)
end
if typeof(last) != u.Disc
cost_list = map(x -> item_width(x), slice)
sum_of_cost_list += reduce((x, y)-> x+y, cost_list) - item_width(slice[end])
else
cost_list = map(x -> item_width(x), slice[1:end-1])
if cost_list != []
sum_of_cost_list += reduce((x, y)-> x+y, cost_list)
end
last_width = item_width(last.before)
sum_of_cost_list += last_width
end
if sum_of_cost_list > linewidth if sum_of_cost_list > linewidth
return Inf return Inf
else else
return linewidth - sum_of_cost_list return (linewidth - sum_of_cost_list) ^ 3
end end
end end
function item_cost(i) """the total cost from 1 to n"""
function total_cost(items, n, linewidth, last_of_queue=false)
if haskey(total_cost_table, n)
return total_cost_table[n][1]
end
# first to nth item cost
fst_to_nth_cost = cost(items, 1, n, linewidth, last_of_queue)
if fst_to_nth_cost < Inf
total_cost_table[n] = [fst_to_nth_cost, 0]
return fst_to_nth_cost
else
mininal_cost = +Inf
prev_breakpoint = nothing
for j in 1:1:(n-1)
tmp_cost = total_cost(items, j, linewidth, false) + cost(items, j+1, n, linewidth, last_of_queue)
if tmp_cost < mininal_cost
mininal_cost = tmp_cost
prev_breakpoint = j
end
end
total_cost_table[n] = [mininal_cost, prev_breakpoint]
return mininal_cost
end
end
"""width of a item"""
function item_width(i)
if typeof(i) == u.HGlue if typeof(i) == u.HGlue
return i.wd return i.wd
elseif typeof(i) == u.ChBox elseif typeof(i) == u.ChBox
return i.wd return i.wd
elseif typeof(i) == u.Disc
return item_width(i.orig)
else else
return 0 return 0
end end
end end
function arrange(vbox, env) function arrange(vbox, env)
result_vbox_inner = []
result_vbox_inner = [u.HBox([],nothing,nothing,nothing,nothing,nothing)]
linewidth = parse(Int, env["linewidth"]) linewidth = parse(Int, env["linewidth"])
print(cost(vbox.eles[1].eles, 1, 20, linewidth)) eles = vbox.eles[1].eles
total_cost(eles, length(eles), linewidth, true)
return vbox
current_point = length(eles)
breakpoint_list = [current_point]
while total_cost_table[current_point][2] !== 0.0
current_point = total_cost_table[current_point][2]
push!(breakpoint_list, current_point)
end end
breakpoint_list_reversed = reverse(breakpoint_list)
for (x, i) in enumerate(1:1:length(eles))
item = eles[x]
if !(x in breakpoint_list_reversed)
if typeof(item) == u.Disc
if item.orig != []
push!(result_vbox_inner[end].eles, item.orig)
end
else
push!(result_vbox_inner[end].eles, item)
end
# x is the last one
elseif i == length(eles)
push!(result_vbox_inner[end].eles, item)
# x in breakpoint_list_reversed
else
if typeof(item) == u.Disc
if item.before != []
push!(result_vbox_inner[end].eles, item.before)
end
push!(result_vbox_inner, u.HBox([],nothing,nothing,nothing,nothing,nothing))
if item.after != []
push!(result_vbox_inner[end].eles, item.after)
end
else
push!(result_vbox_inner, u.HBox([],nothing,nothing,nothing,nothing,nothing))
end
end
end
return result_vbox_inner
end
function position(box_inner, env#=to be used later=#)
pages = [] #a subarray is the content of a page
orig_y = 700 # it can be derived from env
orig_x = 100 # it can be derived from env
posX = orig_x #cursor x
posY = orig_y #cursor y
baselineskip = 30 # it can be derived from env
for hbox in box_inner
pages = position_chbox(hbox.eles, pages, posX, posY)
posX = orig_x
posY -= baselineskip
end
return pages
end
"""positioning all the chboxes in a HBox"""
function position_chbox(hbox_eles, pages, posX, posY)
for i in hbox_eles
if typeof(i) == u.HGlue
deltaX = i.wd
posX += deltaX
else #ChBox
deltaX = i.wd
i.x = posX
i.y = posY
push!(pages, i)
posX += deltaX
end
end
return pages
end
end end

View file

@ -89,9 +89,9 @@ function match_char(ast_item, patterns)
for i in splitted for i in splitted
push!(final, c.CHAR(i)) push!(final, c.CHAR(i))
push!(final, c.SEQ([c.ELE([c.ID("disc")]), push!(final, c.SEQ([c.ELE([c.ID("disc")]),
c.ELE([c.CHAR("-")]),
c.ELE([]), c.ELE([]),
c.ELE([]), c.ELE([])]))
c.ELE([c.CHAR("-")])]))
end end
final = final[1:end-1] final = final[1:end-1]

View file

@ -33,6 +33,11 @@ interp: the intepreter of the uahgi.
function interp(ast, env, res_box, put_char=true_) function interp(ast, env, res_box, put_char=true_)
#println("INTERP", ast) #println("INTERP", ast)
@match ast begin @match ast begin
c.ID(id) =>
begin
print("ID____", id)
return interp(env[id], env, res_box, true_)
end
c.SEQ([c.ELE([c.ID("def")]), c.SEQ([c.ELE([c.ID("def")]),
c.ELE([c.ID(id)]),val]) => c.ELE([c.ID(id)]),val]) =>
begin begin
@ -87,7 +92,7 @@ function interp(ast, env, res_box, put_char=true_)
font_size) font_size)
ex_in_px = charmetrics.wd ex_in_px = charmetrics.wd
(val, _, _) = interp(val, env, res_box, false_) (val, _, _) = interp(val, env, res_box, false_)
val_ex_in_px = ex_in_px * parse(Int,val) val_ex_in_px = ex_in_px * parse(Float64,val)
return (val_ex_in_px, env, res_box) return (val_ex_in_px, env, res_box)
end end
c.SEQ([c.ELE([c.ID("hglue")]), c.SEQ([c.ELE([c.ID("hglue")]),
@ -96,7 +101,7 @@ function interp(ast, env, res_box, put_char=true_)
(width_evaled, _, _) = interp(width, env, res_box, false_) (width_evaled, _, _) = interp(width, env, res_box, false_)
(stretch_evaled, _, _) = interp(stretch, env, res_box, false_) (stretch_evaled, _, _) = interp(stretch, env, res_box, false_)
push!(res_box.eles[end].eles, push!(res_box.eles[end].eles,
u.HGlue(width_evaled, parse(Int, stretch_evaled)) u.HGlue(width_evaled, parse(Float64, stretch_evaled))
) )
end end
@ -145,7 +150,6 @@ function interp(ast, env, res_box, put_char=true_)
(after_evaled, _, _) = interp(after_item, env, res_box, gen_chbox_) (after_evaled, _, _) = interp(after_item, env, res_box, gen_chbox_)
(orig_evaled, _, _) = interp(orig_item, env, res_box, gen_chbox_) (orig_evaled, _, _) = interp(orig_item, env, res_box, gen_chbox_)
ret = u.Disc(before_evaled, after_evaled, orig_evaled) ret = u.Disc(before_evaled, after_evaled, orig_evaled)
println("RETURN", ret)
push!(res_box.eles[end].eles, ret) push!(res_box.eles[end].eles, ret)
return (ret, env, res_box) return (ret, env, res_box)
@ -169,8 +173,8 @@ function interp(ast, env, res_box, put_char=true_)
# empty item # empty item
[] => return (ast, env, res_box) [] => return (ast, env, res_box)
_ => begin _ => begin
println("不知道") println("unknown token", ast)
val_evaled = "不知道" val_evaled = nothing
return (val_evaled, env, res_box) return (val_evaled, env, res_box)
end end
end end

View file

@ -45,9 +45,7 @@ part = seq | comment | space | newline | id | char
all = (Repeat(part) + Eos()) |> Passes.Classes.PROG all = (Repeat(part) + Eos()) |> Passes.Classes.PROG
function parse(input) function parse(input)
print(input)
ast = parse_one(input, all)[1] ast = parse_one(input, all)[1]
print("\n" * string(ast) * "\n")
#print(parse_one(, Pattern(r".b."))) #print(parse_one(, Pattern(r".b.")))
@ -63,7 +61,6 @@ function parse(input)
end end
new_ast = Passes.Classes.PROG(ast_val) new_ast = Passes.Classes.PROG(ast_val)
print(ast_to_string(new_ast))
return new_ast return new_ast
end end

View file

@ -101,11 +101,11 @@ function check_char_size(char, font_path, font_size, font_index=0)
metrics = glyphrec.metrics metrics = glyphrec.metrics
# horizonal mode # horizonal mode
width = metricToPx(metrics.horiAdvance, font_size) width = metricToPx(metrics.horiAdvance, font_size) * 0.75
#from baseline up to top of the glyph #from baseline up to top of the glyph
height = metricToPx(metrics.horiBearingY, font_size) height = metricToPx(metrics.horiBearingY, font_size) * 0.75
#from baseline down to the bottom of the glyph #from baseline down to the bottom of the glyph
depth = metricToPx(metrics.height - metrics.horiBearingY, font_size) depth = metricToPx(metrics.height - metrics.horiBearingY, font_size) * 0.75
return CharMetrics(height, depth, width) return CharMetrics(height, depth, width)
@ -131,5 +131,15 @@ end
# '安', # '安',
# default_font_path, # default_font_path,
# 20) # 20)
"""generate_pdf"""
function generate_pdf(box_list, file_path)
pdf = createPDF()
page = addPage(pdf)
for ch in box_list
font = load_font(pdf, ch.font_path)
put_text(page, ch.char, font, ch.size, ch.x, ch.y)
end
save_pdf(pdf, file_path)
end
end end

View file

@ -2,10 +2,12 @@ module uahgi
include("parsing.jl") include("parsing.jl")
include("interp.jl") include("interp.jl")
include("arrange.jl") include("arrange.jl")
include("pdfoperating.jl")
using .Interp using .Interp
using .Parsing using .Parsing
using .Arrange using .Arrange
using .PDFOperating
using ArgParse using ArgParse
export ChBox, HGlue export ChBox, HGlue
@ -136,12 +138,9 @@ function main()
output_box_result = Interp.interp_main(ast, default_env, output_box_orig) output_box_result = Interp.interp_main(ast, default_env, output_box_orig)
env = output_box_result[2] env = output_box_result[2]
output_box = output_box_result[3] output_box = output_box_result[3]
print(output_box)
#TODO
arranged = Arrange.arrange(output_box, env) arranged = Arrange.arrange(output_box, env)
unit_positioned = Arrange.position(arranged, env)
generated_pdf = PDFOperating.generate_pdf(unit_positioned, file_path[1:end-3] * ".pdf")
end end
main() main()