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
- 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}
{@def|@font|{@quote|FreeSans|AR PL UKai TW}}
{@def|@fontsize|12}
{@def|@linewidth|200} %in px%
{@def|@spacing|{@hglue|{@ex|1}|2}}
{@def|@cjk_spacing|{@hglue|{@ex|0}|0.001}}
11111 222 3333333 444444 555555 666666 77777 88888 99999 000000 11111 22222 33333 55555
{@lang|en}%
%{@def|@font|{@quote|FreeSans|AR PL UKai TW}}%
%{@def|@fontsize|12}%
%{@def|@linewidth|200} %in px
%{@def|@spacing|{@hglue|{@ex|1}|2}}%
%{@def|@cjk_spacing|{@hglue|{@ex|0}|0.001}}%
%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
# the cost of make ith to jth char a line
function cost(items, i, j, linewidth)
total_cost_table = Dict()
# the cost of make ith to jth item a line
function cost(items, i, j, linewidth, last_of_queue=false)
slice = items[i:j]
cost_list = map(x -> item_cost(x), slice)
sum_of_cost_list = reduce((x, y)-> x+y, cost_list)
last = items[j]
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
return Inf
else
return linewidth - sum_of_cost_list
return (linewidth - sum_of_cost_list) ^ 3
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
return i.wd
elseif typeof(i) == u.ChBox
return i.wd
elseif typeof(i) == u.Disc
return item_width(i.orig)
else
return 0
end
end
function arrange(vbox, env)
result_vbox_inner = []
result_vbox_inner = [u.HBox([],nothing,nothing,nothing,nothing,nothing)]
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
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

View file

@ -89,9 +89,9 @@ function match_char(ast_item, patterns)
for i in splitted
push!(final, c.CHAR(i))
push!(final, c.SEQ([c.ELE([c.ID("disc")]),
c.ELE([c.CHAR("-")]),
c.ELE([]),
c.ELE([]),
c.ELE([c.CHAR("-")])]))
c.ELE([])]))
end
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_)
#println("INTERP", ast)
@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.ELE([c.ID(id)]),val]) =>
begin
@ -87,7 +92,7 @@ function interp(ast, env, res_box, put_char=true_)
font_size)
ex_in_px = charmetrics.wd
(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)
end
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_)
(stretch_evaled, _, _) = interp(stretch, env, res_box, false_)
push!(res_box.eles[end].eles,
u.HGlue(width_evaled, parse(Int, stretch_evaled))
u.HGlue(width_evaled, parse(Float64, stretch_evaled))
)
end
@ -145,7 +150,6 @@ function interp(ast, env, res_box, put_char=true_)
(after_evaled, _, _) = interp(after_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)
println("RETURN", ret)
push!(res_box.eles[end].eles, ret)
return (ret, env, res_box)
@ -169,8 +173,8 @@ function interp(ast, env, res_box, put_char=true_)
# empty item
[] => return (ast, env, res_box)
_ => begin
println("不知道")
val_evaled = "不知道"
println("unknown token", ast)
val_evaled = nothing
return (val_evaled, env, res_box)
end
end

View file

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

View file

@ -101,11 +101,11 @@ function check_char_size(char, font_path, font_size, font_index=0)
metrics = glyphrec.metrics
# horizonal mode
width = metricToPx(metrics.horiAdvance, font_size)
width = metricToPx(metrics.horiAdvance, font_size) * 0.75
#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
depth = metricToPx(metrics.height - metrics.horiBearingY, font_size)
depth = metricToPx(metrics.height - metrics.horiBearingY, font_size) * 0.75
return CharMetrics(height, depth, width)
@ -131,5 +131,15 @@ end
# '安',
# default_font_path,
# 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

View file

@ -2,10 +2,12 @@ module uahgi
include("parsing.jl")
include("interp.jl")
include("arrange.jl")
include("pdfoperating.jl")
using .Interp
using .Parsing
using .Arrange
using .PDFOperating
using ArgParse
export ChBox, HGlue
@ -136,12 +138,9 @@ function main()
output_box_result = Interp.interp_main(ast, default_env, output_box_orig)
env = output_box_result[2]
output_box = output_box_result[3]
print(output_box)
#TODO
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
main()