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