Skip to content

Commit

Permalink
feat(harfbuzz): initial Skia integration (revery-ui#927)
Browse files Browse the repository at this point in the history
* Integrated Skia into Harfbuzz

* Fixed dune build errors

* Rename hb_face_from_bytes -> hb_face_from_data

* Add suppression for fontconfig

* Remove duplicate file

* Change CI command for HarfbuzzCLI

* Use String_val in rehb_face_from_bytes

* String_val => Bp_val

* Harfbuzz: return NULL -> CAMLreturn(Val_error("File not found"))

* Remove Cstubs include

* Fix CLI not building on Windows

* Add OS to script
  • Loading branch information
zbaylin committed Jul 1, 2020
1 parent cea8c21 commit d559d44
Show file tree
Hide file tree
Showing 11 changed files with 95 additions and 28 deletions.
4 changes: 2 additions & 2 deletions .ci/esy-build-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ steps:
displayName: 'esy @test run'
- script: esy @examples install
displayName: 'esy @examples install'
- script: esy @examples x HarfbuzzCli
displayName: 'esy @examples x HarfbuzzCli'
- script: esy @examples run-harfbuzz
displayName: 'esy @examples run-harfbuzz'
- script: esy @examples x FontQuery -family=Arial
displayName: 'esy @examples x FontQuery -family=Arial'
- script: esy @examples x SkiaCli
Expand Down
3 changes: 2 additions & 1 deletion examples.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"source": "./package.json",
"scripts": {
"run": "esy x Examples"
"run": "esy @examples x Examples",
"run-harfbuzz": "esy @examples x bash -c run-harfbuzz.sh #{os}"
},
"override": {
"build": ["dune build -p font-manager,harfbuzz,skia,Revery,ReveryExamples -j4"],
Expand Down
13 changes: 9 additions & 4 deletions examples/harfbuzz-cli/HarfbuzzCli.re
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
open Harfbuzz;
open Skia;

Printexc.record_backtrace(true);

Expand All @@ -13,12 +14,15 @@ let getExecutingDirectory = () =>
isNative ? Filename.dirname(Sys.argv[0]) ++ Filename.dir_sep : "";

let run = () => {
let fontPath = getExecutingDirectory() ++ "Roboto-Regular.ttf";
let result = Harfbuzz.hb_new_face(fontPath);
let fontManager = FontManager.makeDefault();
let style = FontStyle.make(400, 5, Upright);
let maybeTypeface =
FontManager.matchFamilyStyle(fontManager, "Arial", style);
let result = maybeTypeface |> Option.map(face => hb_face_from_skia(face));

switch (result) {
| Error(msg) => failwith(msg)
| Ok(font) =>
| Some(Error(msg)) => failwith(msg)
| Some(Ok(font)) =>
let show = ({glyphId, cluster}: hb_shape) =>
Printf.sprintf("GlyphID: %d Cluster: %d", glyphId, cluster);

Expand All @@ -30,6 +34,7 @@ let run = () => {
renderString("abc");
renderString("!=ajga");
renderString("a==>b");
| None => failwith("Font Arial not found!")
};
};

Expand Down
5 changes: 5 additions & 0 deletions examples/harfbuzz-cli/dune
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@
(package ReveryExamples)
(public_name HarfbuzzCli)
(libraries bigarray harfbuzz reason-native-crash-utils.asan))

(install
(section bin)
(package ReveryExamples)
(files run-harfbuzz.sh))
6 changes: 6 additions & 0 deletions examples/harfbuzz-cli/run-harfbuzz.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
set -e

CWD=$(dirname $0)
if [[ $1 == 'windows' ]]; then executable="HarfbuzzCli.exe"; else executable="HarfbuzzCli"; fi

LSAN_OPTIONS=suppressions=lsan.supp $CWD/$executable
2 changes: 2 additions & 0 deletions lsan.supp
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Add suppression for libfontconfig, which has known leaks
leak:*libfontconfig*
2 changes: 1 addition & 1 deletion src/Font/FontCache.re
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ let load: string => result(t, string) =
let assetPath = Environment.getAssetPath(fontName);

let skiaTypeface = Skia.Typeface.makeFromFile(assetPath, 0);
let harfbuzzFace = Harfbuzz.hb_new_face(assetPath);
let harfbuzzFace = Harfbuzz.hb_face_from_path(assetPath);

let metricsCache = MetricsLruHash.create(~initialSize=8, 64);
let shapeCache =
Expand Down
29 changes: 26 additions & 3 deletions src/reason-harfbuzz/Harfbuzz.re
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ type hb_shape = {

module Internal = {
type face;
external hb_new_face: string => result(face, string) = "rehb_new_face";
external hb_face_from_path: string => result(face, string) =
"rehb_face_from_path";
external hb_face_from_data: (string, int) => result(face, string) =
"rehb_face_from_bytes";
external hb_destroy_face: face => unit = "rehb_destroy_face";
external hb_shape: (face, string) => array(hb_shape) = "rehb_shape";
};

type hb_face = {face: Internal.face};

let hb_new_face = str => {
switch (Internal.hb_new_face(str)) {
let hb_face_from_path = str => {
switch (Internal.hb_face_from_path(str)) {
| Error(msg) => Error(msg)
| Ok(face) =>
let ret = {face: face};
Expand All @@ -25,4 +28,24 @@ let hb_new_face = str => {
};
};

let hb_new_face = str => hb_face_from_path(str);

let hb_face_from_skia = sk_typeface => {
let stream = Skia.Typeface.toStream(sk_typeface);
let length = Skia.Stream.getLength(stream);
let data = Skia.Data.makeFromStream(stream, length);
let bytes = Skia.Data.makeString(data);

switch (Internal.hb_face_from_data(bytes, String.length(bytes))) {
| Error(_) as e => e
| Ok(face) =>
let ret = {face: face};

let finalise = ({face}) => Internal.hb_destroy_face(face);

Gc.finalise(finalise, ret);
Ok(ret);
};
};

let hb_shape = ({face}, str) => Internal.hb_shape(face, str);
5 changes: 5 additions & 0 deletions src/reason-harfbuzz/Harfbuzz.rei
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,10 @@ type hb_shape = {
cluster: int,
};

let hb_face_from_path: string => result(hb_face, string);
let hb_face_from_skia: Skia.Typeface.t => result(hb_face, string);

[@ocaml.deprecated "Deprecated in favor of hb_face_from_path"]
let hb_new_face: string => result(hb_face, string);

let hb_shape: (hb_face, string) => array(hb_shape);
1 change: 1 addition & 0 deletions src/reason-harfbuzz/dune
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(library
(name harfbuzz)
(public_name harfbuzz)
(libraries skia ctypes)
(library_flags
(:include flags.sexp))
(c_flags (:include c_flags.sexp))
Expand Down
53 changes: 36 additions & 17 deletions src/reason-harfbuzz/harfbuzz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <hb-ot.h>
#include <hb.h>


/* #define TEST_FONT "E:/FiraCode-Regular.ttf" */
/* #define TEST_FONT "E:/Hasklig-Medium.otf" */
/* #define TEST_FONT "E:/Cassandra.ttf" */
Expand Down Expand Up @@ -36,21 +37,7 @@ CAMLprim value Val_error(const char *szMsg) {

/* Use native open type implementation to load font
https://github.com/harfbuzz/harfbuzz/issues/255 */
hb_font_t *get_font_ot(const char *filename, int size) {
FILE *file = fopen(filename, "rb");

if (!file) {
return NULL;
}

fseek(file, 0, SEEK_END);
unsigned int length = ftell(file);
fseek(file, 0, SEEK_SET);

char *data = (char *)malloc(length);
fread(data, length, 1, file);
fclose(file);

hb_font_t *get_font_ot(char *data, int length, int size) {
hb_blob_t *blob =
hb_blob_create(data, length, HB_MEMORY_MODE_WRITABLE, (void *)data, free);
hb_face_t *face = hb_face_create(blob, 0);
Expand All @@ -76,14 +63,46 @@ CAMLprim value rehb_destroy_face(value vFont) {
CAMLreturn(Val_unit);
}

CAMLprim value rehb_new_face(value vString) {
CAMLprim value rehb_face_from_path(value vString) {
CAMLparam1(vString);
CAMLlocal1(ret);

const char *szFont = String_val(vString);

FILE *file = fopen(szFont, "rb");

if (!file) {
CAMLreturn(Val_error("File does not exist"));
}

fseek(file, 0, SEEK_END);
unsigned int length = ftell(file);
fseek(file, 0, SEEK_SET);

char *data = (char *)malloc(length);
fread(data, length, 1, file);
fclose(file);

hb_font_t *hb_font;
hb_font = get_font_ot(data, length, 12 /*iSize*/ * 64);

if (!hb_font) {
ret = Val_error("Unable to load font");
} else {
ret = Val_success((value)hb_font);
}
CAMLreturn(ret);
}

CAMLprim value rehb_face_from_bytes(value vPtr, value vLength) {
CAMLparam2(vPtr, vLength);
CAMLlocal1(ret);

char *data = Bp_val(vPtr);
int length = Int_val(vLength);

hb_font_t *hb_font;
hb_font = get_font_ot(szFont, 12 /*iSize*/ * 64);
hb_font = get_font_ot(data, length, 12 /*iSize*/ * 64);

if (!hb_font) {
ret = Val_error("Unable to load font");
Expand Down

0 comments on commit d559d44

Please sign in to comment.