From cd4c210e8b22224b7f06e64902f08148b8606280 Mon Sep 17 00:00:00 2001 From: Martin Fouilleul Date: Tue, 5 Nov 2024 11:20:17 +0100 Subject: [PATCH] cache fribidi and harfbuzz builds --- deps/fribidi-commit.txt | 1 + samples/clock/build.sh | 2 +- samples/clock/src/main.c | 23 +++ scripts/dev.py | 272 ++++++++++++++++++++++++++++++++- src/graphics/graphics.h | 19 +++ src/graphics/graphics_common.c | 46 ++++++ 6 files changed, 355 insertions(+), 8 deletions(-) create mode 100644 deps/fribidi-commit.txt diff --git a/deps/fribidi-commit.txt b/deps/fribidi-commit.txt new file mode 100644 index 00000000..179a5c57 --- /dev/null +++ b/deps/fribidi-commit.txt @@ -0,0 +1 @@ +68162babff4f39c4e2dc164a5e825af93bda9983 diff --git a/samples/clock/build.sh b/samples/clock/build.sh index 35301cfc..b3116b54 100755 --- a/samples/clock/build.sh +++ b/samples/clock/build.sh @@ -15,7 +15,7 @@ wasmFlags=(--target=wasm32 \ -I "$ORCA_DIR"/src/ext) # build sample as wasm module and link it with the orca module -clang "${wasmFlags[@]}" -L "$ORCA_DIR"/bin -lorca_wasm -o module.wasm src/main.c +clang -v "${wasmFlags[@]}" -L "$ORCA_DIR"/bin -lorca_wasm -o module.wasm src/main.c # create app directory and copy files into it orca bundle --name Clock --icon icon.png --resource-dir data module.wasm diff --git a/samples/clock/src/main.c b/samples/clock/src/main.c index 181c9684..ecb5cf71 100644 --- a/samples/clock/src/main.c +++ b/samples/clock/src/main.c @@ -7,6 +7,7 @@ **************************************************************************/ #include #include +#include const oc_str8 clockNumberStrings[] = { OC_STR8_LIT("12"), @@ -47,6 +48,28 @@ ORCA_EXPORT void oc_on_init(void) context = oc_canvas_context_create(); font = oc_font_create_from_path(OC_STR8("/segoeui.ttf")); + + //////////////////////////////// + const char* text = "bahrain مصر kuwait"; + + oc_arena_scope scratch = oc_scratch_begin(); + oc_str32 codepoints = oc_utf8_push_to_codepoints(scratch.arena, OC_STR8(text)); + + FriBidiParType baseDir = FRIBIDI_TYPE_LTR_VAL; + FriBidiLevel levels[256]; + + fribidi_log2vis(codepoints.ptr, + codepoints.len, + &baseDir, + 0, 0, 0, + levels); + + for(u64 i = 0; i < codepoints.len; i++) + { + oc_log_info("%i\n", levels[i]); + } + + oc_scratch_end(scratch); } ORCA_EXPORT void oc_on_resize(u32 width, u32 height) diff --git a/scripts/dev.py b/scripts/dev.py index a285ab16..a6281b5b 100644 --- a/scripts/dev.py +++ b/scripts/dev.py @@ -57,6 +57,10 @@ def attach_dev_commands(subparsers): sdk_cmd.add_argument("--release", action="store_true", help="compile in release mode (default is debug)") sdk_cmd.set_defaults(func=dev_shellish(build_sdk)) + fribidi_cmd = subparsers.add_parser("build-fribidi", help="Build Fribidi.") + fribidi_cmd.add_argument("--release", action="store_true", help="compile in release mode (default is debug)") + fribidi_cmd.set_defaults(func=dev_shellish(build_fribidi)) + tool_cmd = subparsers.add_parser("build-tool", help="Build the Orca CLI tool from source.") tool_cmd.add_argument("--version", help="embed a version string in the Orca CLI tool (default is git commit hash)") tool_cmd.add_argument("--release", action="store_true", help="compile Orca CLI tool in release mode (default is debug)") @@ -472,10 +476,238 @@ def build_angle_internal(release, force): print("Done") +#------------------------------------------------------ +# build fribidi +#------------------------------------------------------ + +def check_fribidi(): + print("Checking fribidi cache...", end="") + + with open("deps/fribidi-commit.txt", "r") as f: + FRIBIDI_COMMIT = f.read().strip() + + fribidi_ok = True + + if not os.path.exists("build/fribidi.out/checksums.json"): + print("checksums.json not found") + fribidi_ok = False + else: + with pushd("build/fribidi.out"): + with open("checksums.json", "r") as f: + sums = json.load(f) + + if "commit" not in sums: + print("commit not found in checksums.json") + fribidi_ok = False + elif sums["commit"] != FRIBIDI_COMMIT: + found = sums["commit"] + print(f"Cache does not match commit (expected {FRIBIDI_COMMIT}, found {found})") + fribidi_ok = False + elif "sums" not in sums: + print("sums not found in checksums.json") + fribidi_ok = False + else: + for entry in sums["sums"]: + if "file" not in entry or "sum" not in entry: + print("file or sum not found in cache entry") + print(entry) + fribidi_ok = False + break + + if not os.path.exists(entry["file"]): + f = entry["file"] + print(f"file {f} does not exist in cache") + fribidi_ok = False + break + + fileSum = checksum.filesum(entry["file"]) + if fileSum != entry["sum"]: + f = entry["file"] + s = entry["sum"] + print(f"file {f} does not match checksum (expected {s}, got {fileSum})") + fribidi_ok = False + break + + if not fribidi_ok: + build_fribidi() + else: + print("ok") + + # copy fribidi bin/includes + os.makedirs("src/ext/fribidi/include", exist_ok=True) + shutil.copytree("build/fribidi.out/include", "src/ext/fribidi/include", dirs_exist_ok=True) + + for f in glob.glob("build/fribidi.out/bin/*"): + shutil.copy(f, "build/bin") + + +def build_fribidi(release=False): + print("Building fribidi...") + + with open("deps/fribidi-commit.txt", "r") as f: + FRIBIDI_COMMIT = f.read().strip() + + os.makedirs("build/bin", exist_ok=True) + + # check fribidi repo + with pushd("build"): + if not os.path.exists("fribidi"): + subprocess.run([ + "git", "clone", "--no-tags", "--single-branch", + "https://github.com/fribidi/fribidi.git" + ], check=True) + + with pushd("fribidi"): + subprocess.run([ + "git", "fetch", "--no-tags" + ], check=True) + + subprocess.run([ + "git", "reset", "--hard", FRIBIDI_COMMIT + ], check=True) + + # build native + subprocess.run([ + "./autogen.sh" + ], check=True) + + subprocess.run([ + "./configure" + ], check=True) + + subprocess.run([ + "make" + ], check=True) + + # build wasm + source_files = glob.glob("lib/*.c"); + + subprocess.run([ + "clang", + "--target=wasm32", + "--no-standard-libraries", + "-mbulk-memory", + "-Wl,--no-entry", + "-Wl,--export-dynamic", + "-Wl,--relocatable", + "-DHAVE_CONFIG_H", + "-I.", + "-Ilib", + "-I../../src/orca-libc/include", + *source_files, + "-o", "libfribidi_wasm.a", + ], check=True) + + # copying artifacts + os.makedirs("build/fribidi.out/bin", exist_ok=True) + os.makedirs("build/fribidi.out/include", exist_ok=True) + + sums = [] + + if platform.system() == "Windows": + shutil.copy("build/fribidi/lib/.libs/libfribidi.dll", "build/fribidi.out/bin") + sums.append({ + "file": "bin/libfribidi.dll", + "sum": checksum.filesum("build/fribidi.out/bin/libfribidi.dll") + }) + shutil.copy("build/fribidi/lib/.libs/libfribidi.dll.lib", "build/fribidi.out/bin") + sums.append({ + "file": "bin/libfribidi.dll.lib", + "sum": checksum.filesum("build/fribidi.out/bin/libfribidi.dll.lib") + }) + else: + shutil.copy("build/fribidi/lib/.libs/libfribidi.dylib", "build/fribidi.out/bin") + sums.append({ + "file": "bin/libfribidi.dylib", + "sum": checksum.filesum("build/fribidi.out/bin/libfribidi.dylib") + }) + + shutil.copy("build/fribidi/libfribidi_wasm.a", "build/fribidi.out/bin") + sums.append({ + "file": "bin/libfribidi_wasm.a", + "sum": checksum.filesum("build/fribidi.out/bin/libfribidi_wasm.a") + }) + + for f in glob.iglob("build/fribidi/lib/*.h"): + shutil.copy(f, "build/fribidi.out/include") + sums.append({ + "file": f"include/{os.path.basename(f)}", + "sum": checksum.filesum(f"build/fribidi.out/include/{os.path.basename(f)}") + }) + + # write checksums + with open("build/fribidi.out/checksums.json", "w") as f: + d = { + "commit" : FRIBIDI_COMMIT, + "sums": sums + } + json.dump(d, f) + + #------------------------------------------------------ # build harfbuzz #------------------------------------------------------ +def check_harfbuzz(): + print("Checking harfbuzz cache...", end="") + + with open("deps/harfbuzz-commit.txt", "r") as f: + HARFBUZZ_COMMIT = f.read().strip() + + harfbuzz_ok = True + + if not os.path.exists("build/harfbuzz.out/checksums.json"): + print("checksums.json not found") + harfbuzz_ok = False + else: + with pushd("build/harfbuzz.out"): + with open("checksums.json", "r") as f: + sums = json.load(f) + + if "commit" not in sums: + print("commit not found in checksums.json") + harfbuzz_ok = False + elif sums["commit"] != HARFBUZZ_COMMIT: + found = sums["commit"] + print(f"Cache does not match commit (expected {HARFBUZZ_COMMIT}, found {found})") + harfbuzz_ok = False + elif "sums" not in sums: + print("sums not found in checksums.json") + harfbuzz_ok = False + else: + for entry in sums["sums"]: + if "file" not in entry or "sum" not in entry: + print("file or sum not found in cache entry") + print(entry) + harfbuzz_ok = False + break + + if not os.path.exists(entry["file"]): + f = entry["file"] + print(f"file {f} does not exist in cache") + harfbuzz_ok = False + break + + fileSum = checksum.filesum(entry["file"]) + if fileSum != entry["sum"]: + f = entry["file"] + s = entry["sum"] + print(f"file {f} does not match checksum (expected {s}, got {fileSum})") + harfbuzz_ok = False + break + + if not harfbuzz_ok: + build_harfbuzz() + else: + print("ok") + + # copy harfbuzz bin/includes + os.makedirs("src/ext/harfbuzz/include", exist_ok=True) + shutil.copytree("build/harfbuzz.out/include", "src/ext/harfbuzz/include", dirs_exist_ok=True) + + for f in glob.glob("build/harfbuzz.out/bin/*"): + shutil.copy(f, "build/bin") + def build_harfbuzz(): print("Building harfbuzz...") @@ -519,15 +751,40 @@ def build_harfbuzz(): ], check=True) - # copying artifacts - shutil.copy(f"build/harfbuzz/{libname}", "build/bin") + # copying artifacts and computing checksums + sums = [] + + os.makedirs("build/harfbuzz.out/bin", exist_ok=True) + os.makedirs("build/harfbuzz.out/include", exist_ok=True) + + shutil.copy(f"build/harfbuzz/{libname}", "build/harfbuzz.out/bin") + sums.append({ + "file": f"bin/{libname}", + "sum": checksum.filesum(f"build/harfbuzz.out/bin/{libname}") + }) if platform.system() == "Windows": - shutil.copy("build/harfbuzz/libharfbuzz.dll.lib", "build/bin") + shutil.copy("build/harfbuzz/libharfbuzz.dll.lib", "build/harfbuzz.out/bin") + sums.append({ + "file": f"bin/libharfbuzz.dll.lib", + "sum": checskum.filesum(f"build/harfbuzz.out/bin/libharfbuzz.dll.lib") + }) - os.makedirs("src/ext/harfbuzz/include", exist_ok=True) for f in glob.iglob("build/harfbuzz/src/*.h"): - shutil.copy(f, "src/ext/harfbuzz/include") + shutil.copy(f, "build/harfbuzz.out/include") + sums.append({ + "file": f"include/{os.path.basename(f)}", + "sum": checksum.filesum(f"build/harfbuzz.out/include/{os.path.basename(f)}") + }) + + # write checksums + with open("build/harfbuzz.out/checksums.json", "w") as f: + d = { + "commit" : HARFBUZZ_COMMIT, + "sums": sums + } + + json.dump(d, f) #------------------------------------------------------ @@ -752,7 +1009,8 @@ def build_platform_layer_internal(release): # build harfbuzz #TODO: skip if already built - build_harfbuzz() + check_harfbuzz() + check_fribidi() print("Building Orca platform layer...") @@ -1032,7 +1290,7 @@ def build_sdk_internal(release): subprocess.run([ clang, *flags, *includes, "-o", "build/bin/liborca_wasm.a", - "src/orca.c" + "src/orca.c", "build/bin/libfribidi_wasm.a" ], check=True) #------------------------------------------------------ diff --git a/src/graphics/graphics.h b/src/graphics/graphics.h index d767297b..3935c0d7 100644 --- a/src/graphics/graphics.h +++ b/src/graphics/graphics.h @@ -150,6 +150,15 @@ typedef struct oc_text_shape_settings typedef struct oc_glyph_run oc_glyph_run; +typedef struct oc_text_attributes +{ + oc_font font; + f32 fontSize; + oc_color color; +} oc_text_attributes; + +typedef struct oc_text_line oc_text_line; + //------------------------------------------------------------------------------------------ //SECTION: color helpers //------------------------------------------------------------------------------------------ @@ -341,6 +350,16 @@ ORCA_API void oc_text_draw_run(oc_glyph_run* run, f32 fontSize); ORCA_API void oc_text_draw_utf8(oc_str8 text, oc_font font, f32 fontSize); ORCA_API void oc_text_draw_utf32(oc_str32 codepoints, oc_font font, f32 fontSize); +// text lines +oc_text_line* oc_text_line_from_utf8(oc_arena* arena, oc_str8 string, oc_text_attributes* attributes); +oc_text_line* oc_text_line_from_utf32(oc_arena* arena, oc_str32 string, oc_text_attributes* attributes); + +oc_text_metrics oc_text_line_get_metrics(oc_text_line* line); +u64 oc_text_line_codepoint_index_for_position(oc_text_line* line, oc_vec2 position); +oc_vec2 oc_text_line_position_for_codepoint_index(oc_text_line* line, u64 index); + +void oc_text_line_draw(oc_text_line* line); + //------------------------------------------------------------------------------------------ //SECTION: shapes helpers //------------------------------------------------------------------------------------------ diff --git a/src/graphics/graphics_common.c b/src/graphics/graphics_common.c index 03a56c14..c2d49b9f 100644 --- a/src/graphics/graphics_common.c +++ b/src/graphics/graphics_common.c @@ -1304,6 +1304,52 @@ void oc_text_draw_utf8(oc_str8 text, oc_font font, f32 fontSize) oc_scratch_end(scratch); } +///////////////////// + +typedef struct oc_text_line +{ + u64 runCount; + oc_glyph_run* runs; + oc_text_attributes* attributes; + oc_vec2* offsets; +} oc_text_line; + +oc_text_line* oc_text_line_from_utf32(oc_arena* arena, oc_str32 codepoints, oc_text_attributes* attributes) +{ + oc_arena_scope scratch = oc_scratch_begin_next(arena); + + //TODO: split codepoints into directional runs + oc_str32_list list = { 0 }; + for(u64 i = 0; i < codepoints.len; i++) + { + } + + //TODO: further split based on language / script, reversing RTL and BTT runs + + //TODO: Shape runs + + //TODO: compute runs offsets + + oc_scratch_end(scratch); +} + +oc_text_line* oc_text_line_from_utf8(oc_arena* arena, oc_str8 string, oc_text_attributes* attributes) +{ + oc_arena_scope scratch = oc_scratch_begin_next(arena); + oc_str32 codepoints = oc_utf8_push_to_codepoints(scratch.arena, string); + oc_text_line* line = oc_text_line_from_utf32(arena, codepoints, attributes); + oc_scratch_end(scratch); + return (line); +} + +/* +oc_text_metrics oc_text_line_get_metrics(oc_text_line* line); +u64 oc_text_line_codepoint_index_for_position(oc_text_line* line, oc_vec2 position); +oc_vec2 oc_text_line_position_for_codepoint_index(oc_text_line* line, u64 index); + +void oc_text_line_draw(oc_text_line* line); +*/ + //------------------------------------------------------------------------------------------ //NOTE(martin): clear/fill/stroke //------------------------------------------------------------------------------------------