text: #include "font_metrics.h"
  #include "types.h"
  #include "caches.h"
  #include "utils.h"
  
  #include <cpp11/named_arg.hpp>
  #include <cpp11/logicals.hpp>
  #include <cpp11/list.hpp>
  
  using list_t = cpp11::list;
  using list_w = cpp11::writable::list;
  using data_frame_w = cpp11::writable::data_frame;
  using strings_t = cpp11::strings;
  using strings_w = cpp11::writable::strings;
  using integers_t = cpp11::integers;
  using integers_w = cpp11::writable::integers;
  using logicals_t = cpp11::logicals;
  using logicals_w = cpp11::writable::logicals;
  using doubles_t = cpp11::doubles;
  using doubles_w = cpp11::writable::doubles;
  
  using namespace cpp11::literals;
  
  data_frame_w get_font_info_c(strings_t path, integers_t index, doubles_t size, doubles_t res) {
    bool one_path = path.size() == 1;
    const char* first_path = Rf_translateCharUTF8(path[0]);
    int first_index = index[0];
    bool one_size = size.size() == 1;
    double first_size = size[0];
    bool one_res = res.size() == 1;
    double first_res = res[0];
    int full_length = 1;
    if (!one_path) full_length = path.size();
    else if (!one_size) full_length = size.size();
    else if (!one_res) full_length = res.size();
    
    FreetypeCache& cache = get_font_cache();
    
    strings_w path_col(full_length);
    integers_w index_col(full_length);
    strings_w family(full_length);
    strings_w style(full_length);
    logicals_w italic(full_length);
    logicals_w bold(full_length);
    logicals_w monospace(full_length);
    integers_w weight(full_length);
    weight.attr("class") = {"ordered", "factor"};
    weight.attr("levels") = {
      "thin",
      "ultralight",
      "light",
      "normal",
      "medium",
      "semibold",
      "bold",
      "ultrabold",
      "heavy"
    };
    integers_w width(full_length);
    width.attr("class") = {"ordered", "factor"};
    width.attr("levels") = {
      "ultracondensed",
      "extracondensed",
      "condensed",
      "semicondensed",
      "normal",
      "semiexpanded",
      "expanded",
      "extraexpanded",
      "ultraexpanded"
    };
    logicals_w kerning(full_length);
    logicals_w color(full_length);
    logicals_w scalable(full_length);
    logicals_w vertical(full_length);
    integers_w nglyphs(full_length);
    integers_w nsizes(full_length);
    integers_w ncharmaps(full_length);
    list_w bbox(full_length);
    doubles_w ascend(full_length);
    doubles_w descend(full_length);
    doubles_w advance_w(full_length);
    doubles_w advance_h(full_length);
    doubles_w lineheight(full_length);
    doubles_w u_pos(full_length);
    doubles_w u_size(full_length);
    
    for (int i = 0; i < full_length; ++i) {
      bool success = cache.load_font(
        one_path ? first_path : Rf_translateCharUTF8(path[i]),
        one_path ? first_index : index[i],
        one_size ? first_size : size[i],
        one_res ? first_res : res[i]
      );
      if (!success) {
        cpp11::stop("Failed to open font file (%s) with freetype error %i", Rf_translateCharUTF8(path[i]), cache.error_code);
      }
      FontInfo info = cache.font_info();
      
      path_col[i] = one_path ? first_path : path[i];
      index_col[i] = one_path ? first_index : index[i];
      family[i] = info.family;
      style[i] = info.style;
      italic[i] = (Rboolean) info.is_italic;
      bold[i] = (Rboolean) info.is_bold;
      monospace[i] = (Rboolean) info.is_monospace;
      weight[i] = cache.get_weight() / 100;
      if (weight[i] == 0) {
        weight[i] = NA_INTEGER;
      }
      width[i] = cache.get_width();
      if (width[i] == 0) {
        width[i] = NA_INTEGER;
      }
      kerning[i] = (Rboolean) info.has_kerning;
      color[i] = (Rboolean) info.has_color;
      scalable[i] = (Rboolean) info.is_scalable;
      vertical[i] = (Rboolean) info.is_vertical;
      nglyphs[i] = info.n_glyphs;
      nsizes[i] = info.n_sizes;
      ncharmaps[i] = info.n_charmaps;
      
      bbox[i] = doubles_w({
        "xmin"_nm = double(info.bbox[0]) / 64.0,
        "xmax"_nm = double(info.bbox[1]) / 64.0,
        "ymin"_nm = double(info.bbox[2]) / 64.0,
        "ymax"_nm = double(info.bbox[3]) / 64.0
      });
      
      ascend[i] = info.max_ascend / 64.0;
      descend[i] = info.max_descend / 64.0;
      advance_w[i] = info.max_advance_w / 64.0;
      advance_h[i] = info.max_advance_h / 64.0;
      lineheight[i] = info.lineheight / 64.0;
      u_pos[i] = info.underline_pos / 64.0;
      u_size[i] = info.underline_size / 64.0;
    }
    
    data_frame_w info({
      "path"_nm = path_col,
      "index"_nm = index_col,
      "family"_nm = family,
      "style"_nm = style,
      "italic"_nm = italic,
      "bold"_nm = bold,
      "monospace"_nm = monospace,
      "weight"_nm = weight,
      "width"_nm = width,
      "kerning"_nm = kerning,
      "color"_nm = color,
      "scalable"_nm = scalable,
      "vertical"_nm = vertical,
      "n_glyphs"_nm = nglyphs,
      "n_sizes"_nm = nsizes,
      "n_charmaps"_nm = ncharmaps,
      "bbox"_nm = bbox,
      "max_ascend"_nm = ascend,
      "max_descend"_nm = descend,
      "max_advance_width"_nm = advance_w,
      "max_advance_height"_nm = advance_h,
      "lineheight"_nm = lineheight,
      "underline_pos"_nm = u_pos,
      "underline_size"_nm = u_size
    });
    info.attr("class") = {"tbl_df", "tbl", "data.frame"};
    
    return info;
  }
  
  data_frame_w get_glyph_info_c(strings_t glyphs, strings_t path, integers_t index, doubles_t size, doubles_t res) {
    int n_glyphs = glyphs.size();
    
    bool one_path = path.size() == 1;
    const char* first_path = Rf_translateCharUTF8(path[0]);
    int first_index = index[0];
    bool one_size = size.size() == 1;
    double first_size = size[0];
    bool one_res = res.size() == 1;
    double first_res = res[0];
    
    FreetypeCache& cache = get_font_cache();
    
    integers_w glyph_ids(n_glyphs);
    doubles_w widths(n_glyphs);
    doubles_w heights(n_glyphs);
    doubles_w x_bearings(n_glyphs);
    doubles_w y_bearings(n_glyphs);
    doubles_w x_advances(n_glyphs);
    doubles_w y_advances(n_glyphs);
    list_w bboxes(n_glyphs);
    
    UTF_UCS utf_converter;
    int length = 0;
    int error_c = 0;
    
    for (int i = 0; i < n_glyphs; ++i) {
      bool success = cache.load_font(
        one_path ? first_path : Rf_translateCharUTF8(path[i]),
        one_path ? first_index : index[i],
        one_size ? first_size : size[i],
        one_res ? first_res : res[i]
      );
      if (!success) {
        cpp11::stop("Failed to open font file (%s) with freetype error %i", Rf_translateCharUTF8(path[i]), cache.error_code);
      }
      const char* glyph = Rf_translateCharUTF8(glyphs[i]);
      uint32_t* glyph_code = utf_converter.convert(glyph, length);
      GlyphInfo glyph_info = cache.cached_glyph_info(glyph_code[0], error_c);
      if (error_c != 0) {
        cpp11::stop("Failed to load `%s` from font (%s) with freetype error %i", glyph, Rf_translateCharUTF8(path[i]), error_c);
      }
      
      glyph_ids[i] = glyph_info.index;
      widths[i] = glyph_info.width / 64.0;
      heights[i] = glyph_info.height / 64.0;
      x_bearings[i] = glyph_info.x_bearing / 64.0;
      y_bearings[i] = glyph_info.y_bearing / 64.0;
      x_advances[i] = glyph_info.x_advance / 64.0;
      y_advances[i] = glyph_info.y_advance / 64.0;
      bboxes[i] = doubles_w({
        "xmin"_nm = double(glyph_info.bbox[0]) / 64.0,
        "xmax"_nm = double(glyph_info.bbox[1]) / 64.0,
        "ymin"_nm = double(glyph_info.bbox[2]) / 64.0,
        "ymax"_nm = double(glyph_info.bbox[3]) / 64.0
      });
    }
    
    data_frame_w info({
      "glyph"_nm = glyphs,
      "index"_nm = glyph_ids,
      "width"_nm = widths,
      "height"_nm = heights,
      "x_bearing"_nm = x_bearings,
      "y_bearing"_nm = y_bearings,
      "x_advance"_nm = x_advances,
      "y_advance"_nm = y_advances,
      "bbox"_nm = bboxes
    });
    info.attr("class") = {"tbl_df", "tbl", "data.frame"};
    
    return info;
  }
  
  int glyph_metrics(uint32_t code, const char* fontfile, int index, double size, 
                     double res, double* ascent, double* descent, double* width) {
    BEGIN_CPP
    
    FreetypeCache& cache = get_font_cache();
    if (!cache.load_font(fontfile, index, size, res)) {
      return cache.error_code;
    }
    int error = 0;
    GlyphInfo metrics = cache.cached_glyph_info(code, error);
      
    if (error != 0) {
      return error;
    }
    *width = metrics.x_advance / 64.0;
    *ascent = metrics.bbox[3] / 64.0;
    *descent = -metrics.bbox[2] / 64.0;
    
    END_CPP
    
    return 0;
  }
  
  int font_weight(const char* fontfile, int index) {
    BEGIN_CPP
    
    FreetypeCache& cache = get_font_cache();
    if (!cache.load_font(fontfile, index)) {
      return 0;
    }
    
    return cache.get_weight();
    
    END_CPP
      
    return 0;
  }
  int font_family(const char* fontfile, int index, char* family, int max_length) {
    BEGIN_CPP
    
    FreetypeCache& cache = get_font_cache();
    if (!cache.load_font(fontfile, index)) {
      return 0;
    }
    
    cache.get_family_name(family, max_length);
    
    END_CPP
      
    return 1;
  }
  
  void export_font_metrics(DllInfo* dll) {
    R_RegisterCCallable("systemfonts", "glyph_metrics", (DL_FUNC)glyph_metrics);
    R_RegisterCCallable("systemfonts", "font_weight", (DL_FUNC)font_weight);
    R_RegisterCCallable("systemfonts", "font_family", (DL_FUNC)font_family);
  }
  