xref: /aosp_15_r20/external/harfbuzz_ng/src/justify.py (revision 2d1272b857b1f7575e6e246373e1cb218663db8a)
1*2d1272b8SAndroid Build Coastguard Workerimport gi
2*2d1272b8SAndroid Build Coastguard Worker
3*2d1272b8SAndroid Build Coastguard Workergi.require_version("Gtk", "3.0")
4*2d1272b8SAndroid Build Coastguard Workerfrom gi.repository import Gtk, HarfBuzz as hb
5*2d1272b8SAndroid Build Coastguard Worker
6*2d1272b8SAndroid Build Coastguard Worker
7*2d1272b8SAndroid Build Coastguard WorkerPOOL = {}
8*2d1272b8SAndroid Build Coastguard Worker
9*2d1272b8SAndroid Build Coastguard Worker
10*2d1272b8SAndroid Build Coastguard Workerdef move_to_f(funcs, draw_data, st, to_x, to_y, user_data):
11*2d1272b8SAndroid Build Coastguard Worker    context = POOL[draw_data]
12*2d1272b8SAndroid Build Coastguard Worker    context.move_to(to_x, to_y)
13*2d1272b8SAndroid Build Coastguard Worker
14*2d1272b8SAndroid Build Coastguard Worker
15*2d1272b8SAndroid Build Coastguard Workerdef line_to_f(funcs, draw_data, st, to_x, to_y, user_data):
16*2d1272b8SAndroid Build Coastguard Worker    context = POOL[draw_data]
17*2d1272b8SAndroid Build Coastguard Worker    context.line_to(to_x, to_y)
18*2d1272b8SAndroid Build Coastguard Worker
19*2d1272b8SAndroid Build Coastguard Worker
20*2d1272b8SAndroid Build Coastguard Workerdef cubic_to_f(
21*2d1272b8SAndroid Build Coastguard Worker    funcs,
22*2d1272b8SAndroid Build Coastguard Worker    draw_data,
23*2d1272b8SAndroid Build Coastguard Worker    st,
24*2d1272b8SAndroid Build Coastguard Worker    control1_x,
25*2d1272b8SAndroid Build Coastguard Worker    control1_y,
26*2d1272b8SAndroid Build Coastguard Worker    control2_x,
27*2d1272b8SAndroid Build Coastguard Worker    control2_y,
28*2d1272b8SAndroid Build Coastguard Worker    to_x,
29*2d1272b8SAndroid Build Coastguard Worker    to_y,
30*2d1272b8SAndroid Build Coastguard Worker    user_data,
31*2d1272b8SAndroid Build Coastguard Worker):
32*2d1272b8SAndroid Build Coastguard Worker    context = POOL[draw_data]
33*2d1272b8SAndroid Build Coastguard Worker    context.curve_to(control1_x, control1_y, control2_x, control2_y, to_x, to_y)
34*2d1272b8SAndroid Build Coastguard Worker
35*2d1272b8SAndroid Build Coastguard Worker
36*2d1272b8SAndroid Build Coastguard Workerdef close_path_f(funcs, draw_data, st, user_data):
37*2d1272b8SAndroid Build Coastguard Worker    context = POOL[draw_data]
38*2d1272b8SAndroid Build Coastguard Worker    context.close_path()
39*2d1272b8SAndroid Build Coastguard Worker
40*2d1272b8SAndroid Build Coastguard Worker
41*2d1272b8SAndroid Build Coastguard WorkerDFUNCS = hb.draw_funcs_create()
42*2d1272b8SAndroid Build Coastguard Workerhb.draw_funcs_set_move_to_func(DFUNCS, move_to_f, None)
43*2d1272b8SAndroid Build Coastguard Workerhb.draw_funcs_set_line_to_func(DFUNCS, line_to_f, None)
44*2d1272b8SAndroid Build Coastguard Workerhb.draw_funcs_set_cubic_to_func(DFUNCS, cubic_to_f, None)
45*2d1272b8SAndroid Build Coastguard Workerhb.draw_funcs_set_close_path_func(DFUNCS, close_path_f, None)
46*2d1272b8SAndroid Build Coastguard Worker
47*2d1272b8SAndroid Build Coastguard Worker
48*2d1272b8SAndroid Build Coastguard Workerdef push_transform_f(funcs, paint_data, xx, yx, xy, yy, dx, dy, user_data):
49*2d1272b8SAndroid Build Coastguard Worker    raise NotImplementedError
50*2d1272b8SAndroid Build Coastguard Worker
51*2d1272b8SAndroid Build Coastguard Worker
52*2d1272b8SAndroid Build Coastguard Workerdef pop_transform_f(funcs, paint_data, user_data):
53*2d1272b8SAndroid Build Coastguard Worker    raise NotImplementedError
54*2d1272b8SAndroid Build Coastguard Worker
55*2d1272b8SAndroid Build Coastguard Worker
56*2d1272b8SAndroid Build Coastguard Workerdef color_f(funcs, paint_data, is_foreground, color, user_data):
57*2d1272b8SAndroid Build Coastguard Worker    context = POOL[paint_data]
58*2d1272b8SAndroid Build Coastguard Worker    r = hb.color_get_red(color) / 255
59*2d1272b8SAndroid Build Coastguard Worker    g = hb.color_get_green(color) / 255
60*2d1272b8SAndroid Build Coastguard Worker    b = hb.color_get_blue(color) / 255
61*2d1272b8SAndroid Build Coastguard Worker    a = hb.color_get_alpha(color) / 255
62*2d1272b8SAndroid Build Coastguard Worker    context.set_source_rgba(r, g, b, a)
63*2d1272b8SAndroid Build Coastguard Worker    context.paint()
64*2d1272b8SAndroid Build Coastguard Worker
65*2d1272b8SAndroid Build Coastguard Worker
66*2d1272b8SAndroid Build Coastguard Workerdef push_clip_rectangle_f(funcs, paint_data, xmin, ymin, xmax, ymax, user_data):
67*2d1272b8SAndroid Build Coastguard Worker    context = POOL[paint_data]
68*2d1272b8SAndroid Build Coastguard Worker    context.save()
69*2d1272b8SAndroid Build Coastguard Worker    context.rectangle(xmin, ymin, xmax, ymax)
70*2d1272b8SAndroid Build Coastguard Worker    context.clip()
71*2d1272b8SAndroid Build Coastguard Worker
72*2d1272b8SAndroid Build Coastguard Worker
73*2d1272b8SAndroid Build Coastguard Workerdef push_clip_glyph_f(funcs, paint_data, glyph, font, user_data):
74*2d1272b8SAndroid Build Coastguard Worker    context = POOL[paint_data]
75*2d1272b8SAndroid Build Coastguard Worker    context.save()
76*2d1272b8SAndroid Build Coastguard Worker    context.new_path()
77*2d1272b8SAndroid Build Coastguard Worker    hb.font_draw_glyph(font, glyph, DFUNCS, paint_data)
78*2d1272b8SAndroid Build Coastguard Worker    context.close_path()
79*2d1272b8SAndroid Build Coastguard Worker    context.clip()
80*2d1272b8SAndroid Build Coastguard Worker
81*2d1272b8SAndroid Build Coastguard Worker
82*2d1272b8SAndroid Build Coastguard Workerdef pop_clip_f(funcs, paint_data, user_data):
83*2d1272b8SAndroid Build Coastguard Worker    context = POOL[paint_data]
84*2d1272b8SAndroid Build Coastguard Worker    context.restore()
85*2d1272b8SAndroid Build Coastguard Worker
86*2d1272b8SAndroid Build Coastguard Worker
87*2d1272b8SAndroid Build Coastguard Workerdef push_group_f(funcs, paint_data, user_data):
88*2d1272b8SAndroid Build Coastguard Worker    raise NotImplementedError
89*2d1272b8SAndroid Build Coastguard Worker
90*2d1272b8SAndroid Build Coastguard Worker
91*2d1272b8SAndroid Build Coastguard Workerdef pop_group_f(funcs, paint_data, mode, user_data):
92*2d1272b8SAndroid Build Coastguard Worker    raise NotImplementedError
93*2d1272b8SAndroid Build Coastguard Worker
94*2d1272b8SAndroid Build Coastguard Worker
95*2d1272b8SAndroid Build Coastguard WorkerPFUNCS = hb.paint_funcs_create()
96*2d1272b8SAndroid Build Coastguard Workerhb.paint_funcs_set_push_transform_func(PFUNCS, push_transform_f, None)
97*2d1272b8SAndroid Build Coastguard Workerhb.paint_funcs_set_pop_transform_func(PFUNCS, pop_transform_f, None)
98*2d1272b8SAndroid Build Coastguard Workerhb.paint_funcs_set_color_func(PFUNCS, color_f, None)
99*2d1272b8SAndroid Build Coastguard Workerhb.paint_funcs_set_push_clip_glyph_func(PFUNCS, push_clip_glyph_f, None)
100*2d1272b8SAndroid Build Coastguard Workerhb.paint_funcs_set_push_clip_rectangle_func(PFUNCS, push_clip_rectangle_f, None)
101*2d1272b8SAndroid Build Coastguard Workerhb.paint_funcs_set_pop_clip_func(PFUNCS, pop_clip_f, None)
102*2d1272b8SAndroid Build Coastguard Workerhb.paint_funcs_set_push_group_func(PFUNCS, push_group_f, None)
103*2d1272b8SAndroid Build Coastguard Workerhb.paint_funcs_set_pop_group_func(PFUNCS, pop_group_f, None)
104*2d1272b8SAndroid Build Coastguard Worker
105*2d1272b8SAndroid Build Coastguard Worker
106*2d1272b8SAndroid Build Coastguard Workerdef makebuffer(words):
107*2d1272b8SAndroid Build Coastguard Worker    buf = hb.buffer_create()
108*2d1272b8SAndroid Build Coastguard Worker
109*2d1272b8SAndroid Build Coastguard Worker    text = " ".join(words)
110*2d1272b8SAndroid Build Coastguard Worker    hb.buffer_add_codepoints(buf, [ord(c) for c in text], 0, len(text))
111*2d1272b8SAndroid Build Coastguard Worker
112*2d1272b8SAndroid Build Coastguard Worker    hb.buffer_guess_segment_properties(buf)
113*2d1272b8SAndroid Build Coastguard Worker
114*2d1272b8SAndroid Build Coastguard Worker    return buf
115*2d1272b8SAndroid Build Coastguard Worker
116*2d1272b8SAndroid Build Coastguard Worker
117*2d1272b8SAndroid Build Coastguard Workerdef justify(face, words, advance, target_advance):
118*2d1272b8SAndroid Build Coastguard Worker    font = hb.font_create(face)
119*2d1272b8SAndroid Build Coastguard Worker    buf = makebuffer(words)
120*2d1272b8SAndroid Build Coastguard Worker
121*2d1272b8SAndroid Build Coastguard Worker    wiggle = 5
122*2d1272b8SAndroid Build Coastguard Worker    shrink = target_advance - wiggle < advance
123*2d1272b8SAndroid Build Coastguard Worker    expand = target_advance + wiggle > advance
124*2d1272b8SAndroid Build Coastguard Worker
125*2d1272b8SAndroid Build Coastguard Worker    ret, advance, tag, value = hb.shape_justify(
126*2d1272b8SAndroid Build Coastguard Worker        font,
127*2d1272b8SAndroid Build Coastguard Worker        buf,
128*2d1272b8SAndroid Build Coastguard Worker        None,
129*2d1272b8SAndroid Build Coastguard Worker        None,
130*2d1272b8SAndroid Build Coastguard Worker        target_advance,
131*2d1272b8SAndroid Build Coastguard Worker        target_advance,
132*2d1272b8SAndroid Build Coastguard Worker        advance,
133*2d1272b8SAndroid Build Coastguard Worker    )
134*2d1272b8SAndroid Build Coastguard Worker
135*2d1272b8SAndroid Build Coastguard Worker    if not ret:
136*2d1272b8SAndroid Build Coastguard Worker        return False, buf, None
137*2d1272b8SAndroid Build Coastguard Worker
138*2d1272b8SAndroid Build Coastguard Worker    if tag:
139*2d1272b8SAndroid Build Coastguard Worker        variation = hb.variation_t()
140*2d1272b8SAndroid Build Coastguard Worker        variation.tag = tag
141*2d1272b8SAndroid Build Coastguard Worker        variation.value = value
142*2d1272b8SAndroid Build Coastguard Worker    else:
143*2d1272b8SAndroid Build Coastguard Worker        variation = None
144*2d1272b8SAndroid Build Coastguard Worker
145*2d1272b8SAndroid Build Coastguard Worker    if shrink and advance > target_advance + wiggle:
146*2d1272b8SAndroid Build Coastguard Worker        return False, buf, variation
147*2d1272b8SAndroid Build Coastguard Worker    if expand and advance < target_advance - wiggle:
148*2d1272b8SAndroid Build Coastguard Worker        return False, buf, variation
149*2d1272b8SAndroid Build Coastguard Worker
150*2d1272b8SAndroid Build Coastguard Worker    return True, buf, variation
151*2d1272b8SAndroid Build Coastguard Worker
152*2d1272b8SAndroid Build Coastguard Worker
153*2d1272b8SAndroid Build Coastguard Workerdef shape(face, words):
154*2d1272b8SAndroid Build Coastguard Worker    font = hb.font_create(face)
155*2d1272b8SAndroid Build Coastguard Worker    buf = makebuffer(words)
156*2d1272b8SAndroid Build Coastguard Worker    hb.shape(font, buf)
157*2d1272b8SAndroid Build Coastguard Worker    positions = hb.buffer_get_glyph_positions(buf)
158*2d1272b8SAndroid Build Coastguard Worker    advance = sum(p.x_advance for p in positions)
159*2d1272b8SAndroid Build Coastguard Worker    return buf, advance
160*2d1272b8SAndroid Build Coastguard Worker
161*2d1272b8SAndroid Build Coastguard Worker
162*2d1272b8SAndroid Build Coastguard Workerdef typeset(face, text, target_advance):
163*2d1272b8SAndroid Build Coastguard Worker    lines = []
164*2d1272b8SAndroid Build Coastguard Worker    words = []
165*2d1272b8SAndroid Build Coastguard Worker    for word in text.split():
166*2d1272b8SAndroid Build Coastguard Worker        words.append(word)
167*2d1272b8SAndroid Build Coastguard Worker        buf, advance = shape(face, words)
168*2d1272b8SAndroid Build Coastguard Worker        if advance > target_advance:
169*2d1272b8SAndroid Build Coastguard Worker            # Shrink
170*2d1272b8SAndroid Build Coastguard Worker            ret, buf, variation = justify(face, words, advance, target_advance)
171*2d1272b8SAndroid Build Coastguard Worker            if ret:
172*2d1272b8SAndroid Build Coastguard Worker                lines.append((buf, variation))
173*2d1272b8SAndroid Build Coastguard Worker                words = []
174*2d1272b8SAndroid Build Coastguard Worker            # If if fails, pop the last word and shrink, and hope for the best.
175*2d1272b8SAndroid Build Coastguard Worker            # A too short line is better than too long.
176*2d1272b8SAndroid Build Coastguard Worker            elif len(words) > 1:
177*2d1272b8SAndroid Build Coastguard Worker                words.pop()
178*2d1272b8SAndroid Build Coastguard Worker                _, buf, variation = justify(face, words, advance, target_advance)
179*2d1272b8SAndroid Build Coastguard Worker                lines.append((buf, variation))
180*2d1272b8SAndroid Build Coastguard Worker                words = [word]
181*2d1272b8SAndroid Build Coastguard Worker            # But if it is one word, meh.
182*2d1272b8SAndroid Build Coastguard Worker            else:
183*2d1272b8SAndroid Build Coastguard Worker                lines.append((buf, variation))
184*2d1272b8SAndroid Build Coastguard Worker                words = []
185*2d1272b8SAndroid Build Coastguard Worker
186*2d1272b8SAndroid Build Coastguard Worker    # Justify last line
187*2d1272b8SAndroid Build Coastguard Worker    if words:
188*2d1272b8SAndroid Build Coastguard Worker        _, buf, variation = justify(face, words, advance, target_advance)
189*2d1272b8SAndroid Build Coastguard Worker        lines.append((buf, variation))
190*2d1272b8SAndroid Build Coastguard Worker
191*2d1272b8SAndroid Build Coastguard Worker    return lines
192*2d1272b8SAndroid Build Coastguard Worker
193*2d1272b8SAndroid Build Coastguard Worker
194*2d1272b8SAndroid Build Coastguard Workerdef render(face, text, context, width, height, fontsize):
195*2d1272b8SAndroid Build Coastguard Worker    font = hb.font_create(face)
196*2d1272b8SAndroid Build Coastguard Worker
197*2d1272b8SAndroid Build Coastguard Worker    margin = fontsize * 2
198*2d1272b8SAndroid Build Coastguard Worker    scale = fontsize / hb.face_get_upem(face)
199*2d1272b8SAndroid Build Coastguard Worker    target_advance = (width - (margin * 2)) / scale
200*2d1272b8SAndroid Build Coastguard Worker
201*2d1272b8SAndroid Build Coastguard Worker    lines = typeset(face, text, target_advance)
202*2d1272b8SAndroid Build Coastguard Worker
203*2d1272b8SAndroid Build Coastguard Worker    _, extents = hb.font_get_h_extents(font)
204*2d1272b8SAndroid Build Coastguard Worker    lineheight = extents.ascender - extents.descender + extents.line_gap
205*2d1272b8SAndroid Build Coastguard Worker    lineheight *= scale
206*2d1272b8SAndroid Build Coastguard Worker
207*2d1272b8SAndroid Build Coastguard Worker    context.save()
208*2d1272b8SAndroid Build Coastguard Worker    context.translate(0, margin)
209*2d1272b8SAndroid Build Coastguard Worker    context.set_font_size(12)
210*2d1272b8SAndroid Build Coastguard Worker    context.set_source_rgb(1, 0, 0)
211*2d1272b8SAndroid Build Coastguard Worker    for buf, variation in lines:
212*2d1272b8SAndroid Build Coastguard Worker        rtl = hb.buffer_get_direction(buf) == hb.direction_t.RTL
213*2d1272b8SAndroid Build Coastguard Worker        if rtl:
214*2d1272b8SAndroid Build Coastguard Worker            hb.buffer_reverse(buf)
215*2d1272b8SAndroid Build Coastguard Worker        infos = hb.buffer_get_glyph_infos(buf)
216*2d1272b8SAndroid Build Coastguard Worker        positions = hb.buffer_get_glyph_positions(buf)
217*2d1272b8SAndroid Build Coastguard Worker        advance = sum(p.x_advance for p in positions)
218*2d1272b8SAndroid Build Coastguard Worker
219*2d1272b8SAndroid Build Coastguard Worker        context.translate(0, lineheight)
220*2d1272b8SAndroid Build Coastguard Worker        context.save()
221*2d1272b8SAndroid Build Coastguard Worker
222*2d1272b8SAndroid Build Coastguard Worker        context.save()
223*2d1272b8SAndroid Build Coastguard Worker        context.move_to(0, -20)
224*2d1272b8SAndroid Build Coastguard Worker        if variation:
225*2d1272b8SAndroid Build Coastguard Worker            tag = hb.tag_to_string(variation.tag).decode("ascii")
226*2d1272b8SAndroid Build Coastguard Worker            context.show_text(f" {tag}={variation.value:g}")
227*2d1272b8SAndroid Build Coastguard Worker        context.move_to(0, 0)
228*2d1272b8SAndroid Build Coastguard Worker        context.show_text(f" {advance:g}/{target_advance:g}")
229*2d1272b8SAndroid Build Coastguard Worker        context.restore()
230*2d1272b8SAndroid Build Coastguard Worker
231*2d1272b8SAndroid Build Coastguard Worker        if variation:
232*2d1272b8SAndroid Build Coastguard Worker            hb.font_set_variations(font, [variation])
233*2d1272b8SAndroid Build Coastguard Worker
234*2d1272b8SAndroid Build Coastguard Worker        context.translate(margin, 0)
235*2d1272b8SAndroid Build Coastguard Worker        context.scale(scale, -scale)
236*2d1272b8SAndroid Build Coastguard Worker
237*2d1272b8SAndroid Build Coastguard Worker        if rtl:
238*2d1272b8SAndroid Build Coastguard Worker            context.translate(target_advance, 0)
239*2d1272b8SAndroid Build Coastguard Worker
240*2d1272b8SAndroid Build Coastguard Worker        for info, pos in zip(infos, positions):
241*2d1272b8SAndroid Build Coastguard Worker            if rtl:
242*2d1272b8SAndroid Build Coastguard Worker                context.translate(-pos.x_advance, pos.y_advance)
243*2d1272b8SAndroid Build Coastguard Worker            context.save()
244*2d1272b8SAndroid Build Coastguard Worker            context.translate(pos.x_offset, pos.y_offset)
245*2d1272b8SAndroid Build Coastguard Worker            hb.font_paint_glyph(font, info.codepoint, PFUNCS, id(context), 0, 0x0000FF)
246*2d1272b8SAndroid Build Coastguard Worker            context.restore()
247*2d1272b8SAndroid Build Coastguard Worker            if not rtl:
248*2d1272b8SAndroid Build Coastguard Worker                context.translate(+pos.x_advance, pos.y_advance)
249*2d1272b8SAndroid Build Coastguard Worker
250*2d1272b8SAndroid Build Coastguard Worker        context.restore()
251*2d1272b8SAndroid Build Coastguard Worker    context.restore()
252*2d1272b8SAndroid Build Coastguard Worker
253*2d1272b8SAndroid Build Coastguard Worker
254*2d1272b8SAndroid Build Coastguard Workerdef main(fontpath, textpath):
255*2d1272b8SAndroid Build Coastguard Worker    fontsize = 70
256*2d1272b8SAndroid Build Coastguard Worker
257*2d1272b8SAndroid Build Coastguard Worker    blob = hb.blob_create_from_file(fontpath)
258*2d1272b8SAndroid Build Coastguard Worker    face = hb.face_create(blob, 0)
259*2d1272b8SAndroid Build Coastguard Worker
260*2d1272b8SAndroid Build Coastguard Worker    with open(textpath) as f:
261*2d1272b8SAndroid Build Coastguard Worker        text = f.read()
262*2d1272b8SAndroid Build Coastguard Worker
263*2d1272b8SAndroid Build Coastguard Worker    def on_draw(da, context):
264*2d1272b8SAndroid Build Coastguard Worker        alloc = da.get_allocation()
265*2d1272b8SAndroid Build Coastguard Worker        POOL[id(context)] = context
266*2d1272b8SAndroid Build Coastguard Worker        render(face, text, context, alloc.width, alloc.height, fontsize)
267*2d1272b8SAndroid Build Coastguard Worker        del POOL[id(context)]
268*2d1272b8SAndroid Build Coastguard Worker
269*2d1272b8SAndroid Build Coastguard Worker    drawingarea = Gtk.DrawingArea()
270*2d1272b8SAndroid Build Coastguard Worker    drawingarea.connect("draw", on_draw)
271*2d1272b8SAndroid Build Coastguard Worker
272*2d1272b8SAndroid Build Coastguard Worker    win = Gtk.Window()
273*2d1272b8SAndroid Build Coastguard Worker    win.connect("destroy", Gtk.main_quit)
274*2d1272b8SAndroid Build Coastguard Worker    win.set_default_size(1000, 700)
275*2d1272b8SAndroid Build Coastguard Worker    win.add(drawingarea)
276*2d1272b8SAndroid Build Coastguard Worker
277*2d1272b8SAndroid Build Coastguard Worker    win.show_all()
278*2d1272b8SAndroid Build Coastguard Worker    Gtk.main()
279*2d1272b8SAndroid Build Coastguard Worker
280*2d1272b8SAndroid Build Coastguard Worker
281*2d1272b8SAndroid Build Coastguard Workerif __name__ == "__main__":
282*2d1272b8SAndroid Build Coastguard Worker    import argparse
283*2d1272b8SAndroid Build Coastguard Worker
284*2d1272b8SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(description="HarfBuzz justification demo.")
285*2d1272b8SAndroid Build Coastguard Worker    parser.add_argument("fontfile", help="font file")
286*2d1272b8SAndroid Build Coastguard Worker    parser.add_argument("textfile", help="text")
287*2d1272b8SAndroid Build Coastguard Worker    args = parser.parse_args()
288*2d1272b8SAndroid Build Coastguard Worker    main(args.fontfile, args.textfile)
289