<|start_filename|>sample/animation/flatuianim.cpp<|end_filename|>
// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cassert>
#include "flatui/flatui.h"
#include "flatui/flatui_common.h"
#include "fplbase/input.h"
#include "fplbase/renderer.h"
#include "fplbase/utilities.h"

#include "motive/engine.h"
#include "motive/init.h"

using flatui::kAlignCenter;
using flatui::AnimCurveDescription;
using flatui::EndGroup;
using flatui::Margin;
using flatui::Run;
using flatui::SetVirtualResolution;
using flatui::StartGroup;
using fplbase::kFormat8888;
using mathfu::vec2;
using mathfu::vec2i;
using mathfu::vec3;
using mathfu::vec4;
using mathfu::kZeros2f;
using mathfu::kZeros4f;
using mathfu::kOnes4f;
using motive::MotiveEngine;
using motive::MotiveTime;
using motive::Range;

static const vec4 kBackgroundColors[] = {
    vec4(0.596f, 0.137f, 0.584f, 1.0f),  // 982395
    vec4(1.0f, 0.635f, 0.0f, 1.0f),      // ffa200
    vec4(0.0f, 0.627f, 0.243f, 1.0f),    // 00a03e
    vec4(0.141f, 0.659f, 0.675f, 1.0f),  // 24a8ac
};

// Create curve's typical shape with a typical delta distance of 1.0f,
// a typical total time of 7000.0f, and a bias of 0.5f.
// For this sample, we want a gradual transition from one background color to
// another. By choosing a small distance to traverse and a long total time for
// that traversal, we are able to generate a gentler, more gradual animation
// curve.
// Additionally, the bias of 0.5f gives us an even ease-in and ease-out
// change from one color to another.
// For this specific sample, we are working with animating colors. Since colors
// range from 0~1, the maximum distance a color can travel is 1. We have chosen
// that maximum as our typical distance. We choose 7000 as our typical total
// time so that the transition will take 7 seconds when the color has to travel
// from 0 to 1, its typical delta distance.
static const AnimCurveDescription kColorCurve(flatui::kAnimEaseInEaseOut, 1.0f,
                                              7000.0f, 0.5f);

// The spring and ease curves are parameterized so that they settle down at
// the target at approximately the same time. This means that the spring must
// have a faster typical curve because it needs to oscillate about the target
// after it's reached it the first time.
static const AnimCurveDescription kSpringCurve(flatui::kAnimSpring, 300.0f,
                                               2000.0f, 0.4f);
static const AnimCurveDescription kEaseCurve(flatui::kAnimEaseInEaseOut, 300.0f,
                                             6000.0f, 0.2f);
static const AnimCurveDescription kImageCurve(flatui::kAnimEaseInEaseOut, 1.0f,
                                              4000.0f, 0.0f);
static const float kMovementX = 300.0f;
static const float kImageY = -325.0f;
static const float kButtonY = -50.0f;
static const float kEaseY = 150.0f;
static const float kSpringY = 250.0f;
static const float kImageSize = 250.0f;
static const float kButtonTextSize = 200.0f;
static const float kAnimatedTextSize = 100.0f;
static const vec4 kZeroAlphaRgba(1.0f, 1.0f, 1.0f, 0.0f);

extern "C" int FPL_main(int /*argc*/, char** argv) {
  MotiveEngine motive_engine;

  size_t color_idx = 0;

  fplbase::Renderer renderer;
  renderer.Initialize(vec2i(800, 600), "FlatUI sample");

  fplbase::InputSystem input;
  input.Initialize();

  // Set the local directory to the assets folder for this sample.
  bool result = fplbase::ChangeToUpstreamDir(argv[0], "assets");
  assert(result);
  (void)result;

  fplbase::AssetManager assetman(renderer);

  // Load textures.
  auto fade_texture =
      assetman.LoadTexture("textures/star_icon.webp", kFormat8888);
  assetman.StartLoadingTextures();
  while (!assetman.TryFinalize()) {
    renderer.AdvanceFrame(input.minimized(), input.Time());
  }

  // Load font.
  flatui::FontManager fontman;
  fontman.Open("fonts/LuckiestGuy.ttf");

  vec4 bg = kZeros4f;
  float movement_sign = 1.0f;

  while (!(input.exit_requested() ||
           input.GetButton(fplbase::FPLK_AC_BACK).went_down())) {
    input.AdvanceFrame(&renderer.window_size());
    renderer.AdvanceFrame(input.minimized(), input.Time());
    motive_engine.AdvanceFrame(static_cast<MotiveTime>(
        input.DeltaTime() * flatui::kSecondsToMotiveTime));
    renderer.ClearFrameBuffer(bg);

    // Show test GUI using flatui API.
    // In this sample, it shows basic UI elements of a label and an image.
    // Define flatui block. Note that the block is executed multiple times,
    // One for a layout pass and another for a rendering pass.
    Run(assetman, fontman, input, &motive_engine, [&]() {
      // Set a virtual resolution all coordinates will use. 1000 is now the
      // size of the smallest dimension (Y in landscape mode).
      SetVirtualResolution(1000);

      // Start our root group and position in the center of the screen.
      StartGroup(flatui::kLayoutVerticalCenter);
      PositionGroup(kAlignCenter, kAlignCenter, kZeros2f);

      // Allow the background color to be animated.
      bg = flatui::Animatable<vec4>("color_fading",
                                    kBackgroundColors[color_idx]);

      // Draw star image, that has animated alpha.
      StartGroup(flatui::kLayoutHorizontalCenter);
      PositionGroup(kAlignCenter, kAlignCenter, vec2(kMovementX, kImageY));
      flatui::SetImageColor(flatui::Animatable("image_color", kOnes4f));
      flatui::Image(*fade_texture, kImageSize);
      EndGroup();

      // Create the button and check for input event.
      StartGroup(flatui::kLayoutHorizontalCenter);
      PositionGroup(kAlignCenter, kAlignCenter, vec2(0.0f, kButtonY));
      const auto button_event = TextButton("Animate!", kButtonTextSize,
                                           Margin(10));
      EndGroup();

      // Draw "Ease" test, that will be animated with ease-in ease-out motion.
      StartGroup(flatui::kLayoutHorizontalCenter);
      PositionGroup(kAlignCenter, kAlignCenter,
                    vec2(flatui::Animatable("ease_x", kMovementX), kEaseY));
      flatui::Label("Ease", kAnimatedTextSize);
      EndGroup();

      // Draw "Spring" test, that will be animated with spring motion.
      StartGroup(flatui::kLayoutHorizontalCenter);
      PositionGroup(kAlignCenter, kAlignCenter,
                    vec2(flatui::Animatable("spring_x", kMovementX), kSpringY));
      flatui::Label("Spring", kAnimatedTextSize);
      EndGroup();

      // On button click start animating all variables.
      if (button_event & flatui::kEventWentUp) {
        color_idx = (color_idx + 1) % FPL_ARRAYSIZE(kBackgroundColors);
        flatui::StartAnimation<vec4>("color_fading",
                                     kBackgroundColors[color_idx], kZeros4f,
                                     kColorCurve);

        movement_sign *= -1.0f;
        flatui::StartAnimation<float>("ease_x", movement_sign * kMovementX,
                                      0.0f, kEaseCurve);
        flatui::StartAnimation<float>("spring_x", movement_sign * kMovementX,
                                      0.0f, kSpringCurve);

        vec4 target_color = movement_sign <= 0.0f ? kZeroAlphaRgba : kOnes4f;
        flatui::StartAnimation<vec4>("image_color", target_color, kZeros4f,
                                     kImageCurve);
      }

      // End root group.
      EndGroup();
    });
  }

  return 0;
}


<|start_filename|>src/font_systemfont.cpp<|end_filename|>
// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "precompiled.h"
#include <flatbuffers/flatbuffers.h>

// FreeType2 headers.
// This method of including is odd, but it's the recommended way
// (see https://www.freetype.org/freetype2/docs/tutorial/step1.html).
// It's also necessary to support both Freetype 2.5 and 2.6, since
// the main header is <freetype.h> in 2.5, but <freetype/freetype.h> in 2.6.
#include <ft2build.h>
#include FT_FREETYPE_H

// Platform specific headers.
#ifdef __ANDROID__
#include <fplutil/android_utils.h>
#endif  // __ANDROID__

#ifdef __APPLE__
// Include CoreFoundation headers in macOS/iOS.
#include <CoreFoundation/CoreFoundation.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreText/CTFont.h>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <ftw.h>
#include "TargetConditionals.h"
#endif
#include "font_manager.h"

using fplbase::LogInfo;
using fplbase::LogError;

#ifdef __APPLE__
namespace {
  template <typename T>
  struct CFDeleter {
    using pointer = T;
    void operator()(T ref) {
      if (ref) {
        CFRelease(ref);
      }
    }
  };

  template <typename T>
  using cf_ptr = std::unique_ptr<T, CFDeleter<T> >;
}  // namespace
#endif // ifdef __APPLE__

namespace flatui {
#ifdef _DEBUG
#define FLATUI_PROFILE_SYSTEM_FONT_SEARCH (1)
#endif  // _DEBUG

#ifdef __ANDROID__
using fplutil::JniObject;

// XML parser on Android.
// Enum indicating a tag type used while parsing the XML source.
enum {
  kStartDocument = 0,
  kEndDocument = 1,
  kStartTag = 2,
  kEndTag = 3,
  kText = 4,
};

// A tree node structure represents XML nodes.
const int32_t kInvalidNode = -1;
class XMLNode {
 public:
  XMLNode() : child_index_(kInvalidNode), sibling_index_(kInvalidNode) {}
  int32_t child_index_;
  int32_t sibling_index_;
  std::string name_;
  std::string text_;
  std::map<std::string, std::string> attributes_;
};

// The XML parser uses Android's XmlPullParser implementation using JNI
// interface and constructs a node tree by XMLNode. An application can access
// the node information
// through get_nodes() API. The first element of the array is a root node of the
// parsed XML.
class XMLParser {
 public:
  XMLParser() : sibling_node_(kInvalidNode) {};
  bool Open(std::string& str) {
    // Create XML parser object.
    JniObject parser =
        JniObject::CallStaticObjectMethod("android/util/Xml", "newPullParser",
                                          "()Lorg/xmlpull/v1/XmlPullParser;");

    // Open the source file.
    JniObject src = JniObject::CreateString(str);
    JniObject reader = JniObject::CreateObject(
        "java/io/StringReader", "(Ljava/lang/String;)V", src.get_object());
    parser.CallVoidMethod("setInput", "(Ljava/io/Reader;)V",
                          reader.get_object());

    // Construct a node tree at once.
    auto event = parser.CallIntMethod("getEventType", "()I");
    while (event != kEndDocument) {
      if (event == kStartDocument) {
        //        System.out.println("Start document");
      } else if (event == kStartTag) {
        std::string name =
            parser.CallStringMethod("getName", "()Ljava/lang/String;");
        AddNode(name);
        auto count = parser.CallIntMethod("getAttributeCount", "()I");
        for (auto i = 0; i < count; ++i) {
          std::string attr_name = parser.CallStringMethod(
              "getAttributeName", "(I)Ljava/lang/String;", i);
          std::string value = parser.CallStringMethod(
              "getAttributeValue", "(I)Ljava/lang/String;", i);
          SetAttribute(attr_name, value);
        }
      } else if (event == kEndTag) {
        PopNode();
      } else if (event == kText) {
        std::string text =
            parser.CallStringMethod("getText", "()Ljava/lang/String;");
        SetText(text);
      }
      event = parser.CallIntMethod("next", "()I");
    }

    return true;
  }
  std::vector<XMLNode>& get_nodes() { return nodes_; }

 private:
  void AddNode(std::string& name) {
    // Set up the node.
    XMLNode node;
    node.name_ = name;

    // Set up links.
    auto index = nodes_.size();
    if (sibling_node_ != kInvalidNode) {
      if (nodes_[sibling_node_].sibling_index_ == kInvalidNode) {
        nodes_[sibling_node_].sibling_index_ = index;
      }
    }
    if (!node_stack_.empty()) {
      if (nodes_[node_stack_.back()].child_index_ == kInvalidNode) {
        nodes_[node_stack_.back()].child_index_ = index;
      }
    }
    // Add the node to the vector and the stack.
    sibling_node_ = kInvalidNode;
    node_stack_.push_back(nodes_.size());
    nodes_.push_back(node);
  }

  void PopNode() {
    // Pop the node from the stack.
    sibling_node_ = node_stack_.back();
    node_stack_.pop_back();
  }

  void SetAttribute(std::string& name, std::string& value) {
    nodes_.back().attributes_[name] = value;
  }

  void SetText(std::string& text) {
    if (nodes_.back().text_.length() == 0) {
      nodes_.back().text_ = text;
    }
  }
  std::vector<XMLNode> nodes_;
  std::vector<int32_t> node_stack_;
  int32_t sibling_node_;
};
#endif  // __ANDROID__

#ifdef __APPLE__
// SFNT headers to generate SFNT blob.
struct SFNTHeader {
  int32_t version;
  uint16_t num_tables;
  uint16_t search_range;
  uint16_t entry_selector;
  uint16_t range_shift;
};

struct SFNTTableEntry {
  uint32_t tag;
  uint32_t check_sum;
  uint32_t offset;
  uint32_t length;
};

// Helper functions for CGFontToSFNT().
static uint32_t CalculateTableEntryCheckSum(const uint32_t* table,
                                            uint32_t size) {
  uint32_t sum = 0;
  uint32_t num_long = (size + sizeof(uint32_t) - 1) >> 2;
  while (num_long-- > 0) {
    sum += ntohl(*table++);
  }
  return sum;
}

// Helper function to convert CGFont to in-memory SFNT.
// on iOS/macOS, system font files are not directory accessible from an
// application. The helper extracts glyph data from CGFont and wraps it
// into a in-memory SFNT blob that FreeType can load.
static bool CGFontToSFNT(CGFontRef font, std::string* data) {
  assert(font);
  assert(data);

  // Retrieve tags.
  auto tags = CGFontCopyTableTags(font);
  auto table_count = CFArrayGetCount(tags);
  auto has_cff_table = false;
  auto total = sizeof(SFNTHeader) + sizeof(SFNTTableEntry) * table_count;

  // Allocate a table.
  std::unique_ptr<CFDataRef> table_data(new CFDataRef[table_count]);

  // Retrieve the blob data size.
  const uint32_t kCFFTag = 0x43464620;  //'CFF '
  for (auto i = 0; i < table_count; ++i) {
    uint32_t tag = reinterpret_cast<uint64_t>(CFArrayGetValueAtIndex(tags, i));
    if (tag == kCFFTag) {
      has_cff_table = true;
    }
    auto p = table_data.get()[i] = CGFontCopyTableForTag(font, tag);
    if (p != nullptr) {
      total += mathfu::RoundUpToTypeBoundary<uint32_t>(CFDataGetLength(p));
    }
  }

  // Resize the destination buffer.
  data->resize(total, 0);
  auto buffer = reinterpret_cast<uint8_t*>(&(*data)[0]);
  auto start = buffer;

  // Calculate values for the header.
  // search_range = (Maximum power of 2 <= num_tables) x 16.
  // entry_selector = Log2(maximum power of 2 <= num_tables).
  // range_shift = num_tables x 16 - search_range.
  // For more details of SFNT format, refer
  // https://www.microsoft.com/typography/otspec/otff.htm
  uint16_t entry_selector = 0;
  uint16_t search_range = 1;
  while (search_range < (table_count >> 1)) {
    entry_selector++;
    search_range *= 2;
  }
  search_range <<= 4;
  uint16_t range_shift = (table_count << 4) - search_range;

  // Setup header.
  uint32_t kOTVersion = 0x4f54544f;  //'OTTO'
  auto header = reinterpret_cast<SFNTHeader*>(buffer);
  buffer += sizeof(SFNTHeader);
  header->version =
      has_cff_table ? kOTVersion : flatbuffers::EndianSwap<uint16_t>(1);
  header->num_tables = flatbuffers::EndianSwap<uint16_t>(table_count);
  header->search_range = flatbuffers::EndianSwap<uint16_t>(search_range);
  header->entry_selector = flatbuffers::EndianSwap<uint16_t>(entry_selector);
  header->range_shift = flatbuffers::EndianSwap<uint16_t>(range_shift);

  auto entry = reinterpret_cast<SFNTTableEntry*>(buffer);
  buffer += sizeof(SFNTTableEntry) * table_count;

  for (auto i = 0; i < table_count; ++i) {
    if (table_data.get()[i]) {
      auto p = table_data.get()[i];
      auto size = CFDataGetLength(p);

      // Copy data.
      memcpy(buffer, CFDataGetBytePtr(p), size);

      // Setup a table entry.
      auto tag = reinterpret_cast<uint64_t>(CFArrayGetValueAtIndex(tags, i));
      entry->tag = flatbuffers::EndianSwap<uint32_t>(tag);
      entry->check_sum =
          flatbuffers::EndianSwap<uint32_t>(CalculateTableEntryCheckSum(
              reinterpret_cast<uint32_t*>(buffer), size));

      auto offset = buffer - start;
      entry->offset = flatbuffers::EndianSwap<uint32_t>(offset);
      entry->length = flatbuffers::EndianSwap<uint32_t>(size);
      buffer += mathfu::RoundUpToTypeBoundary<uint32_t>(size);
      ++entry;
      CFRelease(p);
    }
  }

  CFRelease(tags);
  return true;
}
#endif  // __APPLE__

// Open specified font by name and return the raw data.
// Current implementation works on macOS/iOS.
#ifdef __APPLE__
#if TARGET_OS_IPHONE

/// @brief Ensures a directory exists at a URL.
/// @return Returns true if it was able to ensure the directory exists.
static bool EnsureDirectory(CFURLRef url) {
  Boolean isReachable = CFURLResourceIsReachable(url, nullptr);
  if (isReachable) {
    return true;
  } else {
    cf_ptr<CFStringRef> dirPath(
        CFURLCopyFileSystemPath(url,
                                kCFURLPOSIXPathStyle));
    char pathBuffer[1024];
    Boolean didGetCString = CFStringGetCString(dirPath.get(),
                                               pathBuffer,
                                               1024,
                                               kCFStringEncodingUTF8);
    if (!didGetCString) {
      return false;
    }

    int mkdirError = mkdir(pathBuffer, 0755);
    return (mkdirError == 0);
  }
}

static bool GetDarwinVersion(char* str, size_t size) {
  int ret = sysctlbyname("kern.osrelease", str, &size, NULL, 0);
  return 0 == ret;
}

static int NftwRemove(const char *fpath,
                      const struct stat *sb,
                      int typeflag,
                      struct FTW *ftwbuf) {
  int rv = remove(fpath);
  if (rv) {
    perror(fpath);
  }

  return rv;
}

static int DeleteDirectory(char *path) {
  return nftw(path, NftwRemove, 64, FTW_DEPTH | FTW_PHYS);
}

static const void* OpenFontByName(CFStringRef name,
                                  int32_t offset,
                                  int32_t *size) {
  const void* returnValue = nullptr;
  cf_ptr<CFURLRef> homeUrl(CFCopyHomeDirectoryURL());
  cf_ptr<CFURLRef> libraryUrl(
      CFURLCreateCopyAppendingPathComponent(nullptr,
                                            homeUrl.get(),
                                            CFSTR("Library"),
                                            true));
  cf_ptr<CFURLRef> cachesUrl(
      CFURLCreateCopyAppendingPathComponent(nullptr,
                                            libraryUrl.get(),
                                            CFSTR("Caches"),
                                            true));

  cf_ptr<CFURLRef> fontsUrl(
    CFURLCreateCopyAppendingPathComponent(nullptr,
                                          cachesUrl.get(),
                                          CFSTR("flatui-fonts"),
                                          true));

  char darwinVersion[256];
  if (!GetDarwinVersion(darwinVersion, sizeof(darwinVersion))) {
    LogError("OpenFontByName: unable to get darwin version");
    return nullptr;
  }

  cf_ptr<CFStringRef> cfDarwinVersion(
      CFStringCreateWithCString(nullptr,
                                darwinVersion,
                                kCFStringEncodingUTF8));

  cf_ptr<CFURLRef> darwinVersionUrl(
    CFURLCreateCopyAppendingPathComponent(nullptr,
                                          fontsUrl.get(),
                                          cfDarwinVersion.get(),
                                          true));

  // Delete cache if invalidated by new operating system version.
  if (CFURLResourceIsReachable(fontsUrl.get(), nullptr) &&
        !CFURLResourceIsReachable(darwinVersionUrl.get(), nullptr)) {
    cf_ptr<CFStringRef> fontsPath(
        CFURLCopyFileSystemPath(fontsUrl.get(),
                                kCFURLPOSIXPathStyle));

    char fontsPathBuffer[1024];
    Boolean didGetCString = CFStringGetCString(fontsPath.get(),
                                               fontsPathBuffer,
                                               1024,
                                               kCFStringEncodingUTF8);
    if (!didGetCString) {
      return nullptr;
    }

    int err = DeleteDirectory(fontsPathBuffer);
    if (0 != err) {
      return nullptr;
    }
  }

  bool didEnsureFontsUrl = EnsureDirectory(fontsUrl.get());
  if (!didEnsureFontsUrl) {
    LogError("OpenFontByName: unable ensure fonts directory exists");
    return nullptr;
  }

  bool didEnsureDarwinVersionUrl = EnsureDirectory(darwinVersionUrl.get());
  if (!didEnsureDarwinVersionUrl) {
    LogError("OpenFontByName: unable ensure darwin version directory exists");
    return nullptr;
  }

  cf_ptr<CFURLRef> cacheUrl(
      CFURLCreateCopyAppendingPathComponent(nullptr,
                                            darwinVersionUrl.get(),
                                            name,
                                            true));

  cf_ptr<CFStringRef> cachePath(
      CFURLCopyFileSystemPath(cacheUrl.get(),
                              kCFURLPOSIXPathStyle));
  char pathBuffer[1024];
  Boolean didGetCString = CFStringGetCString(cachePath.get(),
                                             pathBuffer,
                                             1024,
                                             kCFStringEncodingUTF8);
  if (!didGetCString) {
    LogError("OpenFontByName: unable to get cstring from cfstringref");
    return nullptr;
  }

  // Check to see if file exists.
  Boolean isReachable = CFURLResourceIsReachable(cacheUrl.get(), nullptr);
  if (!isReachable) {
    cf_ptr<CGFontRef> cgfont(CGFontCreateWithFontName(name));
    if (cgfont.get() == nullptr) {
      LogError("OpenFontByName: unable to open cgfont");
      return nullptr;
    }

    // Load the font file of assets.
    std::string sfntData;
    bool didConvert = CGFontToSFNT(cgfont.get(), &sfntData);
    if (!didConvert) {
      LogError("OpenFontByName: unable to convert to sfnt");
      return nullptr;
    }

    std::ofstream outfile;
    outfile.open(pathBuffer);
    if (outfile.bad()) {
      LogError("OpenFontByName: unable to open output file");
      return nullptr;
    }
    outfile << sfntData;
  }

  returnValue = fplbase::MapFile(pathBuffer, offset, size);

  return returnValue;
}

#endif  // TARGET_OS_IPHONE

static bool OpenFontByName(CFStringRef name, std::string* dest) {
  auto cgfont = CGFontCreateWithFontName(name);
  if (cgfont == nullptr) {
    return false;
  }

  // Load the font file of assets.
  auto ret = CGFontToSFNT(cgfont, dest);
  CFRelease(cgfont);
  return ret;
}
#endif  // __APPLE__

// Open system's default font with a fallback list.
bool FontManager::OpenSystemFont() {
#ifdef __APPLE__
  return OpenSystemFontApple();
#elif defined(__ANDROID__)
  return OpenSystemFontAndroid();
#else   // __APPLE__ || __ANDROID__
  fplbase::LogInfo("OpenSystemFont() not implemented on the platform");
  return false;
#endif  // __APPLE__ || __ANDROID__
}

bool FontManager::CloseSystemFont() {
#ifdef __APPLE__
  return CloseSystemFontApple();
#elif defined(__ANDROID__)
  return CloseSystemFontAndroid();
#else   // __APPLE__ || __ANDROID__
  fplbase::LogInfo("CloseSystemFont() not implemented on the platform");
  return false;
#endif  // __APPLE__ || __ANDROID__
}

// A system font access implementation for iOS/macOS.
#ifdef __APPLE__

static void LogInfoProxy(const char* fmt, ...) {
  // There's an awful resolution order error which using a REAL va_list lets us dodge.
  va_list my_list;
  va_start(my_list, fmt);
  fplbase::LogInfo(fmt, my_list);
  va_end(my_list);
}

bool FontManager::OpenSystemFontApple() {
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_6_0 || \
    __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_8
  if (!system_fallback_list_.empty()) {
    fplbase::LogInfo("The system font is already loaded.");
    return true;
  }

  // Retrieve the system font fallback list using CFPreferences.
  const CFStringRef kTemporaryFontName = CFSTR("Helvetica");
  auto tmporary_font = CTFontCreateWithName(kTemporaryFontName, 0.0f, nullptr);
  auto languages = static_cast<CFArrayRef>(CFPreferencesCopyAppValue(
      CFSTR("AppleLanguages"), kCFPreferencesCurrentApplication));
  auto cascade_list =
      CTFontCopyDefaultCascadeListForLanguages(tmporary_font, languages);
  auto fallback_count = CFArrayGetCount(cascade_list);

  // Iterate through the list and load each font.
  bool ret = false;
  // A set used to check a font coverage while loading system fonts.
  std::set<FT_ULong> font_coverage;
  const int32_t kStringLength = 128;
  char str[kStringLength];

#ifdef FLATUI_PROFILE_SYSTEM_FONT_SEARCH
  auto start = std::chrono::system_clock::now();
#endif  // FLATUI_PROFILE_SYSTEM_FONT_SEARCH
  auto total_size = 0;
  for (auto i = 0; i < fallback_count; ++i) {
    auto descriptor = static_cast<CTFontDescriptorRef>(
        CFArrayGetValueAtIndex(cascade_list, i));
    auto font_name = static_cast<CFStringRef>(
        CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute));

    // Open the font.
    if (font_name != nullptr &&
        CFStringGetCString(font_name, str, kStringLength,
                           kCFStringEncodingUTF8)) {
      LogInfoProxy("Font name %s", str);
      FontFamily family(str, true);
      if (Open(family)) {
        // Retrieve the font size for an information.
        auto it = map_faces_.find(family.get_name());
        if (UpdateFontCoverage(it->second->get_face(), &font_coverage)) {
          total_size += it->second->get_font_size();

          system_fallback_list_.push_back(family);
          ret = true;
        } else {
#ifdef FLATUI_VERBOSE_LOGGING
          fplbase::LogInfo("Skipped loading font:%s %d", str, i);
#endif  // FLATUI_VERBOSE_LOGGING
          Close(str);
        }
      }
      CFRelease(font_name);
    }
  }
#ifdef FLATUI_PROFILE_SYSTEM_FONT_SEARCH
  auto end = std::chrono::system_clock::now();

  fplbase::LogInfo(
      "Loaded system font. %d fonts in the fallback list."
      " Total size:%d KB, Duration:%d msec",
      fallback_count, total_size / 1024,
      std::chrono::duration_cast<std::chrono::milliseconds>(end - start));
#endif  // FLATUI_PROFILE_SYSTEM_FONT_SEARCH

  // Clean up.
  CFRelease(cascade_list);
  CFRelease(tmporary_font);
  CFRelease(languages);
  return ret;
#else   // __IPHONE_OS_VERSION_MIN_REQUIRED || __MAC_OS_X_VERSION_MIN_REQUIRED
  fplbase::LogInfo("OpenSystemFont() requires iOS6~/macOS10.8~");
  return false;
#endif  // __IPHONE_OS_VERSION_MIN_REQUIRED || __MAC_OS_X_VERSION_MIN_REQUIRED
}

bool FontManager::CloseSystemFontApple() {
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_6_0 || \
    __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_8
  auto it = system_fallback_list_.begin();
  auto end = system_fallback_list_.end();
  while (it != end) {
    Close(it->get_name().c_str());
    it++;
  }
  system_fallback_list_.clear();
  return true;
#else   // __IPHONE_OS_VERSION_MIN_REQUIRED || __MAC_OS_X_VERSION_MIN_REQUIRED
  fplbase::LogInfo("CloseSystemFont() requires iOS6~ or macOS10.8~");
  return false;
#endif  // __IPHONE_OS_VERSION_MIN_REQUIRED || __MAC_OS_X_VERSION_MIN_REQUIRED
}
#endif  // __APPLE__

// A system font access implementation for Android.
#ifdef __ANDROID__

// Helper function to retrieve the system locale on Android.
// We don't just use a locale set to flatui because it would have multiple
// system locale with a priority in API 24~.
static std::vector<std::string> GetSystemLocale() {
  auto api_level = fplbase::AndroidGetApiLevel();
  std::vector<std::string> vec;
  if (api_level >= 24) {
    JniObject locale_list = JniObject::CallStaticObjectMethod(
        "android/os/LocaleList", "getDefault", "()Landroid/os/LocaleList;");
    auto size = locale_list.CallIntMethod("size", "()I");
    for (auto i = 0; i < size; ++i) {
      JniObject locale =
          locale_list.CallObjectMethod("get", "(I)Ljava/util/Locale;", i);
      auto lang =
          locale.CallStringMethod("getLanguage", "()Ljava/lang/String;");
      auto script =
          locale.CallStringMethod("getScript", "()Ljava/lang/String;");
      if (script.length()) {
        lang += "-" + script;
      }

      vec.push_back(lang);
      fplbase::LogInfo("System locale:%s", lang.c_str());
    }
  } else {
    JniObject locale = JniObject::CallStaticObjectMethod(
        "java/util/Locale", "getDefault", "()Ljava/util/Locale;");
    auto lang = locale.CallStringMethod("getLanguage", "()Ljava/lang/String;");
    auto script = locale.CallStringMethod("getScript", "()Ljava/lang/String;");
    if (script.length()) {
      lang += "-" + script;
    }
    vec.push_back(lang);
    fplbase::LogInfo("System locale:%s", lang.c_str());
  }
  return vec;
}

bool FontManager::OpenSystemFontAndroid() {
  const char* filename = "/system/etc/fonts.xml";
  std::string file;
  if (!fplbase::LoadFile(filename, &file)) {
    fplbase::LogInfo("Failed loading the setting file.");
    return false;
  }

  // Set the environment to the helper.
  JniObject::SetEnv(fplbase::AndroidGetJNIEnv());

  XMLParser parser;
  parser.Open(file);

  // Retrieve the font fallback list.
  std::vector<XMLNode> nodes = parser.get_nodes();

  // Retrieve the first 'family' entry without 'name' attribute.
  auto index = kInvalidNode;
  auto size = nodes.size();
  for (size_t i = 0; i < size; ++i) {
    if (nodes[i].name_ == "family" &&
        nodes[i].attributes_.find("name") == nodes[i].attributes_.end()) {
      index = i;
      break;
    }
  }

  // Generates a font name list.
  auto ret = false;
  // A set used to check a font coverage while loading system fonts.
  std::set<FT_ULong> font_coverage;
#ifdef FLATUI_PROFILE_SYSTEM_FONT_SEARCH
  auto start = std::chrono::system_clock::now();
#endif  // FLATUI_PROFILE_SYSTEM_FONT_SEARCH
  auto total_size = 0;
  const char* kSystemFontFolder = "/system/fonts/";
  std::vector<FontFamily> font_list;

  // Generate font fallback list.
  while (index != kInvalidNode) {
    XMLNode family = nodes[index];
    if (family.child_index_ != kInvalidNode) {
      XMLNode font = nodes[family.child_index_];

      std::string str = kSystemFontFolder + font.text_;
      auto idx = font.attributes_["index"];
      auto font_index = kIndexInvalid;
      if (idx.length()) {
        font_index = atoi(idx.c_str());
      }
      FontFamily fnt(str, font_index, family.attributes_["lang"], false);
      font_list.push_back(fnt);
    }
    index = family.sibling_index_;
  }

  // Reorder system fonts based on the system locale setting.
  ReorderSystemFonts(&font_list);

  // Add default font to the font list.
  const char* kDefaultFont = "/system/fonts/Roboto-Regular.ttf";
  FontFamily fnt(kDefaultFont, false);
  font_list.insert(font_list.begin(), std::move(fnt));

  // Load fonts.
  auto font_it = font_list.begin();
  auto font_end = font_list.end();
  while (font_it != font_end) {
    if (Open(*font_it)) {
      // Retrieve the font size for an information.
      auto face = map_faces_.find(font_it->get_name());
      if (UpdateFontCoverage(face->second->get_face(), &font_coverage)) {
        total_size += face->second->get_font_size();
        system_fallback_list_.push_back(std::move(*font_it));
        ret = true;
      } else {
#ifdef FLATUI_VERBOSE_LOGGING
        fplbase::LogInfo("Skipped loading font: %s",
                         font_it->get_name().c_str());
#endif  // FLATUI_VERBOSE_LOGGING
        Close(*font_it);
      }
    }
    font_it++;
  }
#ifdef FLATUI_PROFILE_SYSTEM_FONT_SEARCH
  auto end = std::chrono::system_clock::now();
  fplbase::LogInfo(
      "Loaded system font. %d fonts in the fallback list."
      " Total size:%d KB, Duration:%d msec",
      system_fallback_list_.size(), total_size / 1024,
      std::chrono::duration_cast<std::chrono::milliseconds>(end - start));
#endif  // FLATUI_PROFILE_SYSTEM_FONT_SEARCH

  return ret;
}

void FontManager::ReorderSystemFonts(std::vector<FontFamily>* font_list) const {
  // Re-order the list based on the system locale setting.
  auto locale = GetSystemLocale();
  auto it = locale.rbegin();
  auto end = locale.rend();
  while (it != end) {
    auto font_it = font_list->begin();
    auto font_end = font_list->end();
    while (font_it != font_end) {
      if (font_it->get_language().compare(0, it->length(), *it) == 0) {
        // Found a font with a priority locale in the fallback list. Bring the
        // font to the top of the list.
        auto font = std::move(*font_it);
        fplbase::LogInfo("Found a priority font: %s",
                         font.get_language().c_str());
        font_list->erase(font_it);
        font_list->insert(font_list->begin(), std::move(font));
        break;
      }
      font_it++;
    }
    it++;
  }
}

bool FontManager::CloseSystemFontAndroid() {
  auto it = system_fallback_list_.begin();
  auto end = system_fallback_list_.end();
  while (it != end) {
    Close(it->get_name().c_str());
    it++;
  }
  system_fallback_list_.clear();
  return true;
}
#endif  // __ANDROID__

bool FontManager::UpdateFontCoverage(FT_Face face,
                                     std::set<FT_ULong>* font_coverage) {
  auto has_new_coverage = false;
  FT_UInt index = 0;
  FT_ULong code = FT_Get_First_Char(face, &index);
  int32_t new_glyph = 0;
  while (index != 0) {
    if (font_coverage->find(code) == font_coverage->end()) {
      has_new_coverage = true;
      font_coverage->insert(code);
      new_glyph++;
    }
    code = FT_Get_Next_Char(face, code, &index);
  }
#ifdef FLATUI_VERBOSE_LOGGING
  fplbase::LogInfo("Has %d new glyphs", new_glyph);
#endif  // FLATUI_VERBOSE_LOGGING
  return has_new_coverage;
}

const void* FaceData::OpenFontByName(const char* font_name,
                                     int32_t offset,
                                     int32_t *size) {
#ifdef __APPLE__
#if TARGET_OS_IPHONE
  // Open the font using CGFont API and create font data from them.
  auto name = CFStringCreateWithCString(kCFAllocatorDefault, font_name,
                                        kCFStringEncodingUTF8);
  const void* ret = flatui::OpenFontByName(name, offset, size);
  if (!ret) {
    LogInfo("Can't load font resource: %s\n", font_name);
  }
  CFRelease(name);
  return ret;
#else  // TARGET_OS_IPHONE
  (void)font_name;
  (void)offset;
  (void)size;
  fplbase::LogInfo("OpenFontByName() not implemented on the platform.");
  return nullptr;
#endif  // TARGET_OS_IPHONE
#else   // __APPLE__
  (void)font_name;
  (void)offset;
  (void)size;
  fplbase::LogInfo("OpenFontByName() not implemented on the platform.");
  return nullptr;
#endif  // __APPLE__
}

bool FaceData::OpenFontByName(const char* font_name, std::string* dest) {
#ifdef __APPLE__
  // Open the font using CGFont API and create font data from them.
  auto name = CFStringCreateWithCString(kCFAllocatorDefault, font_name,
                                        kCFStringEncodingUTF8);
  bool ret = flatui::OpenFontByName(name, dest);
  if (!ret) {
    LogInfo("Can't load font resource: %s\n", font_name);
  }
  CFRelease(name);
  return ret;
#else   // __APPLE__
  (void)font_name;
  (void)dest;
  fplbase::LogInfo("OpenFontByName() not implemented on the platform.");
  return false;
#endif  // __APPLE__
}
}  // namespace flatui


<|start_filename|>include/flatui/internal/micro_edit.h<|end_filename|>
// Copyright 2015 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef FPL_MICRO_EDIT_H
#define FPL_MICRO_EDIT_H

#include <vector>

#include "fplbase/input.h"
#include "mathfu/constants.h"
#include "flatui/font_manager.h"

namespace flatui {

/// @cond FLATUI_INTERNAL

const int32_t kCaretPosInvalid = -1;

enum CaretPosition {
  kHeadOfLine,
  kTailOfLine,
};

enum EditorMode {
  kSingleLine,
  kMultipleLines,
};

// Micro editor for FlatUI's text input widget.
// Grab input events from SDL's IME (Input Method Editor) support API and handle
// them accordingly.
class MicroEdit {
 public:
  MicroEdit() { Reset(); }

  // Initialize an edit session with a given string.
  void Initialize(std::string *text, EditorMode mode);

  // Handle input events provided by SDL.
  // Returns true if the input session is finished by pressing a return.
  // In FlatUI, the library will call this function in the render pass.
  // Since SDL can generates multiple input events at a time, we accepts events
  // as a vector.
  EditStatus HandleInputEvents(
      const std::vector<fplbase::TextInputEvent> *events);

  // Get a caret position in current text.
  int32_t GetCaretPosition();

  // Get an editing text and selection postition in IME.
  std::string *GetEditingText() {
    return in_text_input_ ? &editing_text_ : text_;
  }

  // Retrieve input region parameters as a caret position index.
  // The input region is a region that is IME is in edit in the editing string
  // (retrieved via GetEditingText()) and the focus region is also a region in
  // IME control but needs to be shown as it's 'in focus' in UI.
  // Returns false if the IME is not active.
  bool GetInputRegions(int32_t *input_region_start,
                       int32_t *input_region_length,
                       int32_t *focus_region_start,
                       int32_t *focus_region_length);

  // Set a langauge used to determine line breaking.
  // language: ISO 639 based language code. Default setting is 'en'(English).
  // The value is passed to libunibreak.
  // As of libunibreak version 3.0, a list of supported languages is,
  // "en", "de", "es", "fr", "ru", "zh", "ja", "ko"
  void SetLanguage(const std::string &language) { language_ = language; }

  // Set a layout direction in the editor.
  // This setting affects forward/backward direction of a cursor control.
  void SetDirection(const TextLayoutDirection direction) {
    direction_ = direction;
  }

  // Set a FontBuffer to the editor.
  // The FontBuffer data is used to move a caret, pick a letter etc.
  void SetBuffer(const FontBuffer *buffer) { buffer_ = buffer; }

  // Get a window of the editor, the value is used in external UI rendering to
  // show a part of the editting string.
  // Returns mathfu::kZeros4i if the FontBuffer size is smaller than the given
  // window size.
  const mathfu::vec4i &GetWindow();

  // Set a window size of the editor in UI in pixels.
  void SetWindowSize(const mathfu::vec2i &size) {
    window_.z = size.x;
    window_.w = size.y;
  }

  // Pick a caret position from the pointer position.
  // Pointer position should be a relative offset value from the top left corner
  // of the edit window.
  int32_t Pick(const mathfu::vec2i &pointer_position, float offset);

  // Set caret position as a character index. The character index can be
  // retrieved Pick() or other APIs.
  bool SetCaret(int32_t position);

  // Returns true if the caret should be shown this frame. Since the caret will
  // blink on and off when a text field is selected.
  bool ShowCaret(float delta_time);

 private:
  void Reset() {
    initial_string_.clear();
    wordbreak_info_.clear();
    editing_text_.clear();
    input_text_selection_start_ = 0;
    input_text_selection_length_ = 0;
    input_text_caret_offset_ = 0;
    caret_pos_ = 0;
    wordbreak_index_ = 0;
    num_characters_ = 0;
    text_ = nullptr;
    in_text_input_ = false;
    window_ = mathfu::kZeros4i;
    window_offset_ = mathfu::kZeros2i;
    buffer_ = nullptr;
    expected_caret_x_position_ = kCaretPosInvalid;
    single_line_ = true;
    language_ = kDefaultLanguage;
    direction_ = kTextLayoutDirectionLTR;
    caret_timer_ = 0.0f;
  }

  // Helper to count a number of characters in a text.
  int32_t GetNumCharacters(const std::string &text);

  // Update a character index information in the UTF8 buffer.
  void UpdateWordBreakInfo();

  // Update an index in the wordbreak info buffer to corresponding caret
  // position.
  void UpdateWordBreakIndex();

  // Update an editing text.
  void UpdateEditingText(const std::string &input);

  // Reset current editing text.
  void ResetEditingText();

  // Caret control APIs.
  // Returns true if the caret has been moved by the API call.
  bool MoveCaret(bool forward);
  bool MoveCaretVertical(int32_t offset);
  bool MoveCaretInLine(CaretPosition position);
  bool MoveCaretToWordBoundary(bool forward);

  // Insert a text before the caret position and update caret position.
  void InsertText(const std::string &text);

  // Remove the specified number of text after the caret position.
  void RemoveText(int32_t num_remove);

  // Helper for Pick() API.
  int32_t PickColumn(const mathfu::vec2i &pointer_position,
                     std::vector<mathfu::vec2i>::const_iterator start_it,
                     std::vector<mathfu::vec2i>::const_iterator end_it);
  void PickRow(const mathfu::vec2i &pointer_position,
               std::vector<mathfu::vec2i>::const_iterator *start_it,
               std::vector<mathfu::vec2i>::const_iterator *end_it);

  int32_t caret_pos_;
  int32_t wordbreak_index_;
  int32_t num_characters_;

  // A pointer to a currently editting text.
  std::string *text_;

  // A copy of initial string. Used to reset the edit.
  std::string initial_string_;

  // Language setting used for line break operation.
  std::string language_;

  // Text layout direciton.
  TextLayoutDirection direction_;

  // Word breaking info retrieved by libUnibreak.
  std::vector<char> wordbreak_info_;

  // Editing text in IME.
  bool in_text_input_;
  std::string input_text_;
  int32_t input_text_characters_;
  int32_t input_text_caret_offset_;
  int32_t input_text_selection_start_;
  int32_t input_text_selection_length_;

  // Timer used to determine the cursor blinking.
  double caret_timer_;

  // Editing text shown in UI (Combination of edited text + IME text).
  std::string editing_text_;

  // Window offset and size of the editor in UI.
  mathfu::vec4i window_;
  mathfu::vec2i window_offset_;

  // Expected caret position, when moving a caret upward/downward, the editor
  // will pick a caret positon closest to this x position. This position is
  // updated when a caret is moved explicitly.
  int32_t expected_caret_x_position_;

  // A flag indicating the editor is for single line edit.
  bool single_line_;

  // A pointer to the FontBuffer.
  const FontBuffer *buffer_;
};
/// @endcond

}  // namespace flatui

#endif  // FPL_MICRO_EDIT_H


<|start_filename|>include/flatui/font_util.h<|end_filename|>
// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef FLATUI_FONT_UTIL_H
#define FLATUI_FONT_UTIL_H

#include <string>
#include <vector>

#include "mathfu/glsl_mappings.h"

/// @brief Namespace for FlatUI library.
namespace flatui {

/// @cond FLATUI_INTERNAL
// Forward decl.
class FontBuffer;
/// @endcond

/// @class HtmlSection
///
/// @brief A string of text that has the same formatting, and optional HREF.
class HtmlSection {
 public:
  HtmlSection() : size_(0), color_(0) {}
  explicit HtmlSection(const std::string &text)
      : text_(text), size_(0), color_(0) {}
  explicit HtmlSection(const std::string &text, const std::string &link)
      : text_(text), link_(link), size_(0), color_(0) {}
  virtual ~HtmlSection() {}
  // Copy constructor.
  HtmlSection(const HtmlSection &obj)
      : text_(obj.text_),
        link_(obj.link_),
        face_(obj.face_),
        size_(obj.size_),
        color_(obj.color_) {}

  // Accessors of the section type and parameters.
  const std::string &text() const { return text_; }
  std::string &text() { return text_; }

  // Setter/Getter for Anchor tag.
  const std::string &link() const { return link_; }
  void set_link(const char *link) { link_ = link; }

  // Setter/Getter for Font tag properties.
  const std::string &face() const { return face_; }
  void set_face(const char *face) { face_ = face; }
  void set_face(const std::string &face) { face_ = face; }

  int32_t size() const { return size_; }
  void set_size(int32_t size) { size_ = size; }

  // Color value in RGBA.
  uint32_t color() const { return color_; }
  void set_color(uint32_t color) { color_ = color; }

 protected:
  /// The text to render on screen. If `link` is specified, should be rendered
  /// as an HTML link (i.e. underlined and in blue).
  std::string text_;

  /// The HREF link to visit when `text` is clicked.
  std::string link_;

  /// A parameter used in font tag. The contents of the variable depends on a
  /// type.
  std::string face_;
  int32_t size_;
  uint32_t color_;
};

/// @brief Convert HTML into a vector of text strings which have the same
/// formatting.
///
/// @param html HTML text.
/// @param out A vector of HTML sections. For now, these are either sections
/// with
/// links or without, and we don't specify anything like bold or italics.
/// @return true if the given html is successfully parsed.
bool ParseHtml(const char *html, std::vector<HtmlSection> *out);

/// @brief Reprocess whitespace the in the manner of an HTML parser.
///
/// Replace intermediate whitespace with a single space.
/// Also, remove leading whitespace if so requested.
///
/// This emulates how HTML processes raw text fields.
///
/// Appends trimmed text to `out`.
/// @return reference to `out`.
std::string &TrimHtmlWhitespace(const char *text, bool trim_leading_whitespace,
                                std::string *out);

/// @brief Generate a triangle strip data representing underline geometry for
/// FontBuffer.
///
/// @param buffer FontBuffer to generate an underline geometory.
/// @param pos An offset value that is added to generated vertices position.
/// @param reverse Reverse the order of the vertices used, for example to
/// preserve mesh normals when using kTextLayoutDirectionRTL.
/// @return A vector of vec3 that includes a triangle strip data representing
/// underline information of given FontBuffer. The strip is divided into pieces
/// with a same width of each underlined character.
///
/// Below is an example to use API and render the underline strip using FPLBase
/// API.
/// auto line = GenerateUnderlineVertices(buffer, pos);
/// color_shader_->Set(renderer_);
/// static const fplbase::Attribute format[] = {fplbase::kPosition3f,
/// fplbase::kEND};
/// fplbase::Mesh::RenderArray(fplbase::Mesh::kTriangleStrip, line.size(),
///                            format, sizeof(mathfu::vec3_packed),
///                            &line[0]);
/// Note that a stride value of vec3 is 16 bytes.
std::vector<mathfu::vec3_packed> GenerateUnderlineVertices(
    const FontBuffer &buffer, const mathfu::vec2 &pos, bool reverse = false);



/// @brief Generate a triangle mesh data representing padded underline geometry
/// for FontBuffer. Requires special sdf aa shaders.
///
/// @param buffer FontBuffer to generate an underline geometory.
/// @param pos An offset value that is added to generated vertices position.
/// @param padding A padding value that is added around each generated underline
/// segment.
/// @param positions The vector of vec3 that will be cleared and filled with
/// triangle positions representing underline information of given FontBuffer.
/// The strip is divided into pieces with a same width of each underlined
/// character, plus padding at the front and back.
/// @param tex_coords The vector of vec2 that will be cleared and filled with
/// texture coordinates with added padding such that [0,1] in uv space delineate
/// the un-padded underline size. When padding is non-zero, that means that the
/// actual values of tex_coords will be <0 and >1. This is used to add SDF
/// antialiasing and the vector will be the same size as |positions|.
/// @param indices The vector of uint16_t that will be filled with indices into
/// |positions| and |tex_coords| that represent triangles. Its size will be a
/// multiple of 3 and all values less than the size of |positions|.
/// @param reverse Reverse the order of the vertices used, for example to
/// preserve mesh normals when using kTextLayoutDirectionRTL.
/// @return A boolean for the success of this function. If false the output
/// vectors will be untouched.
bool GeneratePaddedUnderlineVertices(
    const FontBuffer &buffer, const mathfu::vec2 &pos,
    const mathfu::vec2 &padding, std::vector<mathfu::vec3>* positions,
    std::vector<mathfu::vec2>* tex_coords, std::vector<uint16_t>* indices,
    bool reverse = false);

}  // namespace flatui

#endif  // FLATUI_FONT_UTIL_H


<|start_filename|>sample/basic/flatuisample.cpp<|end_filename|>
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cassert>

#include "fplbase/renderer.h"
#include "fplbase/input.h"
#include "fplbase/utilities.h"
#include "flatui/flatui.h"

using flatui::Run;
using flatui::EndGroup;
using flatui::Image;
using flatui::Label;
using flatui::SetVirtualResolution;
using flatui::StartGroup;
using mathfu::vec2;
using mathfu::vec2i;
using mathfu::vec4;

extern "C" int FPL_main(int /*argc*/, char **argv) {
  fplbase::Renderer renderer;
  renderer.Initialize(vec2i(800, 600), "FlatUI sample");

  fplbase::InputSystem input;
  input.Initialize();

  // Set the local directory to the assets folder for this sample.
  bool result = fplbase::ChangeToUpstreamDir(argv[0], "assets");
  assert(result);
  (void)result;

  fplbase::AssetManager assetman(renderer);

  flatui::FontManager fontman;
  // Open OpenType font.
  fontman.Open("fonts/NotoSansCJKjp-Bold.otf");

  // Load textures.
  auto tex_about =
      assetman.LoadTexture("textures/text_about.webp", fplbase::kFormat8888);
  assetman.StartLoadingTextures();

  // While an initialization of flatui, it implicitly loads shaders used in the
  // API below using AssetManager.
  // shaders/color.glslv & .glslf, shaders/font.glslv & .glslf
  // shaders/textured.glslv & .glslf

  // Wait for everything to finish loading...
  while (!assetman.TryFinalize()) {
    renderer.AdvanceFrame(input.minimized(), input.Time());
  }

  while (!(input.exit_requested() ||
           input.GetButton(fplbase::FPLK_AC_BACK).went_down())) {
    input.AdvanceFrame(&renderer.window_size());
    renderer.AdvanceFrame(input.minimized(), input.Time());
    const float kColorGray = 0.5f;
    renderer.ClearFrameBuffer(
        vec4(kColorGray, kColorGray, kColorGray, 1.0f));

    // Show test GUI using flatui API.
    // In this sample, it shows basic UI elements of a label and an image.
    // Define flatui block. Note that the block is executed multiple times,
    // One for a layout pass and another for a rendering pass.
    Run(assetman, fontman, input, [&]() {
      // Set a virtual resolution all coordinates will use. 1000 is now the
      // size of the smallest dimension (Y in landscape mode).
      SetVirtualResolution(1000);

      // Start our root group.
      StartGroup(flatui::kLayoutVerticalLeft);

      // Position group in the center of the screen.
      PositionGroup(flatui::kAlignCenter, flatui::kAlignCenter,
                    mathfu::kZeros2f);

      // Show a label with a font size of 40px in a virtual resotuion.
      Label("The quick brown fox jumps over the lazy dog.", 40);

      // Show an image with a virtical size of 60px in a virtual resotuion.
      // A width is automatically derived from the image size.
      Image(*tex_about, 60);

      EndGroup();
    });
  }

  return 0;
}


<|start_filename|>test/ref_count/flatui_ref_count_test.cpp<|end_filename|>
// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <stdio.h>
#include <thread>
#include "src/flatui_serialization.cpp"
#include "flatui/flatui_generated.h"
#include "fplutil/main.h"
#include "gtest/gtest.h"
#include "mocks/flatui_common_mocks.h"
#include "mocks/flatui_mocks.h"
#include "mocks/fplbase_mocks.h"

using ::testing::_;
using ::testing::Return;
using ::testing::SaveArg;

// Note: You cannot easily extact `va_list` arguments from the
// `fplbase::LogError` mock. Therefore, I simply check that the correct
// error message was called, without checking the format string conversion
// specifiers.

class FlatUIRefCountTest : public ::testing::Test {
 public:
   MATHFU_DEFINE_CLASS_SIMD_AWARE_NEW_DELETE

 protected:
  static void SetUpTestCase() {       // Called once, before any tests.
    flatui::SetErrorOutputCount(-1);  // Disable error count limit.
  }

  virtual void SetUp() {
    // Initialize stuff.
    renderer_.Initialize(mathfu::vec2i(800, 600), "FlatUI test");
    renderer_.SetCulling(fplbase::kCullingModeBack);

    // Instantiate a FontManager.
    int32_t width = 256;
    int32_t height = 256;
    int32_t slices = 2;
    font_manager_ =
        new flatui::FontManager(mathfu::vec2i(width, height), slices);

    // Set the local directory to the assets folder for this sample.
    bool result = fplbase::ChangeToUpstreamDir("../", "assets");
    assert(result);
    (void)result;

    // Open OpenType font.
    font_manager_->Open("fonts/NotoSansCJKjp-Bold.otf");
  }

  virtual void TearDown() {
    delete font_manager_;
    renderer_.ShutDown();
  }

  fplbase::Renderer renderer_;
  flatui::FontManager *font_manager_;
};

TEST_F(FlatUIRefCountTest, TestCreateBuffer) {
  // Create a FontBuffer.
  auto string = "Test string";
  auto parameter = flatui::FontBufferParameters(
      font_manager_->GetCurrentFont()->GetFontId(), flatui::HashId(string),
      static_cast<float>(48), mathfu::vec2i(0, 0), flatui::kTextAlignmentLeft,
      flatui::kGlyphFlagsNone, true, true);
  auto buffer = font_manager_->GetBuffer(string, strlen(string), parameter);
  ASSERT_EQ(1, static_cast<int32_t>(buffer->get_ref_count()));
  font_manager_->ReleaseBuffer(buffer);
}

TEST_F(FlatUIRefCountTest, TestCheckRefCounts) {
  // multiple ref counts test.
  auto string = "Test string";
  flatui::FontBuffer *buffer;
  auto parameter = flatui::FontBufferParameters(
      font_manager_->GetCurrentFont()->GetFontId(), flatui::HashId(string),
      static_cast<float>(48), mathfu::vec2i(0, 0), flatui::kTextAlignmentLeft,
      flatui::kGlyphFlagsNone, true, true);
  int32_t k = 32;
  for (int32_t i = 0; i < k; ++i) {
    buffer = font_manager_->GetBuffer(string, strlen(string), parameter);
    ASSERT_EQ(i + 1, static_cast<int32_t>(buffer->get_ref_count()));
  }
  for (int32_t i = k; i > 0; --i) {
    font_manager_->ReleaseBuffer(buffer);
    ASSERT_EQ(i - 1, static_cast<int32_t>(buffer->get_ref_count()));
  }
}

TEST_F(FlatUIRefCountTest, TestCheckFaceDataRefCounts) {
  // Test Open & Close font functionality.
  auto font = "fonts/NotoSansCJKjp-Bold.otf";

  // Close active font.
  font_manager_->Close(font);
  ASSERT_EQ(false, font_manager_->SelectFont(font));

  // Open & Close font multiple times.
  int32_t k = 32;
  for (int32_t i = 0; i < k; ++i) {
    font_manager_->Open(font);
  }
  for (int32_t i = 0; i < k; ++i) {
    font_manager_->Close(font);
  }
  ASSERT_EQ(false, font_manager_->SelectFont(font));

  // Keep opening fonts to verify destructor destroys them all.
  for (int32_t i = 0; i < k; ++i) {
    font_manager_->Open(font);
  }
}

TEST_F(FlatUIRefCountTest, TestCacheEviction) {
  // Test cache eviction.
  char string2[] = "!";
  char c = '!';
  const int32_t num = 96;
  flatui::FontBuffer *buffers[num];
  for (int32_t i = 0; i < num; ++i) {
    auto parameter = flatui::FontBufferParameters(
        font_manager_->GetCurrentFont()->GetFontId(), flatui::HashId(string2),
        static_cast<float>(48), mathfu::vec2i(0, 0), flatui::kTextAlignmentLeft,
        flatui::kGlyphFlagsNone, true, true);
    buffers[i] = font_manager_->GetBuffer(string2, 2, parameter);
    snprintf(string2, 2, "%c", c);
    c++;
    // Update counters.
    font_manager_->StartLayoutPass();
    font_manager_->StartRenderPass();
  }

  int32_t invalid = 0;
  for (int32_t i = 0; i < num; ++i) {
    if (!buffers[i]->Verify()) {
      invalid++;
    }
  }
  LogInfo("%d/%d buffers are invalid", invalid, num);
}

// Create unique instances using FontBufferParameters::cache_id.
// By setting the ID, FontManager is expected to create unique FontBuffer
// instances.
TEST_F(FlatUIRefCountTest, TestUniqueInstance) {
  // Flush fontmanager.
  font_manager_->FlushAndUpdate();

  // Test no cached behavior.
  const char string2[] = "!";
  const int32_t num = 64;
  flatui::FontBuffer *buffers[num];
  std::set<flatui::FontBuffer *> buffer_set;
  flatui::HashedId id = flatui::kNullHash;
  for (int32_t i = 0; i < num; ++i) {
    auto parameter = flatui::FontBufferParameters(
        font_manager_->GetCurrentFont()->GetFontId(), flatui::HashId(string2),
        static_cast<float>(48), mathfu::vec2i(0, 0), flatui::kTextAlignmentLeft,
        flatui::kGlyphFlagsNone, true, true, flatui::kKerningScaleDefault,
        flatui::kLineHeightDefault, id);
    buffers[i] = font_manager_->GetBuffer(string2, 2, parameter);
    // Update counters.
    font_manager_->StartLayoutPass();
    font_manager_->StartRenderPass();

    // Verify all intances are unique.
    ASSERT_EQ(buffer_set.end(), buffer_set.find(buffers[i]));
    buffer_set.insert(buffers[i]);

    // Update the ID.
    id++;
  }

  int32_t invalid = 0;
  for (int32_t i = 0; i < num; ++i) {
    if (!buffers[i]->Verify()) {
      invalid++;
    }
  }
  LogInfo("%d/%d buffers are invalid", invalid, num);

  // Flush fontmanager.
  font_manager_->FlushAndUpdate();

  for (int32_t i = 0; i < num; ++i) {
    font_manager_->ReleaseBuffer(buffers[i]);
  }
}

TEST_F(FlatUIRefCountTest, TestMultiThreadBufferCreation) {
  auto buffergen_test = [this] {
    char string2[] = "!";
    char c = '!';
    const int32_t num = 96;
    flatui::FontBuffer *buffers[num];
    for (int32_t i = 0; i < num; ++i) {
      auto parameter = flatui::FontBufferParameters(
          font_manager_->GetCurrentFont()->GetFontId(), flatui::HashId(string2),
          static_cast<float>(48), mathfu::vec2i(0, 0),
          flatui::kTextAlignmentLeft, flatui::kGlyphFlagsNone, true, true);
      buffers[i] = font_manager_->GetBuffer(string2, 2, parameter);
      snprintf(string2, 2, "%c", c);
      c++;
    }

    // Verify the generated buffers.
    for (int32_t i = 0; i < num; ++i) {
      ASSERT_EQ(true, buffers[i]->Verify());
      font_manager_->ReleaseBuffer(buffers[i]);
    }
  };

  // Invoke StartRenderPass() API while generating FontBuffers.
  for (auto i = 0; i < 10; i++) {
    // Flush fontmanager.
    font_manager_->FlushAndUpdate();

    std::thread fontmanager_thread(buffergen_test);
    for (auto j = 0; j < 10; j++) {
      font_manager_->StartLayoutPass();
      font_manager_->StartRenderPass();
    }
    fontmanager_thread.join();
  }

  // TODO: Add a verification of generated cache bitmap.
}

TEST_F(FlatUIRefCountTest, TestEmptyString) {
  // Create a FontBuffer.
  auto string = "Test string";
  auto empty_string = "";
  auto parameter = flatui::FontBufferParameters(
      font_manager_->GetCurrentFont()->GetFontId(), flatui::HashId(string),
      static_cast<float>(48), mathfu::vec2i(300, 100),
      flatui::kTextAlignmentCenter, flatui::kGlyphFlagsNone, true, false);
  auto buffer = font_manager_->GetBuffer(string, strlen(string), parameter);
  (void)buffer;

  // Create another FontBuffer with an empty string and check if it's an empty
  // buffer.
  auto parameter_empty = flatui::FontBufferParameters(
      font_manager_->GetCurrentFont()->GetFontId(),
      flatui::HashId(empty_string), static_cast<float>(48),
      mathfu::vec2i(300, 100), flatui::kTextAlignmentCenter,
      flatui::kGlyphFlagsNone, true, false);
  auto buffer_empty = font_manager_->GetBuffer(
      empty_string, strlen(empty_string), parameter_empty);
  ASSERT_EQ(0, static_cast<int32_t>(buffer_empty->get_vertices().size()));
}

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

// Dummy entry to link the test in Android successfully.
extern "C" int FPL_main(int /*argc*/, char ** /*argv*/) { return 0; }


<|start_filename|>src/font_buffer.cpp<|end_filename|>
// Copyright 2017 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "precompiled.h"

#include "font_manager.h"
#include "fplbase/fpl_common.h"

using mathfu::vec2;
using mathfu::vec2i;
using mathfu::vec4;

namespace flatui {

void FontBufferContext::SetAttribute(const FontBufferAttributes &attribute) {
  auto it = LookUpAttribute(attribute);

  if (attribute_history_.empty() || it != attribute_history_.back()) {
    attribute_history_.push_back(it);
  }
}

FontBufferContext::attribute_map_it FontBufferContext::LookUpAttribute(
    const FontBufferAttributes &attribute) {
  if (!attribute_map_.empty()) {
    // Check if we already has an entry in the map.
    auto it = attribute_map_.find(attribute);
    if (it != attribute_map_.end()) {
      return it;
    }
  }
  auto ret = attribute_map_.insert(std::make_pair(attribute, kIndexInvalid));
  return ret.first;
}

int32_t FontBuffer::GetBufferIndex(int32_t slice, FontBufferContext *context) {
  auto &attr_history = context->attribute_history();

  // Check if we can use the latest attribute in the attribute stack.
  if (attr_history.back()->first.get_slice_index() == slice) {
    return attr_history.back()->second;
  }
  auto new_attr = attr_history.back()->first;
  // Remove temporary attribute.
  if (new_attr.get_slice_index() == kIndexInvalid) {
    attr_history.pop_back();
  }
  new_attr.set_slice_index(slice);
  auto it = context->LookUpAttribute(new_attr);
  if (it->second == kIndexInvalid) {
    // Resize index buffers.
    it->second = static_cast<int32_t>(slices_.size());
    slices_.push_back(it->first);
    indices_.resize(it->second + 1);
  }
  // Update the attribute stack.
  if (attr_history.empty() || it != attr_history.back()) {
    attr_history.push_back(it);
  }
  assert(it->second < static_cast<int32_t>(indices_.size()));
  return it->second;
}

void FontBuffer::AddIndices(int32_t buffer_idx, int32_t count) {
  // Construct indices array.
  assert(buffer_idx < static_cast<int32_t>(indices_.size()));
  const uint16_t kIndices[] = {0, 1, 2, 1, 3, 2};
  auto &indices = get_indices(buffer_idx);
  for (size_t j = 0; j < FPL_ARRAYSIZE(kIndices); ++j) {
    auto index = kIndices[j];
    indices.push_back(static_cast<unsigned short>(index + count * 4));
  }
}

void FontBuffer::UpdateUnderline(int32_t buffer_idx, int32_t vertex_index,
                                 const mathfu::vec2i &y_pos) {
  // Update underline information if necessary.
  auto &attr = slices_[buffer_idx];
  if (!attr.get_underline()) {
    return;
  }
  attr.UpdateUnderline(vertex_index, y_pos);
}

void FontBuffer::AddVertices(const mathfu::vec2 &pos, int32_t base_line,
                             float scale, const GlyphCacheEntry &entry) {
  mathfu::vec2 scaled_offset = entry.get_offset() * scale;
  mathfu::vec2 scaled_size = mathfu::vec2(entry.get_size()) * scale;
  mathfu::vec2 scaled_advance = mathfu::vec2(entry.get_advance()) * scale;

  auto x = pos.x + scaled_offset.x;
  auto y = pos.y + base_line - scaled_offset.y;
  auto uv = entry.get_uv();
  vertices_.push_back(FontVertex(x, y, 0.0f, uv.x, uv.y));
  vertices_.push_back(FontVertex(x, y + scaled_size.y, 0.0f, uv.x, uv.w));
  vertices_.push_back(FontVertex(x + scaled_size.x, y, 0.0f, uv.z, uv.y));
  vertices_.push_back(
      FontVertex(x + scaled_size.x, y + scaled_size.y, 0.0f, uv.z, uv.w));

  last_pos_ = pos;
  last_advance_ = last_pos_ + scaled_advance;
}

void FontBuffer::UpdateUV(int32_t index, const mathfu::vec4 &uv) {
  vertices_[index * 4].uv_ = uv.xy();
  vertices_[index * 4 + 1].uv_ = mathfu::vec2(uv.x, uv.w);
  vertices_[index * 4 + 2].uv_ = mathfu::vec2(uv.z, uv.y);
  vertices_[index * 4 + 3].uv_ = uv.zw();
}

void FontBuffer::AddCaretPosition(const mathfu::vec2 &pos) {
  mathfu::vec2i rounded_pos = mathfu::vec2i(pos);
  AddCaretPosition(rounded_pos.x, rounded_pos.y);
}

void FontBuffer::AddCaretPosition(int32_t x, int32_t y) {
  assert(caret_positions_.capacity());
  caret_positions_.push_back(mathfu::vec2i(x, y));
}

void FontBuffer::AddWordBoundary(const FontBufferParameters &parameters,
                                 FontBufferContext *context) {
  if (parameters.get_text_alignment() & kTextAlignmentJustify) {
    // Keep the word boundary info for a later use for a justificaiton.
    context->word_boundary().push_back(
        static_cast<uint32_t>(glyph_info_.size()));
    context->word_boundary_caret().push_back(
        static_cast<uint32_t>(caret_positions_.size()));
  }
}

void FontBuffer::UpdateLine(const FontBufferParameters &parameters,
                            TextLayoutDirection layout_direction,
                            FontBufferContext *context) {
  // Update previous line layout if necessary.
  auto align = parameters.get_text_alignment() & ~kTextAlignmentJustify;
  auto justify = parameters.get_text_alignment() & kTextAlignmentJustify;
  if (context->lastline_must_break()) {
    justify = false;
  }
  auto glyph_count = vertices_.size() / kVerticesPerCodePoint;
  auto line_start_index = std::min(line_start_indices_.back(),
                                   static_cast<uint32_t>(glyph_count));

  // Do nothing when the width of the text rect is not specified.
  if ((justify || align != kTextAlignmentLeft) && parameters.get_size().x) {
    // Adjust glyph positions.
    auto offset = 0;  // Offset to add for each glyph position.
                      // When we justify a text, the offset is increased for
                      // each word boundary by boundary_offset_change.
    auto boundary_offset_change = 0;
    auto &word_boundary = context->word_boundary();

    // |--start_pos--|----line-----|
    // |--end_pos------------------|
    // |--size---------------------------------------|

    // Center
    // |--size/2--------------|
    // |--start_pos--| |line/2|
    //    offset     |-|

    // offset = size/2 - line/2 - start_pos
    //        = size/2 - (end_pos - start_pos)/2 - 2*start_pos/2
    //        = (size - end_pos - start_pos)/2

    // Right
    // |--size---------------------------------------|
    // |--start_pos--|   |----line-----|--start_pos--|
    //    offset     |---|

    // offset = size - line - 2*start_pos
    //        = size - (end_pos - start_pos) - 2*start_pos
    //        = size - end_pos - start_pos

    auto start_pos = 0;
    auto end_pos = 0;
    if (!vertices_.empty()) {
      // Do not use vertices' positions, use the position and advance of the
      // glyph layout to match font's intended alignments to remove bias from
      // offsets and SDF in the glyph.
      if (layout_direction == kTextLayoutDirectionLTR) {
        start_pos = 0;
        end_pos = last_advance_.x;
      } else if (layout_direction == kTextLayoutDirectionRTL) {
        start_pos = last_pos_.x;
        end_pos = parameters.get_size().x;
      } else {
        // TTB layout is currently not supported.
        assert(0);
      }
    }
    auto free_width = parameters.get_size().x - end_pos - start_pos;

    if (justify && word_boundary.size() > 1) {
      // With a justification, we add an offset for each word boundary.
      // For each word boundary (e.g. spaces), we stretch them slightly to align
      // both the left and right ends of each line of text.
      boundary_offset_change = static_cast<int32_t>(
          free_width / (word_boundary.size() - 1));
    } else {
      justify = false;
      if (align == kTextAlignmentCenter) {
        offset = free_width / 2;
      } else if (align == kTextAlignmentRight) {
        offset = free_width;
      } // kTextAlignmentLeft has no offset.
    }

    // Keep original offset value.
    auto offset_caret = offset;

    // Update each glyph's position.
    auto boundary_index = 0;
    for (auto idx = line_start_index; idx < glyph_count; ++idx) {
      if (justify && idx >= word_boundary[boundary_index]) {
        boundary_index++;
        offset += boundary_offset_change;
      }
      auto it = vertices_.begin() + idx * kVerticesPerCodePoint;
      for (auto i = 0; i < kVerticesPerCodePoint; ++i) {
        it->position_.data[0] += offset;
        it++;
      }
    }

    // Update caret position, too.
    if (HasCaretPositions()) {
      boundary_index = 0;
      for (auto idx = context->line_start_caret_index();
           idx < caret_positions_.size(); ++idx) {
        if (justify && idx >= context->word_boundary_caret()[boundary_index]) {
          boundary_index++;
          offset_caret += boundary_offset_change;
        }
        caret_positions_[idx].x += offset_caret;
      }
    }
  }

  // Update current line information.
  line_start_indices_.push_back(static_cast<uint32_t>(glyph_info_.size()));
  context->set_lastline_must_break(false);
  context->set_line_start_caret_index(
      static_cast<uint32_t>(caret_positions_.size()));
  context->word_boundary().clear();
  context->word_boundary_caret().clear();
}

float FontBuffer::AdjustCurrentLine(const FontBufferParameters &parameters,
                                    FontBufferContext *context) {
  auto new_ysize = static_cast<int32_t>(parameters.get_font_size());
  auto offset = 0;
  if (new_ysize > context->current_font_size() &&
      !line_start_indices_.empty() && !context->lastline_must_break()) {
    // Adjust glyph positions in current line.
    offset = new_ysize * parameters.get_line_height_scale() -
             context->current_font_size() * parameters.get_line_height_scale();

    for (auto idx = line_start_indices_.back(); idx < glyph_info_.size();
         ++idx) {
      auto it = vertices_.begin() + idx * kVerticesPerCodePoint;
      for (auto i = 0; i < kVerticesPerCodePoint; ++i) {
        it->position_.data[1] += offset;
        it++;
      }
    }
  }
  return offset;
}

static void VertexExtents(const FontVertex *v, vec2 *min_position,
                          vec2 *max_position) {
  // Get extents of the glyph.
  const float kInfinity = std::numeric_limits<float>::infinity();
  vec2 min(kInfinity);
  vec2 max(-kInfinity);
  for (auto i = 0; i < FontBuffer::kVerticesPerCodePoint; ++i) {
    const vec2 position(v[i].position_.data);
    min = vec2::Min(min, position);
    max = vec2::Max(max, position);
  }
  *min_position = min;
  *max_position = max;
}

std::vector<vec4> FontBuffer::CalculateBounds(int32_t start_index,
                                              int32_t end_index) const {
  const float kInfinity = std::numeric_limits<float>::infinity();
  std::vector<vec4> extents;

  start_index = std::max(0, start_index);
  end_index = std::max(0, end_index);

  // Clamp to vertex bounds.
  const uint32_t start = static_cast<uint32_t>(start_index);
  const uint32_t end =
      std::min(static_cast<uint32_t>(vertices_.size()) / kVerticesPerCodePoint,
               static_cast<uint32_t>(end_index));

  // Find `line` such that line_start_indices[line] is the *end* index of
  // start's line.
  assert(start <= end && end <= line_start_indices_.back());
  uint32_t line = 0;
  while (line_start_indices_[line] <= start) ++line;

  // Prime the loop with the first glyph.
  vec2 min(kInfinity);
  vec2 max(-kInfinity);
  for (auto i = start; i < end; ++i) {
    // If i is on the next line, output the current extents.
    const bool is_new_line = line_start_indices_[line] == i;
    if (is_new_line) {
      // Record the extents for the current line.
      extents.push_back(vec4(min, max));

      // Reset the extents for the next line.
      min = vec2(kInfinity);
      max = vec2(-kInfinity);

      // Advance to the next line.
      line++;
      assert(line < line_start_indices_.size());
    }

    // Get extents of the next glyph.
    vec2 glyph_min;
    vec2 glyph_max;
    VertexExtents(&vertices_[kVerticesPerCodePoint * i], &glyph_min,
                  &glyph_max);

    // Update our current bounds.
    min = vec2::Min(min, glyph_min);
    max = vec2::Max(min, glyph_max);
  }

  // Record the extents for the last line.
  extents.push_back(vec4(min, max));
  return extents;
}

void FontBufferAttributes::UpdateUnderline(int32_t vertex_index,
                                           const mathfu::vec2i &y_pos) {
  // TODO: Underline information need to be consolidated into one attribute,
  // rather than distributed to multiple slices. Otherwise, we wouldn't get
  // continuous underline.
  if (!underline_) {
    return;
  }

  if (underline_info_.empty() || underline_info_.back().y_pos_ != y_pos ||
      underline_info_.back().end_vertex_index_ != vertex_index - 1) {
    // Create new underline info.
    UnderlineInfo info(vertex_index, y_pos);
    underline_info_.push_back(info);
  } else {
    // Update existing line.
    underline_info_.back().y_pos_ = y_pos;
    underline_info_.back().end_vertex_index_ = vertex_index;
  }
}

void FontBufferAttributes::WrapUnderline(int32_t vertex_index) {
  if (!underline_) {
    return;
  }

  // Add new line.
  underline_info_.back().end_vertex_index_ = vertex_index;
  UnderlineInfo info(vertex_index + 1, underline_info_.back().y_pos_);
  underline_info_.push_back(info);
}

void GlyphCacheRow::InvalidateReferencingBuffers() {
  auto begin = ref_.begin();
  auto end = ref_.end();
  while (begin != end) {
    auto buffer = *begin;
    buffer->Invalidate();
    begin++;
  }
}

void GlyphCacheRow::ReleaseReferencesFromFontBuffers() {
  while (!ref_.empty()) {
    auto buffer = *ref_.begin();
    // This will lead to the buffer being removed from ref_ via
    // GlyphCacheRow::Release(), so no need to use iterators here.
    buffer->ReleaseCacheRowReference();
  }
}

}  // namespace flatui


<|start_filename|>include/flatui/flatui_core.h<|end_filename|>
// Copyright 2017 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef FPL_FLATUI_CORE_H
#define FPL_FLATUI_CORE_H

#include "mathfu/constants.h"
#include "mathfu/glsl_mappings.h"

namespace flatui {

// clang-format off
/// @enum Alignment
///
/// @brief Alignment of the groups.
///
/// @note: `kAlignTop` and `kAlignLeft`(as well as `kAlignBottom` and
/// `kAlignRight`) are intended to be aliases of one another, as they both
/// express the same thing on their respective axis.
///
/// **Enumerations**:
///
/// * `kAlignTop` or `kAlignLeft` (`1`) - Align along the top (or left,
///                                       depending on the axis).
/// * `kAlignCenter` (`2`) - Align along the center of the axis.
/// * `kAlignBottom` or `kAlignRight` (`3`) - Align along the bottom (or right,
///                                           depending on the axis).
enum Alignment {
  kAlignTop = 1,
  kAlignLeft = 1,
  kAlignCenter = 2,
  kAlignBottom = 3,
  kAlignRight = 3
};

/// @enum Direction
///
/// @brief Direction of the groups.
///
/// **Enumerations**:
///
/// * `kDirHorizontal` (`4`) - The direction of the group is horizontal
///                            (x-axis).
/// * `kDirVertical` (`8`) - The direction of the group is vertical (y-axis).
/// * `kDirOverlay` (`12`) - The group of elements are placed on top of one
///                          another (along the z-axis).
enum Direction {
  kDirHorizontal = 4,
  kDirVertical = 8,
  kDirOverlay = 12
};

/// @enum Layout
///
/// @brief Specify how to layout a group.
///
/// Elements can be positioned either horizontally or vertically. The elements
/// can be aligned on either side, or centered.
///
/// For example, `kLayoutHorizontalTop` indicates that the elements are layed
/// out from left-to-right, with items of uneven height being aligned from the
/// top.
///
/// In this example, we have three elements: `A` with a height of 3, `B` with
/// a height of 1, and `C` with a height of 2. So we lay the elements out from
/// left to right in the order `A`->`B`->`C`, and we align them along the top.
///
/// That layout would look like this:
///
/// A @htmlonly &nbsp @endhtmlonly  B @htmlonly &nbsp @endhtmlonly  C
///
/// A @htmlonly &nbsp&nbsp&nbsp&nbsp&nbsp&nbsp @endhtmlonly C
///
/// A
///
/// **Enumerations**:
///
/// * `kLayoutHorizontalTop` (`5`) - Lay out the elements horizontally, aligning
///                                  elements of uneven height along the top.
/// * `kLayoutHorizontalCenter` (`6`) - Lay out the elements horizontally,
///                                     aligning elements of uneven height along
///                                     the center.
/// * `kLayoutHotizontalBottom` (`7`) - Lay out the elements horizontally,
///                                     aligning elements of uneven height along
///                                     the bottom.
/// * `kLayoutVerticalLeft` (`9`) - Lay out the elements vertically, aligning
///                                 elements of uneven width along the left.
/// * `kLayoutVerticalCenter` (`10`) - Lay out the elements vertically, aligning
///                                   elements of uneven width along the center.
/// * `kLayoutVerticalRight` (`11`) - Lay out the elements vertically, aligning
///                                  the elements of uneven width along the
///                                  right.
/// * `kLayoutOverlay` (`14`) - Lay out the elements on top of one another, from
///                             the center.
enum Layout {
  kLayoutHorizontalTop =    kDirHorizontal| kAlignTop,
  kLayoutHorizontalCenter = kDirHorizontal| kAlignCenter,
  kLayoutHorizontalBottom = kDirHorizontal| kAlignBottom,
  kLayoutVerticalLeft =     kDirVertical  | kAlignLeft,
  kLayoutVerticalCenter =   kDirVertical  | kAlignCenter,
  kLayoutVerticalRight =    kDirVertical  | kAlignRight,
  kLayoutOverlay =          kDirOverlay   | kAlignCenter,
};
// clang-format on

/// @var FLATUI_DEFAULT_VIRTUAL_RESOLUTION
///
/// @brief The default virtual resolution, if none is set.
const float FLATUI_DEFAULT_VIRTUAL_RESOLUTION = 1000.0f;

/// @var kDefaultGroupID
///
/// @brief A sentinel value for group IDs.
const char *const kDefaultGroupID = "__group_id__";

/// @var kDefaultImageID
///
/// @brief A sentinel value for image IDs.
const char *const kDefaultImageID = "__image_id__";

/// @struct Margin
///
/// @brief Specifies the margins for a group, in units of virtual
/// resolution.
struct Margin {
  /// @brief Create a Margin with all four sides of equal size.
  ///
  /// @param[in] m A float size to be used as the margin on all sides.
  Margin(float m) : borders(mathfu::vec4(m)) {}

  /// @brief Create a Margin with the left and right sizes of `x`, and top
  /// and bottom sizes of `y`.
  ///
  /// @param[in] x A float size to be used as the margin for the left and
  /// right sides.
  /// @param[in] y A float size to be used as the margin for the right and
  /// left sides.
  Margin(float x, float y) : borders(mathfu::vec4(x, y, x, y)) {}

  // Create a margin specifying all 4 sides individually.
  /// @brief Creates a margin specifying all four sides individually.
  ///
  /// @param[in] left A float size to be used as the margin for the left side.
  /// @param[in] top A float size to be used as the margin for the top side.
  /// @param[in] right A float size to be used as the margin for the right side.
  /// @param[in] bottom A float size to be used as the margin for the bottom
  /// side.
  Margin(float left, float top, float right, float bottom)
      : borders(mathfu::vec4(left, top, right, bottom)) {}

  /// @var borders
  ///
  /// @brief A vector of four floats containing the values for the
  /// four sides of the margin.
  ///
  /// The internal layout of the margin is: `left`, `top`, `right`, `bottom`.
  mathfu::vec4_packed borders;
};

}  // namespace flatui

#endif  // FPL_FLATUI_CORE_H


<|start_filename|>jni/libs/freetype/Android.mk<|end_filename|>
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

LOCAL_PATH:=$(call my-dir)

# Project directory relative to this file.
FLATUI_RELATIVE_DIR:=../../..
FLATUI_DIR:=$(LOCAL_PATH)/$(FLATUI_RELATIVE_DIR)
include $(FLATUI_DIR)/jni/android_config.mk
FLATUI_RELATIVEPATH:=$(call realpath-portable,$(FLATUI_DIR))

# realpath-portable from android_config.mk
LOCAL_PATH:=$(call realpath-portable,$(DEPENDENCIES_FREETYPE_DIR))

###########################
#
# Freetype build
#
###########################

FREETYPE_SRC_FILES = \
    src/base/ftbbox.c \
    src/base/ftbitmap.c \
    src/base/ftfstype.c \
    src/base/ftglyph.c \
    src/base/ftlcdfil.c \
    src/base/ftstroke.c \
    src/base/fttype1.c \
    src/base/ftbase.c \
    src/base/ftsystem.c \
    src/base/ftinit.c \
    src/base/ftgasp.c \
    src/base/ftmm.c \
    src/gzip/ftgzip.c \
    src/raster/raster.c \
    src/sfnt/sfnt.c \
    src/smooth/smooth.c \
    src/autofit/autofit.c \
    src/truetype/truetype.c \
    src/cff/cff.c \
    src/psnames/psnames.c \
    src/pshinter/pshinter.c \
    $(FLATUI_RELATIVEPATH)/src/libpng_to_stbimage/pngshim.cpp

#############################################################
#   build the harfbuzz shared library
#
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm
LOCAL_SRC_FILES := $(FREETYPE_SRC_FILES)
LOCAL_C_INCLUDES += $(DEPENDENCIES_FREETYPE_DIR)/include\
  $(DEPENDENCIES_HARFBUZZ_DIR)/src\
  $(FLATUI_DIR)/external/include/harfbuzz\
  $(FLATUI_DIR)/src/libpng_to_stbimage\
  $(DEPENDENCIES_STB_DIR)
LOCAL_CFLAGS += \
  -DFT2_BUILD_LIBRARY \
  -DFT_CONFIG_MODULES_H=\"$(FLATUI_RELATIVEPATH)/cmake/freetype/ftmodule.h\"\
  -DFT_CONFIG_OPTION_USE_PNG \
  -DFT_CONFIG_OPTION_USE_HARFBUZZ
LOCAL_MODULE := libfreetype
LOCAL_MODULE_FILENAME := libfreetype

include $(BUILD_STATIC_LIBRARY)


<|start_filename|>sample/advanced/flatuiadvanced.cpp<|end_filename|>
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cassert>

#include "fplbase/renderer.h"
#include "fplbase/input.h"
#include "fplbase/utilities.h"
#include "flatui/flatui.h"
#include "flatui/flatui_common.h"

using namespace flatui;
using fplbase::kFormat8888;
using mathfu::vec2;
using mathfu::vec2i;
using mathfu::vec4;

extern "C" int FPL_main(int /*argc*/, char **argv) {
  fplbase::Renderer renderer;
  fplbase::InputSystem input;
  FontManager fontman;
  fplbase::AssetManager assetman(renderer);

  // Set the local directory to the assets for this test.
  bool result = fplbase::ChangeToUpstreamDir(argv[0], "assets");
  assert(result);
  (void)result;

  // Initialize stuff.
  renderer.Initialize(vec2i(800, 600), "FlatUI sample");
  renderer.SetCulling(fplbase::kCullingModeBack);
  input.Initialize();

  // Open OpenType font including the color emoji font.
  const FontFamily fonts[] = {
    FontFamily("fonts/NotoSansCJKjp-Bold.otf"),
    FontFamily("fonts/NotoNaskhArabic-Regular.ttf"),
    FontFamily("fonts/NotoColorEmoji.ttf")
  };
  fontman.EnableColorGlyph();

  for (size_t i = 0; i < FPL_ARRAYSIZE(fonts); ++i) {
    fontman.Open(fonts[i]);
  }
  fontman.SelectFont(fonts, FPL_ARRAYSIZE(fonts));

  // Load textures.
  auto tex_about =
      assetman.LoadTexture("textures/text_about.webp", kFormat8888);
  auto tex_check_on =
      assetman.LoadTexture("textures/btn_check_on.webp", kFormat8888);
  auto tex_check_off =
      assetman.LoadTexture("textures/btn_check_off.webp", kFormat8888);
  auto tex_circle =
      assetman.LoadTexture("textures/white_circle.webp", kFormat8888);
  auto tex_bar = assetman.LoadTexture("textures/gray_bar.webp", kFormat8888);
  assetman.StartLoadingTextures();

  // Wait for everything to finish loading...
  while (!assetman.TryFinalize()) {
    renderer.AdvanceFrame(input.minimized(), input.Time());
  }

  // Main loop.
  while (!input.exit_requested()) {
    input.AdvanceFrame(&renderer.window_size());
    renderer.AdvanceFrame(input.minimized(), input.Time());

    const float kColorGray = 0.25f;
    renderer.ClearFrameBuffer(
        vec4(kColorGray, kColorGray, kColorGray, 1.0f));

    // Draw GUI test
    static float f = 0.0f;
    f += 0.04f;
    const TextAlignment alignments[] = {TextAlignment::kTextAlignmentLeft,
      TextAlignment::kTextAlignmentRight, TextAlignment::kTextAlignmentCenter,
      TextAlignment::kTextAlignmentJustify,
      TextAlignment::kTextAlignmentRightJustify,
      TextAlignment::kTextAlignmentCenterJustify};
    static int32_t alignment_idx = 0;

    static bool show_about = false;
    static vec2 scroll_offset(mathfu::kZeros2f);
    static bool checkbox1_checked;
    static bool test_rtl;
    static float slider_value;
    static std::string str("Edit box.");
    static std::string str2("More Edit box.");
    static std::string str3("The\nquick brown fox jumps over the lazy dog.\n"
                            "The quick brown fox jumps over the lazy dog. "
                            "The quick brown fox jumps over the lazy dog. "
                            "The quick brown fox jumps over the lazy dog. ");
    // Sample Arabic string encoded Unicode entities.
    auto rtl_string =
    "\xd9\x8a\xd9\x8e\xd8\xac\xd9\x90\xd8\xa8\xd9\x8f\x20\x3a\x20\xd9\x85\xd9"
    "\x90\xd9\x86\x20\xd9\x88\xd9\x8e\xd8\xac\xd9\x8e\xd8\xa8\xd9\x8e\x20\x28"
    "\xd9\x85\xd9\x90\xd8\xab\xd9\x8e\xd8\xa7\xd9\x84\x20\x29\x20\x2c\x20\xd9"
    "\x81\xd9\x90\xd8\xb9\xd9\x92\xd9\x84\xd9\x8c\x20\xd9\x85\xd9\x8f\xd8\xb6"
    "\xd9\x8e\xd8\xa7\xd8\xb1\xd9\x90\xd8\xb9\xd9\x8c\x20\xd9\x85\xd9\x8e\xd8"
    "\xb1\xd9\x92\xd9\x81\xd9\x8f\xd9\x88\xd8\xb9\xd9\x8c\x20\xd8\xa8\xd9\x90"
    "\xd8\xb6\xd9\x8e\xd9\x85\xd9\x91\xd9\x8e\xd8\xa9\xd9\x8d .It is necessary";

    auto click_about_example = [&](const char *id, bool about_on) {
      if (ImageButton(*tex_about, 50, Margin(10), id) ==
          kEventWentUp) {
        fplbase::LogInfo("You clicked: %s", id);
        show_about = about_on;
      }
    };

    Run(assetman, fontman, input, [&]() {
      SetGlobalListener([](HashedId id, Event event) {
        // Example global event listener for logging / debugging / analytics.
        // Don't use this for normal event handling, see examples below instead.
        if (event & (kEventWentUp | kEventWentDown))
          fplbase::LogInfo("Event Listener: %x -> %d", id, event);
      });
      SetVirtualResolution(1000);
      if (test_rtl) {
        SetTextLocale("ar");
      } else {
        SetTextLocale("en");
      }
      SetTextEllipsis("...");

      StartGroup(kLayoutOverlay, 0);
        StartGroup(kLayoutHorizontalTop, 10);
          PositionGroup(kAlignCenter, kAlignCenter,
                        mathfu::kZeros2f);
          StartGroup(kLayoutVerticalLeft, 20);
           CheckBox(*tex_check_on, *tex_check_off, "Test RTL", 30,
               Margin(6, 0), &test_rtl);
            click_about_example("my_id1", true);
            Edit(30, vec2(400, 30), "edit2", nullptr, &str2);
            StartGroup(kLayoutHorizontalTop, 0);
              Edit(30, vec2(0, 30), "edit", nullptr, &str);
              Label(">Tail", 30);
            EndGroup();
            Slider(*tex_circle, *tex_bar,
                   vec2(300, 25), 0.5f, "slider", &slider_value);
            CheckBox(*tex_check_on, *tex_check_off, "CheckBox", 30,
                          Margin(6, 0), &checkbox1_checked);
            StartGroup(kLayoutHorizontalTop, 0);
              Label("Property T", 30);
              SetTextColor(vec4(1.0f, 0.0f, 0.0f, 1.0f));
              Label("Test ", 30);
              SetTextColor(mathfu::kOnes4f);
              Label("ffWAWÄテスト", 30);
            EndGroup();
            if (TextButton("text button test", 20, Margin(10)) ==
                kEventWentUp) {
              fplbase::LogInfo("You clicked: text button");
            }
            StartGroup(kLayoutVerticalLeft, 20, "scroll");
            StartScroll(vec2(300, 200), &scroll_offset);
                ImageBackgroundNinePatch(*tex_about,
                                         vec4(0.2f, 0.2f, 0.8f, 0.8f));
                click_about_example("my_id4", true);
                Label("The quick brown fox jumps over the lazy dog", 24);
                Label("The quick brown fox jumps over the lazy dog", 20);
              EndScroll();
            EndGroup();
          EndGroup();
          StartGroup(kLayoutVerticalCenter, 40);
            click_about_example("my_id2", true);
            Image(*tex_about, 40);
            Image(*tex_about, 30);
          EndGroup();
          StartGroup(kLayoutVerticalRight, 0);
            if (TextButton("Change layout", 20, Margin(10)) ==
                kEventWentUp) {
              alignment_idx = (alignment_idx + 1) % FPL_ARRAYSIZE(alignments);
            }
            Edit(24, vec2(400, 400), alignments[alignment_idx], "edit3",
                 nullptr, &str3);
            // Some arabic labels.
            SetTextLocale("ar");
            Label(rtl_string, 40, vec2(400, 0));
            SetTextLocale("en");
          EndGroup();
        EndGroup();
        if (show_about) {
          StartGroup(kLayoutVerticalLeft, 20, "about_overlay");
            ModalGroup();
            PositionGroup(kAlignRight,
                          kAlignTop, vec2(-30, 30));
            SetMargin(Margin(10));
            ColorBackground(vec4(0.5f, 0.5f, 0.0f, 1.0f));
            click_about_example("my_id3", false);
            Label("This is the about window! すし!", 32);
            Label("You should only be able to click on the", 24);
            Label("about button above, not anywhere else", 20);

            // Layout test with ellipsis.
            flatui::SetTextEllipsis("...");
            Label("The quick brown fox jumps over the lazy dog", 40,
                  vec2(400, 100), flatui::kTextAlignmentCenter);
            Label("US NATIONAL PARKS AND HISTORY", 40, vec2(400, 100),
                  flatui::kTextAlignmentCenter);
            Label("PARKS CANADA, PARCS CANADA", 40, vec2(400, 100),
                  flatui::kTextAlignmentCenter);
            Label("ONE", 40, vec2(400, 100),
                  flatui::kTextAlignmentCenter);
          EndGroup();
        }
      EndGroup();
    });
  }

  return 0;
}


<|start_filename|>src/flatui_common.cpp<|end_filename|>
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "precompiled.h"
#include "fplbase/renderer.h"
#include "flatui/flatui.h"
#include "flatui/flatui_common.h"

using fplbase::Texture;
using mathfu::vec2;
using mathfu::vec2i;
using mathfu::vec4;

namespace flatui {

vec4 g_hover_color = vec4(0.5f, 0.5f, 0.5f, 0.5f);
vec4 g_click_color = vec4(1.0f, 1.0f, 1.0f, 0.5f);

void SetHoverClickColor(const vec4 &hover_color, const vec4 &click_color) {
  g_hover_color = hover_color;
  g_click_color = click_color;
}

void EventBackground(Event event) {
  if (event & kEventIsDown)
    ColorBackground(g_click_color);
  else if (event & kEventHover)
    ColorBackground(g_hover_color);
}

Event ImageButton(const Texture &texture, float size, const Margin &margin,
                  const char *id) {
  StartGroup(kLayoutVerticalLeft, size, id);
  SetMargin(margin);
  auto event = CheckEvent();
  EventBackground(event);
  Image(texture, size);
  EndGroup();
  return event;
}

Event ToggleImageButton(const fplbase::Texture &up_texture,
                          const fplbase::Texture &down_texture,
                          float size, const Margin &margin, const char *id) {
  StartGroup(kLayoutVerticalLeft, size, id);
  SetMargin(margin);
  auto event = CheckEvent();
  EventBackground(event);
  if (event & kEventIsDown) {
    Image(down_texture, size);
  } else {
    Image(up_texture, size);
  }
  EndGroup();
  return event;
}

Event TextButton(const char *text, float size, const Margin &margin) {
  StartGroup(kLayoutVerticalLeft, size, text);
  SetMargin(margin);
  auto event = CheckEvent();
  EventBackground(event);
  Label(text, size);
  EndGroup();
  return event;
}

void ShowTexture(const Texture &texture, const Margin &texture_margin,
                 float size) {
  StartGroup(kLayoutVerticalLeft, size);
  SetMargin(texture_margin);
  Image(texture, size);
  EndGroup();
}

Event TextButton(const Texture &texture, const Margin &texture_margin,
                 const char *text, float size, const Margin &text_margin,
                 const ButtonProperty property) {
  StartGroup(kLayoutHorizontalCenter, 0, text);
  auto event = kEventNone;
  if (!(property & kButtonPropertyDisabled)) {
    event = CheckEvent();
    EventBackground(event);
  }

  if (property & kButtonPropertyImageLeft) {
    ShowTexture(texture, texture_margin, size);
  }

  StartGroup(kLayoutVerticalLeft, size);
  SetMargin(text_margin);
  Label(text, size);
  EndGroup();

  if (property & kButtonPropertyImageRight) {
    ShowTexture(texture, texture_margin, size);
  }

  EndGroup();
  return event;
}

Event CheckBox(const Texture &texture_checked, const Texture &texture_unchecked,
               const char *label, float size, const Margin &margin,
               bool *is_checked) {
  StartGroup(kLayoutHorizontalBottom, 0, label);
  auto event = CheckEvent();
  Image(*is_checked ? texture_checked : texture_unchecked, size);
  SetMargin(margin);
  Label(label, size);
  // Change the state here, to ensure it changes after we've already rendered.
  if (event & kEventWentUp) {
    *is_checked = !*is_checked;
  }
  EndGroup();
  return event;
}

Event Slider(const Texture &tex_bar, const Texture &tex_knob, const vec2 &size,
             float bar_height, const char *id, float *slider_value) {
  StartGroup(kLayoutHorizontalBottom, 0, id);
  StartSlider(kDirHorizontal, size.y * 0.5f, slider_value);
  auto event = CheckEvent();
  // Show focus area when controled by a gamepad.
  if (!IsLastEventPointerType()) EventBackground(event);
  CustomElement(size, id, [&tex_knob, &tex_bar, bar_height, slider_value](
                              const vec2i &pos, const vec2i &size) {
    // Render the slider.
    auto bar_pos = pos;
    auto bar_size = size;
    bar_pos +=
        vec2i(size.y / 2, static_cast<int>(size.y * (1.0 - bar_height) / 2.0f));
    bar_size = vec2i(std::max(bar_size.x - size.y, 0),
                     static_cast<int>(bar_size.y * bar_height));

    auto knob_pos = pos;
    vec2i knob_sizes(size.y, size.y);
    knob_pos.x +=
        static_cast<int>(*slider_value * static_cast<float>(size.x - size.y));
    RenderTextureNinePatch(tex_bar, vec4(0.5f, 0.5f, 0.5f, 0.5f), bar_pos,
                           bar_size);
    RenderTexture(tex_knob, knob_pos, knob_sizes);
  });
  EndSlider();
  EndGroup();
  return event;
}

Event ScrollBar(const Texture &tex_background, const Texture &tex_foreground,
                const vec2 &size, float bar_size, const char *id,
                float *scroll_value) {
  StartGroup(kLayoutHorizontalBottom, 0, id);
  Direction direction;
  int32_t dimension;
  if (size.y < size.x) {
    direction = kDirHorizontal;
    dimension = 0;
  } else {
    direction = kDirVertical;
    dimension = 1;
  }
  float margin = size[dimension] * bar_size * 0.5f;
  StartSlider(direction, margin, scroll_value);

  auto event = CheckEvent();
  // Show focus area when controled by a gamepad.
  if (!IsLastEventPointerType()) EventBackground(event);
  CustomElement(size, id, [&tex_foreground, &tex_background, bar_size,
                           scroll_value, dimension, margin](
                              const vec2i &pos, const vec2i &render_size) {
    // Set up the bar position and size.
    auto bar_render_pos = pos;
    bar_render_pos[dimension] += static_cast<int>(
        *scroll_value * (render_size[dimension] - margin * 2.0f * GetScale()));

    vec2i bar_render_size(render_size.x, render_size.y);
    bar_render_size[dimension] =
        static_cast<int>(bar_render_size[dimension] * bar_size);

    RenderTextureNinePatch(tex_background, vec4(0.5f, 0.5f, 0.5f, 0.5f), pos,
                           render_size);
    RenderTextureNinePatch(tex_foreground, vec4(0.5f, 0.5f, 0.5f, 0.5f),
                           bar_render_pos, bar_render_size);
  });
  EndSlider();
  EndGroup();
  return event;
}

Event CollapsibleGroup(const char *label, float ysize, const Margin &margin,
                       const char *id, const std::function<void()> &contents,
                       bool *expand) {
  StartGroup(kLayoutVerticalLeft, 0, id);
  SetMargin(margin);
  Event event = TextButton(label, ysize, margin);
  if (event & kEventWentUp) {
    *expand = !*expand;
  }
  EndGroup();

  if (*expand) {
    contents();
  }

  return event;
}

}  // namespace flatui


<|start_filename|>include/flatui/internal/flatui_layout.h<|end_filename|>
// Copyright 2015 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// This file contains the implementation of the layout algorithm of FlatUI.
// It is mostly used internally by FlatUI, but can possibly also be useful
// standalone if you need to layout something independent of the rendering &
// input portions of FlatUI, e.g.:
//
// LayoutManager lm;
// lm.Run([&]() {
//   lm.StartGroup(..);
//     lm.Element(vec2(100, 20), "myid",
//       [](const vec2i &pos, const vec2i &size) { /* render rect */ });
//   lm.EndGroup(..);
// });
//
// See FlatUI documentation for more information on these functions.

#ifndef FPL_FLATUI_LAYOUT_H
#define FPL_FLATUI_LAYOUT_H

#include "flatui/flatui_core.h"
#include "flatui/version.h"
#include "flatui/internal/flatui_util.h"

namespace flatui {

using mathfu::vec2;
using mathfu::vec2i;
using mathfu::vec3;
using mathfu::vec3i;
using mathfu::vec4;
using mathfu::vec4i;
using mathfu::mat4;

// This holds the transient state of a group while its layout is being
// calculated / rendered.
class Group {
 public:
  Group()
      : direction_(kDirHorizontal),
        align_(kAlignTop),
        spacing_(0),
        size_(mathfu::kZeros2i),
        position_(mathfu::kZeros2i),
        element_idx_(0),
        margin_(mathfu::kZeros4i) {}

  Group(Direction direction, Alignment align, int spacing, size_t element_idx)
      : direction_(direction),
        align_(align),
        spacing_(spacing),
        size_(mathfu::kZeros2i),
        position_(mathfu::kZeros2i),
        element_idx_(element_idx),
        margin_(mathfu::kZeros4i) {}

  // Extend this group with the size of a new element, and possibly spacing
  // if it wasn't the first element.
  void Extend(const vec2i &extension) {
    switch (direction_) {
      case kDirHorizontal:
        size_ = vec2i(size_.x + extension.x + (size_.x ? spacing_ : 0),
                      std::max(size_.y, extension.y));
        break;
      case kDirVertical:
        size_ = vec2i(std::max(size_.x, extension.x),
                      size_.y + extension.y + (size_.y ? spacing_ : 0));
        break;
      case kDirOverlay:
        size_ = vec2i(std::max(size_.x, extension.x),
                      std::max(size_.y, extension.y));
        break;
    }
  }

  Direction direction_;
  Alignment align_;
  int spacing_;
  vec2i size_;
  vec2i position_;
  size_t element_idx_;
  vec4i margin_;
};

// We create one of these per GUI element, so new fields should only be
// added when absolutely necessary.
struct UIElement {
  UIElement(const vec2i &_size, HashedId _hash)
      : size(_size),
        extra_size(mathfu::kZeros2i),
        hash(_hash),
        interactive(false) {}
  vec2i size;        // Minimum on-screen size computed by layout pass.
  vec2i extra_size;  // Additional size in a scrolling area (TODO: remove?)
  HashedId hash;     // From id specified by the user.
  bool interactive;  // Wants to respond to user input.
};

// This holds the transient state while a layout is being performed.
// Call Run() on an instance to layout a definition.
// See the FlatUI documentation (or flatui.h) for a more extensive explanation
// of how this works.
class LayoutManager : public Group {
 public:
  LayoutManager(const vec2i &canvas_size)
      : Group(kDirVertical, kAlignLeft, 0, 0),
        layout_pass_(true),
        canvas_size_(canvas_size),
        virtual_resolution_(FLATUI_DEFAULT_VIRTUAL_RESOLUTION),
        version_(&MainVersion()) {
    SetScale();
  }

  // Changes the virtual resolution (defaults to
  // FLATUI_DEFAULT_VIRTUAL_RESOLUTION).
  // All floating point sizes below for elements are in terms of this
  // resolution, which will then be converted to physical (pixel) based integer
  // coordinates during layout.
  void SetVirtualResolution(float virtual_resolution) {
    if (layout_pass_) {
      virtual_resolution_ = virtual_resolution;
      SetScale();
    }
  }

  vec2 GetVirtualResolution() const {
    return vec2(canvas_size_) / pixel_scale_;
  }

  // Helpers to convert entire vectors between virtual and physical coordinates.
  template <int D>
  mathfu::Vector<int, D> VirtualToPhysical(const mathfu::Vector<float, D> &v)
      const {
    return mathfu::Vector<int, D>(v * pixel_scale_ + 0.5f);
  }

  template <int D>
  mathfu::Vector<float, D> PhysicalToVirtual(const mathfu::Vector<int, D> &v)
      const {
    return mathfu::Vector<float, D>(v) / pixel_scale_;
  }

  // Retrieve the scaling factor for the virtual resolution.
  float GetScale() const { return pixel_scale_; }

  // Determines placement for the UI as a whole inside the available space
  // (screen).
  void PositionGroup(Alignment horizontal, Alignment vertical,
                     const vec2 &offset) {
    if (!layout_pass_) {
      auto space = canvas_size_ - size_;
      position_ = AlignDimension(horizontal, 0, space) +
                  AlignDimension(vertical, 1, space) +
                  VirtualToPhysical(offset);
    }
  }

  // Switch from the layout pass to the second pass (for positioning and
  // rendering etc).
  // Layout happens in two passes, where the first computes the sizes of
  // things, and the second assigns final positions based on that. As such,
  // you define your layout using a function (where you call StartGroup /
  // EndGroup / ELement etc.) which you call once before and once
  // after this function.
  // See the implementation of Run() below.
  bool StartSecondPass() {
    // If you hit this assert, you are missing an EndGroup().
    assert(!group_stack_.size());

    // Do nothing if there is no elements.
    if (elements_.size() == 0) return false;

    // Put in a sentinel element. We'll use this element to point to
    // when a group didn't exist during layout but it does during rendering.
    NewElement(mathfu::kZeros2i, kNullHash);

    position_ = mathfu::kZeros2i;
    size_ = elements_[0].size;

    layout_pass_ = false;
    element_it_ = elements_.begin();

    return true;
  }

  // Set the margin for the current group.
  void SetMargin(const Margin &margin) {
    margin_ = VirtualToPhysical(vec4(margin.borders));
  }

  // Generic element with user supplied renderer.
  void Element(
      const vec2 &virtual_size, const char *id,
      const std::function<void(const vec2i &pos, const vec2i &size)> renderer) {
    Element(virtual_size, HashId(id), renderer);
  }

  // Same, but using an already computed hash.
  void Element(
      const vec2 &virtual_size, HashedId hash,
      const std::function<void(const vec2i &pos, const vec2i &size)> renderer) {
    if (layout_pass_) {
      auto size = VirtualToPhysical(virtual_size);
      NewElement(size, hash);
      Extend(size);
    } else {
      auto element = NextElement(hash);
      if (element) {
        const vec2i pos = Position(*element);
        if (renderer) {
          renderer(pos, element->size);
        }
        Advance(element->size);
      }
    }
  }

  // An element that has sub-elements.
  void StartGroup(const Group &group, HashedId hash) {
    StartGroup(group.direction_, group.align_,
               static_cast<float>(group.spacing_), hash);
  }

  // An element that has sub-elements. Tracks its state in an instance of
  // Layout, that is pushed/popped from the stack as needed.
  void StartGroup(Direction direction, Alignment align, float spacing,
                  HashedId hash) {
    Group layout(direction, align, static_cast<int>(spacing), elements_.size());
    group_stack_.push_back(*this);
    if (layout_pass_) {
      NewElement(mathfu::kZeros2i, hash);
    } else {
      auto element = NextElement(hash);
      if (element) {
        layout.position_ = Position(*element);
        layout.size_ = element->size;
        // Make layout refer to element it originates from, iterator points
        // to next element after the current one.
        layout.element_idx_ = element_it_ - elements_.begin() - 1;
      } else {
        // This group did not exist during layout, but since all code inside
        // this group will run, it is important to have a valid element_idx_
        // to refer to, so we point it to our (empty) sentinel element:
        layout.element_idx_ = elements_.size() - 1;
      }
    }
    *static_cast<Group *>(this) = layout;
  }

  // Clean up the Group element started by StartGroup()
  void EndGroup() {
    // If you hit this assert, you have one too many EndGroup().
    assert(group_stack_.size());

    auto size = size_;
    auto margin = margin_.xy() + margin_.zw();
    auto element_idx = element_idx_;
    *static_cast<Group *>(this) = group_stack_.back();
    group_stack_.pop_back();
    if (layout_pass_) {
      size += margin;
      // Contribute the size of this group to its parent.
      Extend(size);
      // Set the size of this group as the size of the element tracking it.
      elements_[element_idx].size = size;
    } else {
      Advance(elements_[element_idx].size);
    }
  }

  // Return the position for the current enclosing StartGroup/EndGroup.
  const vec2i &GroupPosition() const { return position_; }

  // Return the size for the current enclosing StartGroup/EndGroup.
  vec2i GroupSize() const { return size_ + elements_[element_idx_].extra_size; }

  // Run two passes of layout.
  void Run(const std::function<void()> &layout_definition) {
    layout_definition();
    StartSecondPass();
    layout_definition();
  }

 protected:
  // (second pass): retrieve the next corresponding cached element we
  // created in the layout pass. This is slightly more tricky than a straight
  // lookup because event handlers may insert/remove elements.
  UIElement *NextElement(HashedId hash) {
    assert(!layout_pass_);
    auto backup = element_it_;
    while (element_it_ != elements_.end()) {
      // This loop usually returns on the first iteration, the only time it
      // doesn't is if an event handler caused an element to removed.
      auto &element = *element_it_;
      ++element_it_;
      if (EqualId(element.hash, hash)) return &element;
    }
    // Didn't find this id at all, which means an event handler just caused
    // this element to be added, so we skip it.
    element_it_ = backup;
    return nullptr;
  }

  // (layout pass): create a new element.
  void NewElement(const vec2i &size, HashedId hash) {
    assert(layout_pass_);
    elements_.push_back(UIElement(size, hash));
  }

  // (second pass): move the group's current position past an element of
  // the given size.
  void Advance(const vec2i &size) {
    assert(!layout_pass_);
    switch (direction_) {
      case kDirHorizontal:
        position_ += vec2i(size.x + spacing_, 0);
        break;
      case kDirVertical:
        position_ += vec2i(0, size.y + spacing_);
        break;
      case kDirOverlay:
        // Keep at starting position.
        break;
    }
  }

  // (second pass): return the top-left position of the current element, as a
  // function of the group's current position and the alignment.
  vec2i Position(const UIElement &element) const {
    assert(!layout_pass_);
    auto pos = position_ + margin_.xy();
    auto space = size_ - element.size - margin_.xy() - margin_.zw();
    switch (direction_) {
      case kDirHorizontal:
        pos += AlignDimension(align_, 1, space);
        break;
      case kDirVertical:
        pos += AlignDimension(align_, 0, space);
        break;
      case kDirOverlay:
        pos += AlignDimension(align_, 0, space);
        pos += AlignDimension(align_, 1, space);
        break;
    }
    return pos;
  }

 private:
  // Compute a space offset for a particular alignment for just the x or y
  // dimension.
  static vec2i AlignDimension(Alignment align, int dim, const vec2i &space) {
    vec2i dest(0, 0);
    switch (align) {
      case kAlignTop:  // Same as kAlignLeft.
        break;
      case kAlignCenter:
        dest[dim] += space[dim] / 2;
        break;
      case kAlignBottom:  // Same as kAlignRight.
        dest[dim] += space[dim];
        break;
    }
    return dest;
  }

  // Initialize the scaling factor for the virtual resolution.
  void SetScale() {
    vec2 scale = vec2(canvas_size_) / virtual_resolution_;
    pixel_scale_ = std::min(scale.x, scale.y);
  }

 protected:
  bool layout_pass_;
  std::vector<UIElement> elements_;
  std::vector<UIElement>::iterator element_it_;
  std::vector<Group> group_stack_;
  vec2i canvas_size_;
  float virtual_resolution_;
  float pixel_scale_;
  const FlatUIVersion *version_;
};

}  // namespace flatui

#endif  // FPL_FLATUI_LAYOUT_H


<|start_filename|>src/font_util.cpp<|end_filename|>
// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "precompiled.h"

#include <cctype>
#include <sstream>

#if defined(FLATUI_HAS_GUMBO)
#include <gumbo.h>
#endif  // defined(FLATUI_HAS_GUMBO)

#include "flatui.h"
#include "font_manager.h"
#include "font_util.h"
#include "fplbase/fpl_common.h"

namespace flatui {

using mathfu::vec2;
using mathfu::vec3;
using mathfu::vec2_packed;
using mathfu::vec3_packed;

// Replace whitespace with a single space.
// This emulates how HTML processes raw text fields.
// Appends trimmed text to `out`.
// Returns reference to `out`.
std::string &TrimHtmlWhitespace(const char *text, bool trim_leading_whitespace,
                                std::string *out) {
  const char *t = text;

  if (trim_leading_whitespace) {
    while (std::isspace(*t)) ++t;
  }

  for (;;) {
    // Output non-whitespace.
    while (!std::isspace(*t) && *t != '\0') out->push_back(*t++);

    // Break out if at end of input. Note that we do *not* trim all
    // trailing whitespace.
    if (*t == '\0') break;

    // Skip whitespace.
    while (std::isspace(*t)) ++t;

    // Insert single space to compensate for trimmed whitespace.
    out->push_back(' ');
  }
  return *out;
}

#if defined(FLATUI_HAS_GUMBO)
// Remove trailing whitespace. Then add a `prefix` if there is any preceeding
// text.
static std::string &StartHtmlLine(const char *prefix, std::string *out) {
  // Trim trailing whitespace.
  while (!out->empty() && std::isspace(out->back())) out->pop_back();

  // Append the new lines in `prefix`.
  if (!out->empty()) {
    out->append(prefix);
  }
  return *out;
}

// The very first text output we want to trim the leading whitespace.
// We should also trim if the previous text ends in whitespace.
static bool ShouldTrimLeadingWhitespace(const std::vector<HtmlSection> &s) {
  if (s.empty()) return true;
  if (s.back().text().empty() && s.size() < 2) return true;

  const std::string &prev_text =
      s.back().text().empty() ? s[s.size() - 2].text() : s.back().text();
  return std::isspace(prev_text.back());
}

static bool ParseColorValue(const char *value, uint32_t *color) {
  // We only support a hex formatted parameter.
  if (*value++ != '#') {
    return false;
  }
  uint32_t ret = static_cast<uint32_t>(std::strtol(value, nullptr, 16));
  if (ret >= 0xffffff) {
    return false;
  }
  // Convert RGB value to RGBA.
  ret = ret << 8 | 0xff;
  *color = ret;
  return true;
}

static void GumboTreeToHtmlSections(const GumboNode *node,
                                    std::vector<HtmlSection> *s,
                                    HtmlSection *current_setting) {
  auto original_face = current_setting->face();
  auto original_color = current_setting->color();
  auto original_size = current_setting->size();

  switch (node->type) {
    // Process non-text elements, possibly recursing into child nodes.
    case GUMBO_NODE_ELEMENT: {
      const GumboElement &element = node->v.element;

      // Tree prefix processing.
      switch (element.tag) {
        case GUMBO_TAG_A: {
          // Record the link address.
          GumboAttribute *href =
              gumbo_get_attribute(&element.attributes, "href");
          if (href != nullptr) {
            // Start a new section for the anchor.
            if (!s->back().text().empty()) {
              s->push_back(HtmlSection());
            }
            s->back().set_link(href->value);
          }
          break;
        }
        case GUMBO_TAG_FONT: {
          // Record the properties.
          GumboAttribute *face =
              gumbo_get_attribute(&element.attributes, "face");
          GumboAttribute *color =
              gumbo_get_attribute(&element.attributes, "color");
          GumboAttribute *size =
              gumbo_get_attribute(&element.attributes, "size");

          // Start a new section for the font tag.
          bool new_params =
              face != nullptr || color != nullptr || size != nullptr;
          if (new_params && !s->back().text().empty()) {
            s->push_back(HtmlSection());
          }
          if (face != nullptr) {
            // Keep current setting and update font face setting.
            original_face = current_setting->face();
            s->back().set_face(face->value);
            current_setting->set_face(face->value);
          } else {
            s->back().set_face(current_setting->face());
          }
          if (color != nullptr) {
            uint32_t color_value = kDefaultColor;
            if (ParseColorValue(color->value, &color_value)) {
              // Keep current setting and update font face setting.
              original_color = current_setting->color();
              s->back().set_color(color_value);
              current_setting->set_color(color_value);
            } else {
              LogInfo("Failed to parse a value: %s", color->value);
            }
          } else {
            s->back().set_color(current_setting->color());
          }
          if (size != nullptr) {
            auto value =
                static_cast<int>(std::strtol(size->value, nullptr, 10));
            if (value) {
              // Convert the virtual size to physical size.
              value = VirtualToPhysical(vec2(0, value)).y;

              // Keep current setting and update font face setting.
              original_size = current_setting->size();
              s->back().set_size(value);
              current_setting->set_size(value);
            } else {
              LogInfo("Failed to parse a value: %s", size->value);
            }
          } else {
            s->back().set_size(current_setting->size());
          }
          break;
        }
        case GUMBO_TAG_P:
        case GUMBO_TAG_H1:  // fallthrough
        case GUMBO_TAG_H2:  // fallthrough
        case GUMBO_TAG_H3:  // fallthrough
        case GUMBO_TAG_H4:  // fallthrough
        case GUMBO_TAG_H5:  // fallthrough
        case GUMBO_TAG_H6:  // fallthrough
          StartHtmlLine("\n\n", &s->back().text());
          break;

        default:
          break;
      }

      // Tree children processing via recursion.
      for (unsigned int i = 0; i < element.children.length; ++i) {
        const GumboNode *child =
            static_cast<GumboNode *>(element.children.data[i]);
        GumboTreeToHtmlSections(child, s, current_setting);
      }

      // Tree postfix processing.
      switch (element.tag) {
        case GUMBO_TAG_A:
          // Start a new section for the non-anchor.
          s->push_back(HtmlSection());
          break;
        case GUMBO_TAG_FONT: {
          // Restore the font setting if it's changed.
          bool new_params = s->back().face() != original_face ||
                            s->back().color() != original_color ||
                            s->back().size() != original_size;
          if (new_params) {
            s->push_back(HtmlSection());
          }

          // Restore settings.
          current_setting->set_face(original_face);
          s->back().set_face(original_face);
          current_setting->set_color(original_color);
          s->back().set_color(original_color);
          current_setting->set_size(original_size);
          s->back().set_size(original_size);
          break;
        }
        case GUMBO_TAG_HR:
        case GUMBO_TAG_P:  // fallthrough
          s->back().text().append("\n\n");
          break;

        case GUMBO_TAG_H1:  // fallthrough
        case GUMBO_TAG_H2:  // fallthrough
        case GUMBO_TAG_H3:  // fallthrough
        case GUMBO_TAG_H4:  // fallthrough
        case GUMBO_TAG_H5:  // fallthrough
        case GUMBO_TAG_H6:  // fallthrough
        case GUMBO_TAG_BR:
          s->back().text().append("\n");
          break;

        default:
          break;
      }
      break;
    }

    // Append text without excessive whitespaces.
    case GUMBO_NODE_TEXT:
      TrimHtmlWhitespace(node->v.text.text, ShouldTrimLeadingWhitespace(*s),
                         &s->back().text());
      break;

    // Append a single whitespace where appropriate.
    case GUMBO_NODE_WHITESPACE:
      if (!ShouldTrimLeadingWhitespace(*s)) {
        s->back().text().append(" ");
      }
      break;

    // Ignore other node types.
    default:
      break;
  }
}
#endif  // defined(FLATUI_HAS_GUMBO)

bool ParseHtml(const char *html, std::vector<HtmlSection> *s) {
#if defined(FLATUI_HAS_GUMBO)
  // Ensure there is an HtmlSection that can be appended to.
  assert(s->empty());
  s->push_back(HtmlSection());

  // Parse html into tree, with Gumbo, then process the tree.
  GumboOutput *gumbo = gumbo_parse(html);
  HtmlSection current_setting;
  GumboTreeToHtmlSections(gumbo->root, s, &current_setting);
  gumbo_destroy_output(&kGumboDefaultOptions, gumbo);

  // Prune empty last section.
  if (s->back().text().empty()) {
    s->pop_back();
  }
  return true;
#else
  (void)html;
  (void)s;
  return false;
#endif  // defined(FLATUI_HAS_GUMBO)
}

static size_t NumUnderlineVertices(const FontBuffer &buffer) {
  const int32_t kUnderlineVerticesPerGlyph = 2;
  const int32_t kExtraUnderlineVerticesPerInfo = 2;
  size_t num_verts = 0;
  auto &slices = buffer.get_slices();
  for (size_t i = 0; i < slices.size(); ++i) {
    auto regions = slices.at(i).get_underline_info();
    for (size_t i = 0; i < regions.size(); ++i) {
      auto info = regions[i];
      num_verts += (info.end_vertex_index_ - info.start_vertex_index_ + 2) *
                   kUnderlineVerticesPerGlyph;
      num_verts += kExtraUnderlineVerticesPerInfo;
    }
  }
  if (num_verts) {
    // Subtract the amount of the first strip which doesn't need a triangle to
    // stitch.
    num_verts -= kExtraUnderlineVerticesPerInfo;
  }
  return num_verts;
}

std::vector<vec3_packed> GenerateUnderlineVertices(const FontBuffer &buffer,
                                                   const mathfu::vec2 &pos,
                                                   bool reverse) {
  std::vector<vec3_packed> vec;
  auto num_verts = NumUnderlineVertices(buffer);
  if (!num_verts) {
    return vec;
  }
  vec.reserve(num_verts);
  auto &slices = buffer.get_slices();
  auto &vertices = buffer.get_vertices();
  bool degenerated_triangle = false;
  for (size_t i = 0; i < slices.size(); ++i) {
    if (slices.at(i).get_underline()) {
      // Generate underline strips.
      auto regions = slices.at(i).get_underline_info();
      for (size_t i = 0; i < regions.size(); ++i) {
        auto info = regions[i];
        auto y_start = info.y_pos_.x + pos.y;
        auto y_end = y_start + info.y_pos_.y;
        int start_vertex_index = info.start_vertex_index_;
        int end_vertex_index = info.end_vertex_index_;
        const int index_count = end_vertex_index - start_vertex_index;
        int index_direction = 1;
        if (reverse) {
          std::swap(start_vertex_index, end_vertex_index);
          index_direction = -1;
        }

        if (degenerated_triangle) {
          // Add degenerated triangle to connect multiple strips.
          auto start_pos = vec3(vertices.at(start_vertex_index *
                                            kVerticesPerGlyph).position_);
          vec.push_back(vec.back());
          vec.push_back(vec3_packed(vec3(start_pos.x + pos.x, y_start, 0.f)));
        }

        // Add vertices.
        for (int i = 0; i <= index_count; ++i) {
          int idx = start_vertex_index + i * index_direction;
          auto strip_pos = vec3(vertices.at(idx * kVerticesPerGlyph).position_);
          vec.push_back(vec3_packed(vec3(strip_pos.x + pos.x, y_start, 0.f)));
          vec.push_back(vec3_packed(vec3(strip_pos.x + pos.x, y_end, 0.f)));
        }

        // Add last 2 vertices.
        auto end_pos =
            vec3(vertices.at(end_vertex_index * kVerticesPerGlyph +
                             kVerticesPerGlyph - 1).position_);
        vec.push_back(vec3_packed(vec3(end_pos.x + pos.x, y_start, 0.f)));
        vec.push_back(vec3_packed(vec3(end_pos.x + pos.x, y_end, 0.f)));

        degenerated_triangle = true;
      }
    }
  }
  assert(num_verts == vec.size());
  return vec;
}

static void NumPaddedUnderlineVertices(const FontBuffer &buffer,
                                       size_t* num_verts, size_t* num_indices) {
  *num_verts = 0;
  *num_indices = 0;
  const int32_t kUnderlineVerticesPerGlyph = 2;
  const int32_t kUnderlineIndicesPerGlyph = 6;
  const auto& slices = buffer.get_slices();
  for (size_t i = 0; i < slices.size(); ++i) {
    const auto& regions = slices[i].get_underline_info();
    for (size_t i = 0; i < regions.size(); ++i) {
      const auto& info = regions[i];
      const auto num_glyphs =
          info.end_vertex_index_ - info.start_vertex_index_ + 1;
      *num_verts += (num_glyphs + 1) * kUnderlineVerticesPerGlyph;
      *num_indices += num_glyphs * kUnderlineIndicesPerGlyph;
    }
  }
}

bool GeneratePaddedUnderlineVertices(
    const FontBuffer &buffer, const mathfu::vec2 &pos,
    const mathfu::vec2 &padding, std::vector<mathfu::vec3>* positions,
    std::vector<mathfu::vec2>* tex_coords, std::vector<uint16_t>* indices,
    bool reverse) {
  size_t num_verts, num_indices;
  NumPaddedUnderlineVertices(buffer, &num_verts, &num_indices);
  if (num_verts == 0 || num_indices == 0) {
    return false;
  }
  positions->clear();
  positions->reserve(num_verts);
  tex_coords->clear();
  tex_coords->reserve(num_verts);
  indices->clear();
  indices->reserve(num_indices);

  const int32_t kUnderlineVerticesPerGlyph = 2;
  const auto& slices = buffer.get_slices();
  const auto& vertices = buffer.get_vertices();
  for (size_t i = 0; i < slices.size(); ++i) {
    const auto& regions = slices[i].get_underline_info();
    for (size_t i = 0; i < regions.size(); ++i) {
      const auto& info = regions[i];
      // Positions where tex_coord will be 0 or 1.
      const float tex_y_start = pos.y + info.y_pos_.x;
      const float tex_y_end = tex_y_start + info.y_pos_.y;
      // Positions after adding padding.
      const float y_start = tex_y_start - padding.y;
      const float y_end = tex_y_end + padding.y;
      int start_vertex_index = info.start_vertex_index_;
      int end_vertex_index = info.end_vertex_index_;
      const int num_glyphs = end_vertex_index - start_vertex_index + 1;
      int index_direction = 1;
      if (reverse) {
        std::swap(start_vertex_index, end_vertex_index);
        index_direction = -1;
      }
      const size_t start_index = positions->size();

      // Add first 2 vertices.
      const float tex_x_start =
          vertices[start_vertex_index * kVerticesPerGlyph].position_.x + pos.x;
      positions->emplace_back(tex_x_start - padding.x, y_start, 0.f);
      positions->emplace_back(tex_x_start - padding.x, y_end, 0.f);

      // Add middle vertices.
      for (int i = 1; i < num_glyphs; ++i) {
        const int idx = start_vertex_index + i * index_direction;
        const vec3_packed& strip_pos =
            vertices[idx * kVerticesPerGlyph].position_;
        positions->emplace_back(strip_pos.x + pos.x, y_start, 0.f);
        positions->emplace_back(strip_pos.x + pos.x, y_end, 0.f);
      }

      // Add last 2 vertices.
      const int end_idx =
          (end_vertex_index + 1) * kVerticesPerGlyph + kVertexOfRightEdge;
      const float tex_x_end = vertices[end_idx].position_.x + pos.x;
      positions->emplace_back(tex_x_end + padding.x, y_start, 0.f);
      positions->emplace_back(tex_x_end + padding.x, y_end, 0.f);

      // Add tex_coords for every position, such that tex_x/y_start is (0,0) and
      // tex_x/y_end is (1,1).
      const float y_size = tex_y_end - tex_y_start;
      const float x_size = tex_x_end - tex_x_start;
      while (tex_coords->size() < positions->size()) {
        const auto& p = positions->at(tex_coords->size());
        tex_coords->emplace_back((p.x - tex_x_start) / x_size,
                                 (p.y - tex_y_start) / y_size);
      }

      // Add indices.
      for (int i = 0; i < num_glyphs; ++i) {
        const uint16_t index =
            static_cast<uint16_t>(start_index + i * kUnderlineVerticesPerGlyph);
        indices->emplace_back(index);
        indices->emplace_back(static_cast<uint16_t>(index + 1));
        indices->emplace_back(static_cast<uint16_t>(index + 2));

        // Keep same winding order.
        indices->emplace_back(static_cast<uint16_t>(index + 2));
        indices->emplace_back(static_cast<uint16_t>(index + 1));
        indices->emplace_back(static_cast<uint16_t>(index + 3));
      }
    }
  }
  assert(num_verts == positions->size());
  assert(num_verts == tex_coords->size());
  assert(num_indices == indices->size());
  return true;
}

}  // namespace flatui


<|start_filename|>jni/libs/harfbuzz/Android.mk<|end_filename|>
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

LOCAL_PATH:=$(call my-dir)

# Project directory relative to this file.
FLATUI_RELATIVE_DIR:=../../..
FLATUI_DIR:=$(LOCAL_PATH)/$(FLATUI_RELATIVE_DIR)
include $(FLATUI_DIR)/jni/android_config.mk

# realpath-portable from android_config.mk
LOCAL_PATH:=$(call realpath-portable,$(DEPENDENCIES_HARFBUZZ_DIR))

###########################
#
# Harfbuzz build
#
###########################

HARFBUZZ_SRC_FILES = \
    src/hb-blob.cc \
    src/hb-buffer-serialize.cc \
    src/hb-buffer.cc \
    src/hb-common.cc \
    src/hb-face.cc \
    src/hb-font.cc \
    src/hb-ft.cc \
    src/hb-ot-tag.cc \
    src/hb-set.cc \
    src/hb-shape.cc \
    src/hb-shape-plan.cc \
    src/hb-shaper.cc \
    src/hb-unicode.cc \
    src/hb-warning.cc \
    src/hb-ot-font.cc \
    src/hb-ot-layout.cc \
    src/hb-ot-map.cc \
    src/hb-ot-shape.cc \
    src/hb-ot-shape-complex-arabic.cc \
    src/hb-ot-shape-complex-default.cc \
    src/hb-ot-shape-complex-hangul.cc \
    src/hb-ot-shape-complex-hebrew.cc \
    src/hb-ot-shape-complex-indic.cc \
    src/hb-ot-shape-complex-indic-table.cc \
    src/hb-ot-shape-complex-myanmar.cc \
    src/hb-ot-shape-complex-thai.cc \
    src/hb-ot-shape-complex-tibetan.cc \
    src/hb-ot-shape-complex-use.cc \
    src/hb-ot-shape-complex-use-table.cc \
    src/hb-ot-shape-normalize.cc \
    src/hb-ot-shape-fallback.cc \
    src/hb-ot-var.cc

#############################################################
#   build the harfbuzz shared library
#
include $(CLEAR_VARS)

LOCAL_ARM_MODE := arm
LOCAL_SRC_FILES := $(HARFBUZZ_SRC_FILES)
LOCAL_CPP_EXTENSION := .cc
LOCAL_C_INCLUDES += \
    $(FLATUI_DIR)/external/include/harfbuzz \
    $(DEPENDENCIES_HARFBUZZ_DIR)/src \
    $(DEPENDENCIES_HARFBUZZ_DIR)/src/hb-ucdn \
    $(DEPENDENCIES_FREETYPE_DIR)/include
LOCAL_CFLAGS += -DHAVE_OT -DHB_NO_MT -DHB_NO_UNICODE_FUNCS
LOCAL_MODULE := libharfbuzz
LOCAL_MODULE_FILENAME := libharfbuzz
LOCAL_STATIC_LIBRARIES := libfreetype

include $(BUILD_STATIC_LIBRARY)


<|start_filename|>src/flatui.cpp<|end_filename|>
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "flatui/flatui.h"
#include <cstring>
#include "flatui/font_util.h"
#include "flatui/internal/flatui_layout.h"
#include "flatui/internal/flatui_util.h"
#include "flatui/internal/hb_complex_font.h"
#include "flatui/internal/micro_edit.h"
#include "fplbase/render_utils.h"
#include "fplbase/utilities.h"
#include "motive/engine.h"
#include "motive/const_init.h"
#include "motive/ease_in_ease_out_init.h"
#include "motive/spring_init.h"
#include "motive/vector_motivator.h"

using flatui::AnimCurveDescription;
using fplbase::Button;
using fplbase::InputSystem;
using fplbase::LogError;
using fplbase::LogInfo;
using fplbase::Mesh;
using fplbase::Shader;
using fplbase::Texture;
using motive::MotiveCurveShape;
using motive::MotivatorType;

namespace flatui {

// Enum indicating a type of shaders used during font rendering.
enum FontShaderType {
  kFontShaderTypeDefault = 0,
  kFontShaderTypeSdf,
  kFontShaderTypeColor,
  kFontShaderTypeCount,
};

// Since the largest type we currently expose in the external API is
// mathfu::vec4, our max dimensions is 4. If this changes, the code inside of
// StartAnimation and Animatable should be adjusted to use std::vectors instead
// of constant arrays.
static const int kMaxAnimationDimensions = 4;
static const float kScrollSpeedDragDefault = 2.0f;
static const float kScrollSpeedWheelDefault = 16.0f;
static const float kScrollSpeedGamepadDefault = 0.1f;
static const int32_t kDragStartThresholdDefault = 8;
static const int32_t kPointerIndexInvalid = -1;
static const int32_t kElementIndexInvalid = -1;
static const vec2i kDragStartPoisitionInvalid = vec2i(-1, -1);
#if !defined(NDEBUG)
static const uint32_t kDefaultGroupHashedId = HashId(kDefaultGroupID);
#endif

struct Anim {
  bool called_last_frame;
  motive::MotivatorNf motivator;
};

struct Sprite {
  Sprite() : sequence_number(0), called_last_frame(false), group_hash(0) {}
  Sprite(const std::function<bool(SequenceId seq)> &draw,
         SequenceId sequence_number, bool called_last_frame,
         HashedId group_hash)
      : draw(draw),
        sequence_number(sequence_number),
        called_last_frame(called_last_frame),
        group_hash(group_hash) {}
  std::function<bool(SequenceId seq)> draw;
  SequenceId sequence_number;
  bool called_last_frame;
  HashedId group_hash;
};

// When StartAnimation() is called before Animatable() has been called,
// we store the call parameters in a PendingTarget until the end of the frame.
// If Animatable() is called later that frame, we use the stored PendingTarget
// to call StartAnimation() then, to kick off the motion.
struct PendingTarget {
  PendingTarget() : dimensions(0) {}
  PendingTarget(const float *target_values_param,
                const float *target_velocities_param, int dimensions,
                const AnimCurveDescription &description)
      : dimensions(dimensions), description(description) {
    // Our expected use case is only up to 4 dimensions. If you
    // hit an issue with this assert, use a vector instead for starting
    // values and velocities and pass in vector.data() to Initialize.
    assert(0 <= dimensions && dimensions <= kMaxAnimationDimensions);
    memcpy(target_values, target_values_param,
           sizeof(target_values[0]) * dimensions);
    memcpy(target_velocities, target_velocities_param,
           sizeof(target_velocities[0]) * dimensions);
  }

  // These are exact copies of the parameters passed into StartAnimation().
  float target_values[kMaxAnimationDimensions];
  float target_velocities[kMaxAnimationDimensions];
  int dimensions;
  AnimCurveDescription description;
};

Direction GetDirection(Layout layout) {
  return static_cast<Direction>(layout & ~(kDirHorizontal - 1));
}

Alignment GetAlignment(Layout layout) {
  return static_cast<Alignment>(layout & (kDirHorizontal - 1));
}

// This holds transient state used while a GUI is being laid out / rendered.
// It is intentionally hidden from the interface.
// It is implemented as a singleton that the GUI element functions can access.

class InternalState;
InternalState *state = nullptr;

class InternalState : public LayoutManager {
 public:
  InternalState(fplbase::AssetManager &assetman, FontManager &fontman,
                fplbase::InputSystem &input,
                motive::MotiveEngine *motive_engine)
      : LayoutManager(assetman.renderer().window_size()),
        default_projection_(true),
        depth_test_(false),
        matman_(assetman),
        renderer_(assetman.renderer()),
        input_(input),
        fontman_(fontman),
        motive_engine_(motive_engine),
        clip_position_(mathfu::kZeros2i),
        clip_size_(mathfu::kZeros2i),
        clip_inside_(false),
        text_outer_color_size_(0.0f),
        text_line_height_scale_(kLineHeightDefault),
        text_kerning_scale_(kKerningScaleDefault),
        glyph_flags_(kGlyphFlagsNone),
        sdf_threshold_(kSDFThresholdDefault),
        enable_hyphenation_(false),
        pointer_max_active_index_(kPointerIndexInvalid),
        gamepad_has_focus_element(false),
        default_focus_element_(kElementIndexInvalid),
        gamepad_event(kEventHover),
        latest_event_(kEventNone),
        latest_event_element_idx_(0) {
    bool flush_pointer_capture = true;
    // Cache the state of multiple pointers, so we have to do less work per
    // interactive element.
    pointer_max_active_index_ = 0;  // Mouse is always active.
    // TODO: pointer_max_active_index_ should start at kPointerIndexInvalid
    // if on a touchscreen.
    for (int i = 0; i < InputSystem::kMaxSimultanuousPointers; i++) {
      pointer_buttons_[i] = &input.GetPointerButton(i);
      clip_mouse_inside_[i] = true;
      if (pointer_buttons_[i]->is_down() || pointer_buttons_[i]->went_down() ||
          pointer_buttons_[i]->went_up()) {
        pointer_max_active_index_ = std::max(pointer_max_active_index_, i);
        flush_pointer_capture = false;
      }
    }

    for (int i = 0; i <= pointer_max_active_index_; i++) {
      pointer_pos_[i] = input_.get_pointers()[i].mousepos;
      pointer_delta_[i] = input_.get_pointers()[i].mousedelta;
    }

    // If no pointer is active, flush the pointer capture status.
    if (flush_pointer_capture) {
      persistent_.dragging_pointer_ = kPointerIndexInvalid;
      persistent_.mouse_capture_ = kNullHash;
      for (int i = 0; i < InputSystem::kMaxSimultanuousPointers; i++) {
        persistent_.pointer_element[i] = kNullHash;
      }
    }

    // If this assert hits, you likely are trying to created nested GUIs.
    assert(!state);

    state = this;

    // Load shaders ahead.
    image_shader_ = matman_.LoadShader("shaders/textured");
    assert(image_shader_);
    color_shader_ = matman_.LoadShader("shaders/color");
    assert(color_shader_);

    font_shaders_[kFontShaderTypeDefault][0].set(
        matman_.LoadShader("shaders/font"));
    font_shaders_[kFontShaderTypeDefault][1].set(
        matman_.LoadShader("shaders/font_clipping"));
    font_shaders_[kFontShaderTypeSdf][0].set(
        matman_.LoadShader("shaders/font_sdf"));
    font_shaders_[kFontShaderTypeSdf][1].set(
        matman_.LoadShader("shaders/font_clipping_sdf"));
    font_shaders_[kFontShaderTypeColor][0].set(
        matman_.LoadShader("shaders/font_color"));
    font_shaders_[kFontShaderTypeColor][1].set(
        matman_.LoadShader("shaders/font_clipping_color"));

    image_color_ = mathfu::kOnes4f;
    text_color_ = mathfu::kOnes4f;
    caret_color_ = mathfu::kOnes4f;

    scroll_speed_drag_ = kScrollSpeedDragDefault;
    scroll_speed_wheel_ = kScrollSpeedWheelDefault;
    scroll_speed_gamepad_ = kScrollSpeedGamepadDefault;
    drag_start_threshold_ =
        vec2i(kDragStartThresholdDefault, kDragStartThresholdDefault);
    current_pointer_ = kPointerIndexInvalid;

    fontman_.StartLayoutPass();

    if (!persistent_.initialized) {
      motive::ConstInit::Register();
      motive::EaseInEaseOutInit::Register();
      motive::SpringInit::Register();
      persistent_.initialized = true;
    }
  }

  ~InternalState() { state = nullptr; }

  // Override the use of a default projection matrix and canvas size.
  void UseExistingProjection(const vec2i &canvas_size) {
    canvas_size_ = canvas_size;
    default_projection_ = false;
  }

  void ApplyCustomTransform(const mat4 &imvp) {
    if (layout_pass_) {
      for (int i = 0; i <= pointer_max_active_index_; i++) {
        auto clip_pos =
            vec2(pointer_pos_[i]) / vec2(renderer_.window_size()) * 2.0f - 1.0f;
        clip_pos.y *= -1;  // Mouse coords are LH, clip space is RH.
        // Get two 3d positions at the pointer position to form a ray
        auto obj_pos1 = imvp * vec4(vec2(clip_pos), vec2(-0.5f, 1.0f));
        auto obj_pos2 = imvp * vec4(vec2(clip_pos), vec2(0.5f, 1.0f));
        // (inverse) perspective divide.
        obj_pos1 /= obj_pos1.w;
        obj_pos2 /= obj_pos2.w;
        auto ray = obj_pos2 - obj_pos1;
        // Find where the ray intersects object-space plane Z=0.
        auto t = (0.0f - obj_pos1.z) / ray.z;
        auto on_plane = t * ray.xy() + obj_pos1.xy();
        pointer_pos_[i] = vec2i(on_plane + 0.5f);
        // Back into LH UI pixels.
        pointer_pos_[i].y = canvas_size_.y - pointer_pos_[i].y;
        // TODO(wvo): transform delta relative to current pointer_pos_ ?
      }
    }
  }

  void SetDepthTest(bool enable) { depth_test_ = enable; }

  // Set up an ortho camera for all 2D elements, with (0, 0) in the top left,
  // and the bottom right the windows size in pixels.
  // This is currently hardcoded to use overlay on top of the entire GL window.
  // If that ever changes, we also need to change our use of glScissor below.
  void SetOrtho() {
    auto ortho_mat = mathfu::mat4::Ortho(
        0.0f, static_cast<float>(canvas_size_.x),
        static_cast<float>(canvas_size_.y), 0.0f, -1.0f, 1.0f);
    renderer_.set_model_view_projection(ortho_mat);
  }

  // Switch from the layout pass to the render pass.
  void StartRenderPass() {
    // Do nothing if there is no elements.
    if (!StartSecondPass()) return;

    // Update font manager if they need to upload font atlas texture.
    fontman_.StartRenderPass();

    CheckGamePadNavigation();

    if (default_projection_) SetOrtho();
  }

  void RenderQuad(Shader *sh, const vec4 &color, const vec2i &pos,
                  const vec2i &size, const vec4 &uv) {
    renderer_.set_color(color);
    renderer_.SetShader(sh);
    fplbase::RenderAAQuadAlongX(vec3(vec2(pos), 0), vec3(vec2(pos + size), 0),
                                uv.xy(), uv.zw());
  }

  void RenderQuad(Shader *sh, const vec4 &color, const vec2i &pos,
                  const vec2i &size) {
    RenderQuad(sh, color, pos, size, vec4(0, 0, 1, 1));
  }

  // An image element.
  void Image(const Texture &texture, float ysize, const char *id) {
    auto hash = HashId(id);
    if (layout_pass_) {
      auto virtual_image_size = vec2(
          texture.original_size().x * ysize / texture.original_size().y, ysize);
      // Map the size to real screen pixels, rounding to the nearest int
      // for pixel-aligned rendering.
      auto size = VirtualToPhysical(virtual_image_size);
      NewElement(size, hash);
      Extend(size);
    } else {
      auto element = NextElement(hash);
      if (element) {
        texture.Set(0);
        RenderQuad(image_shader_, image_color_, Position(*element),
                   element->size);
        Advance(element->size);
      }
    }
  }

  Event Edit(float ysize, const mathfu::vec2 &edit_size,
             TextAlignment alignment, const char *id, EditStatus *status,
             std::string *text) {
    auto hash = HashId(id);
    StartGroup(GetDirection(kLayoutHorizontalBottom),
               GetAlignment(kLayoutHorizontalBottom), 0, hash);
    EditStatus edit_status = kEditStatusNone;
    if (EqualId(persistent_.input_focus_, hash)) {
      // The widget is in edit.
      edit_status = kEditStatusInEdit;
    }

    // Check event, this marks this element as an interactive element.
    auto event = CheckEvent(false);

    // Set text color
    renderer_.set_color(text_color_);

    auto physical_label_size = VirtualToPhysical(edit_size);
    auto size = VirtualToPhysical(vec2(0, ysize));
    auto ui_text = text;
    auto edit_mode = kMultipleLines;
    // Check if the editbox is a single line editbox.
    if (physical_label_size.y == 0 || physical_label_size.y == size.y) {
      physical_label_size.y = size.y;
      edit_mode = kSingleLine;
    }
    if (edit_status && persistent_.text_edit_.GetEditingText()) {
      // Get a text from the micro editor when it's editing.
      ui_text = persistent_.text_edit_.GetEditingText();
    }
    auto parameter = FontBufferParameters(
        fontman_.GetCurrentFont()->GetFontId(), HashId(ui_text->c_str()),
        static_cast<float>(size.y), physical_label_size, alignment,
        glyph_flags_, edit_status == kEditStatusInEdit, false,
        enable_hyphenation_,
        fontman_.GetLayoutDirection() == kTextLayoutDirectionRTL,
        text_kerning_scale_, text_line_height_scale_);
    auto buffer =
        fontman_.GetBuffer(ui_text->c_str(), ui_text->length(), parameter);
    assert(buffer);

    // Check if the editbox is an auto expanding edit box.
    if (physical_label_size.x == 0) {
      physical_label_size.x = buffer->get_size().x;
      edit_mode = kSingleLine;
    }

    persistent_.text_edit_.SetBuffer(buffer);
    persistent_.text_edit_.SetWindowSize(physical_label_size);

    auto window = vec4i(vec2i(0, 0), physical_label_size);
    if (edit_status) {
      window = persistent_.text_edit_.GetWindow();
    }
    auto pos = Label(*buffer, parameter, window);
    if (!layout_pass_) {
      bool pick_caret = (event & kEventWentDown) != 0;
      if (EqualId(persistent_.input_focus_, hash)) {
        // The edit box is in focus. Now we can start text input.
        if (persistent_.input_capture_ != hash) {
          // Initialize the editor.
          persistent_.text_edit_.Initialize(text, edit_mode);
          persistent_.text_edit_.SetLanguage(fontman_.GetLanguage());
          persistent_.text_edit_.SetDirection(fontman_.GetLayoutDirection());
          persistent_.text_edit_.SetBuffer(buffer);
          pick_caret = true;
          CaptureInput(hash, true);
        }
        edit_status = kEditStatusInEdit;
      } else {
        edit_status = kEditStatusNone;
      }
      if (pick_caret) {
        auto caret_pos =
            persistent_.text_edit_.Pick(GetPointerPosition() - pos, 0);
        persistent_.text_edit_.SetCaret(caret_pos);
      }

      int32_t input_region_start, input_region_length;
      int32_t focus_region_start, focus_region_length;
      if (persistent_.text_edit_.GetInputRegions(
              &input_region_start, &input_region_length, &focus_region_start,
              &focus_region_length)) {
        // IME is active in the editor.
        // Show some input region indicators.
        if (edit_status && input_region_length) {
          const float kInputLineWidth = 1.0f;
          const float kFocusLineWidth = 3.0f;

          // Calculate and render an input text region.
          DrawUnderline(*buffer, input_region_start, input_region_length, pos,
                        static_cast<float>(size.y), kInputLineWidth);

          // Calculate and render a focus text region inside the input text.
          if (focus_region_length) {
            DrawUnderline(*buffer, focus_region_start, focus_region_length, pos,
                          static_cast<float>(size.y), kFocusLineWidth);
          }

          // Specify IME rect to input system.
          auto ime_rect = pos + buffer->GetCaretPosition(input_region_start);
          auto ime_size = pos +
                          buffer->GetCaretPosition(input_region_start +
                                                   input_region_length) -
                          ime_rect;
          if (focus_region_length) {
            ime_rect = pos + buffer->GetCaretPosition(focus_region_start);
            ime_size = pos +
                       buffer->GetCaretPosition(focus_region_start +
                                                focus_region_length) -
                       ime_rect;
          }
          vec4 rect;
          rect.x = static_cast<float>(ime_rect.x);
          rect.y = static_cast<float>(ime_rect.y);
          rect.z = static_cast<float>(ime_size.x);
          rect.w = static_cast<float>(ime_size.y);
          input_.SetTextInputRect(rect);
        }
      }

      if (edit_status) {
        // Render caret.
        const float kCaretPositionSizeFactor = 0.8f;
        const float kCaretWidth = 2.0f;
        auto caret_pos =
            buffer->GetCaretPosition(persistent_.text_edit_.GetCaretPosition());
        auto caret_height = size.y * kCaretPositionSizeFactor;
        if (caret_pos.x >= window.x - kCaretWidth &&
            caret_pos.x <= window.x + window.z + kCaretWidth &&
            caret_pos.y >= window.y &&
            caret_pos.y - caret_height <= window.y + window.w) {
          caret_pos += pos;
          // Caret Y position is at the base line, add some offset.
          caret_pos.y -= static_cast<int>(caret_height);

          auto caret_size = VirtualToPhysical(vec2(kCaretWidth, ysize));
          RenderCaret(caret_pos, caret_size);
        }

        // Handle text input events only after the rendering for the pass is
        // finished.
        edit_status = persistent_.text_edit_.HandleInputEvents(
            input_.GetTextInputEvents());
        input_.ClearTextInputEvents();
        if (edit_status == kEditStatusFinished ||
            edit_status == kEditStatusCanceled) {
          CaptureInput(kNullHash, true);
        }
      }
    }
    EndGroup();
    if (status) {
      *status = edit_status;
    }
    return event;
  }

  // Helper for Edit widget to draw an underline.
  void DrawUnderline(const FontBuffer &buffer, int32_t start, int32_t length,
                     const vec2i &pos, float font_size, float line_width) {
    const float kUnderlineOffsetFactor = 0.2f;

    auto startpos = buffer.GetCaretPosition(start);
    auto size = buffer.GetCaretPosition(start + length) - startpos;
    startpos.y += static_cast<int>(font_size * kUnderlineOffsetFactor);
    size.y += static_cast<int>(line_width);

    RenderQuad(color_shader_, mathfu::kOnes4f, pos + startpos, size);
  }

  // Helper for Edit widget to render a caret.
  void RenderCaret(const vec2i &caret_pos, const vec2i &caret_size) {
    if (persistent_.text_edit_.ShowCaret(input_.DeltaTime())) {
      RenderQuad(color_shader_, caret_color_, caret_pos, caret_size);
    }
  }

  FontBufferParameters CalculateLabelFontBufferParameters(
      const char *text, float ysize, const vec2 &label_size,
      TextAlignment alignment, uint32_t hash_id) const {
    auto physical_label_size = VirtualToPhysical(label_size);
    auto size = VirtualToPhysical(vec2(0, ysize));
    return FontBufferParameters(
        fontman_.GetCurrentFont()->GetFontId(), HashId(text),
        static_cast<float>(size.y), physical_label_size, alignment,
        glyph_flags_, false, false, enable_hyphenation_,
        fontman_.GetLayoutDirection() == kTextLayoutDirectionRTL,
        text_kerning_scale_, text_line_height_scale_, hash_id);
  }

  void Label(const char *text, float ysize, const vec2 &label_size,
             TextAlignment alignment, HashedId label_id = kNullHash) {
    auto parameter = CalculateLabelFontBufferParameters(text, ysize, label_size,
                                                        alignment, kNullHash);

    // Set text color.
    renderer_.set_color(text_color_);
    auto buffer = fontman_.GetBuffer(text, strlen(text), parameter);
    assert(buffer);
    Label(*buffer, parameter, vec4i(vec2i(0, 0), buffer->get_size()), label_id);
  }

  void HtmlLabel(const char *html, float ysize, const mathfu::vec2 &label_size,
                 TextAlignment alignment, const char *id) {
    auto parameter = CalculateLabelFontBufferParameters(html, ysize, label_size,
                                                        alignment, HashId(id));

    // Set text color.
    renderer_.set_color(text_color_);

    auto buffer = fontman_.GetHtmlBuffer(html, parameter);
    assert(buffer);
    Label(*buffer, parameter, vec4i(vec2i(0, 0), buffer->get_size()));
  }

  void DrawFontBuffer(const FontBuffer &buffer, const vec2 &pos,
                      const mathfu::vec4 &clip_rect, bool use_sdf,
                      bool render_outer_color) {
    auto &slices = buffer.get_slices();
    auto current_format = fplbase::kFormatAuto;
    bool clipping = clip_rect.z != 0.0f && clip_rect.w != 0.0f;
    FontShader *current_shader = nullptr;
    vec4 color = mathfu::kZeros4f;

    for (size_t i = 0; i < slices.size(); ++i) {
      auto texture = fontman_.GetAtlasTexture(slices.at(i).get_slice_index());
      texture->Set(0);

      if (current_format != texture->format()) {
        current_format = texture->format();

        // Switch shaders based on drawing conditions.
        FontShaderType shader_type =
            current_format == fplbase::kFormat8888
                ? kFontShaderTypeColor
                : use_sdf ? kFontShaderTypeSdf : kFontShaderTypeDefault;

        // Color glyph doesn't support outer_color.
        if (shader_type == kFontShaderTypeColor && render_outer_color) continue;

        current_shader = &font_shaders_[shader_type][clipping ? 1 : 0];
        current_shader->set_renderer(&renderer_);

        // Set shader specific parameters.
        color = text_color_;
        current_shader->set_position_offset(vec3(pos, 0.0f));
        if (use_sdf) {
          if (render_outer_color) {
            color = text_outer_color_;
            current_shader->set_threshold(text_outer_color_size_);
          } else {
            current_shader->set_threshold(sdf_threshold_);
          }
        }
        if (clipping) {
          current_shader->set_clipping(clip_rect);
        }
      }

      if (fplbase::ValidUniformHandle(current_shader->color_handle())) {
        if (slices.at(i).get_color() != kDefaultColor) {
          // Use attributed color.
          auto c = slices.at(i).get_color();
          color = vec4(((c & 0xff000000) >> 24) * 1.0f / 255.0f,
                       ((c & 0xff0000) >> 16) * 1.0f / 255.0f,
                       ((c & 0xff00) >> 8) * 1.0f / 255.0f,
                       (c & 0xff) * 1.0f / 255.0f);
          current_shader->set_color(color);
        } else {
          // Use system set color.
          current_shader->set_color(color);
        }
      }

      const fplbase::Attribute kFormat[] = {
          fplbase::kPosition3f, fplbase::kTexCoord2f, fplbase::kEND};
      auto &indices = buffer.get_indices(static_cast<int32_t>(i));
      if (!indices.empty()) {
        RenderArray(
            Mesh::kTriangles, static_cast<int>(indices.size()), kFormat,
            sizeof(FontVertex),
            reinterpret_cast<const char *>(buffer.get_vertices().data()),
            indices.data());
      }

      if (slices.at(i).get_underline()) {
        // Draw underlines.
        auto regions = slices.at(i).get_underline_info();
        for (size_t i = 0; i < regions.size(); ++i) {
          auto info = regions[i];
          auto start_pos = buffer.get_vertices()
                               .at(info.start_vertex_index_ * kVerticesPerGlyph)
                               .position_;
          auto end_pos = buffer.get_vertices()
                             .at(info.end_vertex_index_ * kVerticesPerGlyph +
                                 kVerticesPerGlyph - 1)
                             .position_;
          auto p = vec2i(start_pos.data[0] + pos.x, info.y_pos_.x + pos.y);
          // NOTE: Use abs value for a size to account with RTL.
          auto size = vec2i(std::abs(end_pos.data[0] - start_pos.data[0]),
                            info.y_pos_.y);
          RenderQuad(color_shader_, color, p, size);
        }
        current_format = fplbase::kFormatAuto;
      }
    }
  }

  vec2i Label(const FontBuffer &buffer, const FontBufferParameters &parameter,
              const vec4i &window, HashedId label_id = kNullHash) {
    vec2i pos = mathfu::kZeros2i;
    auto hash = label_id == kNullHash ? parameter.get_text_id() : label_id;
    if (layout_pass_) {
      auto size = window.zw();
      NewElement(size, hash);
      Extend(size);
    } else {
      // Check if texture atlas needs to be updated.
      if (buffer.get_pass() > 0) {
        fontman_.StartRenderPass();
      }

      auto element = NextElement(hash);
      if (element) {
        pos = Position(*element);

        bool clipping = false;
        if (window.z && window.w) {
          clipping = window.x || window.y || (buffer.get_size().x > window.z) ||
                     (buffer.get_size().y > window.w);
        }

        if (clipping) {
          // Font rendering with a clipping.
          pos -= window.xy();

          // Set a window to show a part of the label.
          auto start = vec2(position_ - pos);
          auto end = start + vec2(window.zw());
          start.y -= buffer.metrics().internal_leading();
          end.y -= buffer.metrics().external_leading();

          if (parameter.get_glyph_flags() &
              (kGlyphFlagsInnerSDF | kGlyphFlagsOuterSDF)) {
            if (text_outer_color_size_ != 0.0f) {
              // Render shadow.
              DrawFontBuffer(buffer, vec2(pos) + text_outer_color_offset,
                             vec4(start, end), true, true);
            }
            DrawFontBuffer(buffer, vec2(pos), vec4(start, end), true, false);
          } else {
            DrawFontBuffer(buffer, vec2(pos), vec4(start, end), false, false);
          }
        } else {
          // Regular font rendering.
          if (parameter.get_glyph_flags() &
              (kGlyphFlagsInnerSDF | kGlyphFlagsOuterSDF)) {
            if (text_outer_color_size_ != 0.0f) {
              // Render shadow.
              DrawFontBuffer(buffer, vec2(pos) + text_outer_color_offset,
                             mathfu::kZeros4f, true, true);
            }
            DrawFontBuffer(buffer, vec2(pos), mathfu::kZeros4f, true, false);
          } else {
            DrawFontBuffer(buffer, vec2(pos), mathfu::kZeros4f, false, false);
          }
        }
        Advance(element->size);
      }
    }
    return pos;
  }

  // Render texture on the screen.
  void RenderTexture(const Texture &tex, const vec2i &pos, const vec2i &size) {
    RenderTexture(tex, pos, size, mathfu::kOnes4f);
  }

  // Render texture on the screen with color.
  void RenderTexture(const Texture &tex, const vec2i &pos, const vec2i &size,
                     const vec4 &color) {
    if (!layout_pass_) {
      tex.Set(0);
      RenderQuad(image_shader_, color, pos, size);
    }
  }

  void RenderTextureNinePatch(const Texture &tex, const vec4 &patch_info,
                              const vec2i &pos, const vec2i &size) {
    if (!layout_pass_) {
      tex.Set(0);
      renderer_.set_color(mathfu::kOnes4f);
      renderer_.SetShader(image_shader_);
      fplbase::RenderAAQuadAlongXNinePatch(vec3(vec2(pos), 0),
                                           vec3(vec2(pos + size), 0),
                                           tex.size(), patch_info);
    }
  }

  void ModalGroup() {
    if (group_stack_.back().direction_ == kDirOverlay) {
      // Simply mark all elements before this last group as non-interactive.
      for (size_t i = 0; i < element_idx_; i++)
        elements_[i].interactive = false;
    }
  }

  void StartScroll(const vec2 &size, vec2 *virtual_offset) {
    auto psize = VirtualToPhysical(size);
    auto offset = VirtualToPhysical(*virtual_offset);

    if (layout_pass_) {
      // If you hit this assert, you are nesting scrolling areas, which is
      // not supported.
      assert(!clip_inside_);
      clip_inside_ = true;
      // Pass this size to EndScroll.
      clip_size_ = psize;
      clip_position_ = mathfu::kZeros2i;
    } else {
      // This currently assumes an ortho camera that corresponds to all pixels
      // of the GL screen, which is exactly what Run() sets up.
      // If that ever changes, this call will have to get more complicated,
      // translating to whereever the GUI is placed, or in the case of 3D
      // placement use another technique alltogether (render to texture,
      // glClipPlane, or stencil buffer).
      assert(default_projection_);
      renderer_.ScissorOn(
          vec2i(position_.x, canvas_size_.y - position_.y - psize.y), psize);

      vec2i pointer_delta = mathfu::kZeros2i;
      int32_t scroll_speed = static_cast<int32_t>(scroll_speed_drag_);

      // Check drag event only.
      auto &element = elements_[element_idx_];
      size_ = psize;

      // Set the interactive flag, check event and restore the flag.
      auto interactive = element.interactive;
      element.interactive = true;
      auto event = CheckEvent(true);
      element.interactive = interactive;

      if (event & kEventStartDrag) {
        // Start drag.
        CapturePointer(element.hash);
      }

      if (IsPointerCaptured(element.hash)) {
        if (event & kEventEndDrag) {
          // Finish dragging and release the pointer.
          ReleasePointer();
        }
        pointer_delta = pointer_delta_[0];
      } else {
        // Wheel scroll
        if (mathfu::InRange2D(pointer_pos_[0], position_, position_ + psize)) {
          pointer_delta = input_.mousewheel_delta();
          scroll_speed = static_cast<int32_t>(-scroll_speed_wheel_);
        }
      }

      if (!IsLastEventPointerType()) {
        // Gamepad/keyboard control.
        // Note that a scroll group is not an interactive group by default.
        // If the user want to nagivate a scroll group with a game pad,
        // the user need to make a group interactive by invoking CheckEvent()
        // right after StartGroup().
        if (event & kEventWentUp) {
          // Toggle capture.
          if (!IsInputCaptured(element.hash)) {
            CaptureInput(element.hash, false);
          } else {
            CaptureInput(kNullHash, false);
          }
        }

        if (IsInputCaptured(element.hash)) {
          // Gamepad navigation.
          pointer_delta = GetNavigationDirection2D();
          pointer_delta *=
              vec2i(vec2(element.extra_size) * scroll_speed_gamepad_);
          scroll_speed = 1;
        }
      }

      // Scroll the pane on user input.
      offset = vec2i::Max(mathfu::kZeros2i,
                          vec2i::Min(elements_[element_idx_].extra_size,
                                     offset - pointer_delta * scroll_speed));

      // See if the mouse is outside the clip area, so we can avoid events
      // being triggered by elements that are not visible.
      for (int i = 0; i <= pointer_max_active_index_; i++) {
        if (!mathfu::InRange2D(pointer_pos_[i], position_, position_ + psize)) {
          clip_mouse_inside_[i] = false;
        }
      }
      // Store size/position, so expensive rendering commands can choose to
      // clip against the viewport.
      // TODO: add culling code where appropriate.
      clip_size_ = psize;
      clip_position_ = position_;
      // Start the rendering of this group at the offset before the start of
      // the window to clip against. Also makes events work correctly.
      position_ -= offset;
    }

    *virtual_offset = PhysicalToVirtual(offset);
  }

  void EndScroll() {
    if (layout_pass_) {
      // Track original size.
      elements_[element_idx_].extra_size = size_ - clip_size_;
      // Overwrite what was computed for the elements.
      size_ = clip_size_;
      clip_inside_ = false;
    } else {
      for (int i = 0; i <= pointer_max_active_index_; i++) {
        clip_mouse_inside_[i] = true;
      }
      renderer_.ScissorOff();
    }
  }

  void StartSlider(Direction direction, float scroll_margin, float *value) {
    auto event = CheckEvent(false);
    if (!layout_pass_) {
      auto hash = elements_[element_idx_].hash;
      if (event & kEventStartDrag) {
        CapturePointer(hash);
      } else if (event & kEventEndDrag) {
        ReleasePointer();
      }

      if (IsLastEventPointerType()) {
        // Update the knob position.
        if (event & kEventIsDragging || event & kEventWentDown ||
            event & kEventIsDown) {
          switch (direction) {
            case kDirHorizontal:
              *value = static_cast<float>(GetPointerPosition().x - position_.x -
                                          scroll_margin) /
                       static_cast<float>(size_.x - scroll_margin * 2.0f);
              break;
            case kDirVertical:
              *value = static_cast<float>(GetPointerPosition().y - position_.y -
                                          scroll_margin) /
                       static_cast<float>(size_.y - scroll_margin * 2.0f);
              break;
            default:
              assert(0);
          }
          // Clamp the slider value.
          *value = mathfu::Clamp(*value, 0.0f, 1.0f);
        }
      } else {
        // Gamepad/keyboard control.
        if (event & kEventWentUp) {
          // Toggle capture.
          if (!IsInputCaptured(hash)) {
            CaptureInput(hash, false);
          } else {
            CaptureInput(kNullHash, false);
          }
        }

        if (IsInputCaptured(hash)) {
          // Accept gamepad navigation.
          int dir = GetNavigationDirection();
          if (dir) {
            *value += dir * scroll_speed_gamepad_;
            *value = mathfu::Clamp(*value, 0.0f, 1.0f);
          }
        }
      }
    }
  }

  void EndSlider() {}

  // The hashmap stores the internal animation state for the API
  // call to `Animatable(id)`. To conserve memory space, the
  // internal state for `id` will be removed if `Animatable(id)`
  // was not called in the previous frame.
  void Clean() {
    for (auto it = persistent_.animations.begin();
         it != persistent_.animations.end();) {
      if (it->second.called_last_frame) {
        it->second.called_last_frame = false;
        ++it;
      } else {
        it = persistent_.animations.erase(it);
      }
    }
    for (auto it = persistent_.sprites.begin();
         it != persistent_.sprites.end();) {
      if (it->called_last_frame) {
        it->called_last_frame = false;
        ++it;
      } else {
        it = persistent_.sprites.erase(it);
      }
    }
    persistent_.pending_targets.clear();
  }

  // Return the internal animation state for the API call to
  // `Animatable(id)`. This state persists from frame to
  // frame as long as `Animatable(id)` is called every frame.
  // If no state currently exists for `id` because
  // `Animatable(id)` was not called the previous frame,
  // return nullptr.
  static Anim *FindAnim(HashedId id) {
    auto it = persistent_.animations.find(id);
    return it != persistent_.animations.end() ? &(it->second) : nullptr;
  }

  Anim *CreateAnim(HashedId id, const float *starting_values,
                   const float *starting_velocities, int dimensions) {
    Anim *anim = &persistent_.animations[id];
    anim->motivator.Initialize(
        motive::ConstInit(starting_values, starting_velocities), motive_engine_,
        dimensions);
    return anim;
  }

  // Returns the Motive animation algorithm used to animate `type`.
  static motive::MotivatorType MotivatorTypeFromAnimType(AnimType type) {
    static motive::MotivatorType kMotivatorTypeFromAnimType[] = {
        motive::EaseInEaseOutInit::kType,  // kAnimEaseInEaseOut
        motive::SpringInit::kType,         // kAnimSpring
    };
    static_assert(FPL_ARRAYSIZE(kMotivatorTypeFromAnimType) == kAnimTypeCount,
                  "Need to update kMotivatorTypeFromAnimType");
    assert(0 <= type && type < kAnimTypeCount);
    return kMotivatorTypeFromAnimType[type];
  }

  // Ensure the Motivator's type matches its animation algorithm.
  //
  // When Animatable() is first called, it uses the 'const' Motivator type,
  // since it's not moving at all. This has to be changed to the proper
  // Motivator type (e.g. 'ease-in ease-out' or 'spring') when StartAnimation()
  // is called.
  //
  // Also, it's possible to start animating with one kind of motion
  // (e.g. ease-in ease-out) but later request a different kind of animation
  // (e.g. spring).
  void MatchAnimType(AnimType type, Anim *anim) {
    // Do nothing if Motivator is already the correct type.
    const MotivatorType motivator_type = MotivatorTypeFromAnimType(type);
    if (motivator_type == anim->motivator.Type()) return;

    // Use the current state when reinitializing the Motivator, so it starts
    // from is current values and velocities.
    const motive::MotiveDimension dimensions = anim->motivator.Dimensions();
    float current_values[kMaxAnimationDimensions];
    float current_velocities[kMaxAnimationDimensions];
    memcpy(current_values, anim->motivator.Values(),
           sizeof(current_values[0]) * dimensions);
    anim->motivator.Velocities(current_velocities);

    // Currently all Inits are essentially copies of SimpleInit, so we just
    // use SimpleInit for initializing (instead of EaseInEaseOutInit or
    // SpringInit).
    assert(motivator_type == motive::EaseInEaseOutInit::kType ||
           motivator_type == motive::SpringInit::kType);
    const motive::SimpleInit init(motivator_type, current_values,
                                  current_velocities);
    anim->motivator.Initialize(init, motive_engine_, dimensions);
  }

  // Create a new Motivator if it isn't found in our hashmap and initialize it
  // with starting values.
  // Return the current value of the motivator.
  const float *Animatable(HashedId id, const float *starting_values,
                          int dimensions) {
    assert(motive_engine_ != nullptr);

    // Get or create an entry in persistent storage for this animation.
    Anim *current = FindAnim(id);
    if (!current) {
      current =
          CreateAnim(id, starting_values, mathfu::kZeros4f.data_, dimensions);

      // Apply the pending target if StartAnimation() was called before this
      // call to Animatable().
      auto &targets = persistent_.pending_targets;
      auto pending_target = targets.find(id);
      if (pending_target != targets.end()) {
        const PendingTarget &target = pending_target->second;
        StartAnimation(pending_target->first, target.target_values,
                       target.target_velocities, target.dimensions,
                       target.description);
        targets.erase(pending_target);
      }
    }

    current->called_last_frame = true;
    return current->motivator.Values();
  }

  // Set the target value and velocity to which the motivator animates.
  void StartAnimation(HashedId id, const float *target_values,
                      const float *target_velocities, const int dimensions,
                      const AnimCurveDescription &description) {
    Anim *current = FindAnim(id);
    if (!current) {
      // Create a PendingTarget, since StartAnimation() was called before
      // Animatable(). If Animatable() is not called this frame, this
      // PendingTarget will be forgotten.
      persistent_.pending_targets[id] = PendingTarget(
          target_values, target_velocities, dimensions, description);
      return;
    }
    assert(current->motivator.Dimensions() == dimensions);

    // Reinitialize Motivator if it's of the wrong type.
    MatchAnimType(description.type, current);

    // Animate the Motivator to the target, following the requested shape.
    const MotiveCurveShape target_shape(description.typical_delta_distance,
                                        description.typical_total_time,
                                        description.bias);
    current->motivator.SetTargetWithShape(target_values, target_velocities,
                                          target_shape);
  }

  // Return the time remaining until the animation asssociated with id is
  // done.
  double AnimationTimeRemaining(HashedId id) {
    Anim *current = FindAnim(id);
    if (current) {
      return static_cast<double>(current->motivator.TargetTime());
    }
    return 0.0;
  }

  int NumActiveSprites(HashedId id) {
    int num_active_sprites = 0;
    for (auto it = persistent_.sprites.begin();
         it != persistent_.sprites.end(); ++it) {
      if (it->group_hash == id) {
        num_active_sprites++;
      }
    }
    return num_active_sprites;
  }

  SequenceId AddSprite(const char *group_id,
                       const std::function<bool(SequenceId seq)> &draw) {
    assert(motive_engine_);
    HashedId group_hash = HashId(group_id);
    persistent_.sprites.push_back(
        Sprite(draw, persistent_.sprite_sequence_number, false, group_hash));
    return persistent_.sprite_sequence_number++;
  }

  void DrawSprites(const char *group_id) {
    HashedId group_hash = HashId(group_id);
    auto it = persistent_.sprites.begin();
    while (it != persistent_.sprites.end()) {
      if (it->group_hash == group_hash) {
        bool done_drawing = it->draw(it->sequence_number);
        if (done_drawing && !layout_pass_) {
          it = persistent_.sprites.erase(it);
          continue;
        }
        it->called_last_frame = true;
      }
      ++it;
    }
  }

  // Set scroll speed of the scroll group.
  // scroll_speed_drag: Scroll speed with a pointer drag operation.
  // scroll_speed_wheel: Scroll speed with a mouse wheel operation.
  // scroll_speed_gamepad: Scroll speed with a gamepad operation.
  void SetScrollSpeed(float scroll_speed_drag, float scroll_speed_wheel,
                      float scroll_speed_gamepad) {
    scroll_speed_drag_ = scroll_speed_drag;
    scroll_speed_wheel_ = scroll_speed_wheel;
    scroll_speed_gamepad_ = scroll_speed_gamepad;
  }

  // Set drag start threshold.
  // The value is used to determine if the drag operation should start after a
  // pointer WENT_DOWN event happened.
  void SetDragStartThreshold(int drag_start_threshold) {
    drag_start_threshold_ = vec2i(drag_start_threshold, drag_start_threshold);
  }

  // Capture the input to an element.
  void CaptureInput(HashedId hash, bool control_text_input) {
    persistent_.input_capture_ = hash;
    if (!EqualId(hash, kNullHash)) {
      persistent_.input_focus_ = hash;

      if (control_text_input) {
        // Start recording input events.
        if (!input_.IsRecordingTextInput()) {
          input_.RecordTextInput(true);
        }

        // Enable IME.
        input_.StartTextInput();
      }
    } else {
      // The element releases keyboard focus as well.
      persistent_.input_focus_ = kNullHash;

      if (control_text_input) {
        // Stop recording input events.
        if (input_.IsRecordingTextInput()) {
          input_.RecordTextInput(false);
        }
        // Disable IME
        input_.StopTextInput();
      }
    }
  }

  // Check if the element is capturing input events.
  bool IsInputCaptured(HashedId hash) {
    return EqualId(persistent_.input_capture_, hash);
  }

  // Capture the pointer to an element.
  // The element with element_id will continue to recieve pointer events
  // exclusively until CapturePointer() with kNullHash API is called.
  //
  // hash: an element hash that captures the pointer.
  void CapturePointer(HashedId hash) {
    persistent_.mouse_capture_ = hash;
    RecordId(hash, current_pointer_);
  }

  // Check if the element can recieve pointer events.
  // Return false if the pointer is captured by another element.
  bool CanReceivePointerEvent(HashedId hash) {
    return persistent_.mouse_capture_ == kNullHash ||
           EqualId(persistent_.mouse_capture_, hash);
  }

  // Check if the element is capturing pointer events.
  bool IsPointerCaptured(HashedId hash) {
    return EqualId(persistent_.mouse_capture_, hash);
  }

  void RecordId(HashedId hash, int i) { persistent_.pointer_element[i] = hash; }
  bool SameId(HashedId hash, int i) {
    return EqualId(hash, persistent_.pointer_element[i]);
  }

  Event FireEvent(size_t element_idx, Event e) {
    latest_event_ = e;
    latest_event_element_idx_ = element_idx;
    if (global_listener_) global_listener_(elements_[element_idx].hash, e);
    return e;
  }

  Event CheckEvent(bool check_dragevent_only) {
    if (latest_event_element_idx_ == element_idx_) return latest_event_;

    auto &element = elements_[element_idx_];
    if (layout_pass_) {
      element.interactive = true;
#if !defined(NDEBUG)
      // Sanity check for not to have default ID in an interactive element.
      // Also each interactive elements should have unique IDs
      // (not checking here for a performance reason).
      // When some elements has same ID, a game pad navigation wouldn't work
      // as expected.
      if (element.hash == kDefaultGroupHashedId) {
        LogInfo("An interactive element %d shouldn't have a default group ID.",
                element_idx_);
      }
#endif
    } else {
      // We only fire events after the layout pass.
      // Check if this is an inactive part of an overlay.
      if (element.interactive) {
        auto hash = element.hash;
        // pointer_max_active_index_ is typically 0, so loop not expensive.
        for (int i = 0; i <= pointer_max_active_index_; i++) {
          if ((CanReceivePointerEvent(hash) && clip_mouse_inside_[i] &&
               mathfu::InRange2D(pointer_pos_[i], position_,
                                 position_ + size_)) ||
              IsPointerCaptured(hash)) {
            auto &button = *pointer_buttons_[i];
            int event = 0;

            if (persistent_.dragging_pointer_ == i && !button.went_down()) {
              // The pointer is in drag operation.
              if (button.went_up()) {
                event |= kEventEndDrag;
                persistent_.dragging_pointer_ = kPointerIndexInvalid;
                persistent_.drag_start_position_ = kDragStartPoisitionInvalid;
                if (SameId(hash, i)) {
                  event |= kEventWentUp;
                }
              } else if (button.is_down()) {
                event |= kEventIsDragging;
                if (SameId(hash, i)) {
                  event |= kEventIsDown;
                }
              } else {
                persistent_.dragging_pointer_ = kPointerIndexInvalid;
              }
            } else {
              if (!check_dragevent_only) {
                // Regular pointer event handlings.
                if (button.went_down()) {
                  RecordId(hash, i);
                  event |= kEventWentDown;
                }

                if (button.went_up() && SameId(hash, i)) {
                  event |= kEventWentUp;
                } else if (button.is_down() && SameId(hash, i)) {
                  event |= kEventIsDown;

                  if (persistent_.input_focus_ != hash) {
                    // Stop input handling.
                    CaptureInput(kNullHash, true);
                    // Record the last element we received an up on, as the
                    // target for keyboard input.
                    persistent_.input_focus_ = hash;
                  }
                }
              }

              // Check for drag events.
              if (button.went_down()) {
                persistent_.drag_start_position_ = pointer_pos_[i];
              }
              if (button.is_down() &&
                  mathfu::InRange2D(persistent_.drag_start_position_, position_,
                                    position_ + size_) &&
                  !mathfu::InRange2D(
                      pointer_pos_[i],
                      persistent_.drag_start_position_ - drag_start_threshold_,
                      persistent_.drag_start_position_ +
                          drag_start_threshold_)) {
                // Start drag event.
                // Note that any element the event can recieve the drag start
                // event, so that parent layer can start a dragging operation
                // regardless if it's sub-layer is checking event.
                event |= kEventStartDrag;
                persistent_.drag_start_position_ = pointer_pos_[i];
                persistent_.dragging_pointer_ = i;
              }
            }

            if (event) persistent_.is_last_event_pointer_type = true;

            if (!event) event = kEventHover;

            gamepad_has_focus_element = true;
            current_pointer_ = i;
            // We only report an event for the first finger to touch an element.
            // This is intentional.

            return FireEvent(element_idx_, static_cast<Event>(event));
          }
        }
        // Generate events for the current element the gamepad/keyboard
        // is focused on, but only if the gamepad/keyboard is active.
        if (!persistent_.is_last_event_pointer_type &&
            EqualId(persistent_.input_focus_, hash)) {
          gamepad_has_focus_element = true;
          return FireEvent(element_idx_, gamepad_event);
        }
      }
    }
    return kEventNone;
  }

  ssize_t GetCapturedPointerIndex() { return persistent_.dragging_pointer_; }

  bool IsLastEventPointerType() {
    return persistent_.is_last_event_pointer_type;
  }

  void SetDefaultFocus() {
    // Need to keep an index rather than the hash to check if the element is
    // an interactive element later.
    default_focus_element_ = static_cast<int32_t>(element_idx_);
  }

  bool depth_test() { return depth_test_; }

  void CheckGamePadFocus() {
    if (!gamepad_has_focus_element && persistent_.input_capture_ == kNullHash) {
      // This may happen when a GUI first appears or when elements get removed.
      // TODO: only do this when there's an actual gamepad connected.
      if (default_focus_element_ != kElementIndexInvalid &&
          elements_[default_focus_element_].interactive) {
        persistent_.input_focus_ = elements_[default_focus_element_].hash;
      } else {
        persistent_.input_focus_ = NextInteractiveElement(-1, 1);
      }
    }
  }

  void CheckGamePadNavigation() {
    // Update state.
    int dir = GetNavigationDirection();

    // Gamepad/keyboard navigation only happens when the keyboard is not
    // captured.
    if (persistent_.input_capture_ != kNullHash) {
      return;
    }

    // If "back" is pressed, clear the current focus.
    if (BackPressed()) {
      CaptureInput(kNullHash, true);
    }

    // Now find the current element, and move to the next.
    if (dir) {
      for (auto it = elements_.begin(); it != elements_.end(); ++it) {
        if (EqualId(it->hash, persistent_.input_focus_)) {
          persistent_.input_focus_ = NextInteractiveElement(
              static_cast<int>(&*it - &elements_[0]), dir);
          break;
        }
      }
    }
  }

  // Returns true if the "back" (leave this level of focus) button is pressed.
  bool BackPressed() {
    bool back_pressed = false;
#ifdef ANDROID_GAMEPAD
    auto &gamepads = input_.GamepadMap();
    for (auto &gamepad : gamepads) {
      back_pressed |=
          gamepad.second.GetButton(fplbase::Gamepad::kButtonBack).is_down();
    }
#endif  // ANDROID_GAMEPAD
    // Map escape to back by default.
    back_pressed |= input_.GetButton(fplbase::FPLK_ESCAPE).is_down();
    return back_pressed;
  }

  vec2i GetNavigationDirection2D() {
    vec2i dir = mathfu::kZeros2i;
// FIXME: this should work on other platforms too.
#ifdef ANDROID_GAMEPAD
    auto &gamepads = input_.GamepadMap();
    for (auto &gamepad : gamepads) {
      dir = CheckButtons(gamepad.second.GetButton(fplbase::Gamepad::kLeft),
                         gamepad.second.GetButton(fplbase::Gamepad::kRight),
                         gamepad.second.GetButton(fplbase::Gamepad::kUp),
                         gamepad.second.GetButton(fplbase::Gamepad::kDown),
                         gamepad.second.GetButton(fplbase::Gamepad::kButtonA));
    }
#endif
    // For testing, also support keyboard:
    if (!dir.x && !dir.y) {
      dir = CheckButtons(input_.GetButton(fplbase::FPLK_LEFT),
                         input_.GetButton(fplbase::FPLK_RIGHT),
                         input_.GetButton(fplbase::FPLK_UP),
                         input_.GetButton(fplbase::FPLK_DOWN),
                         input_.GetButton(fplbase::FPLK_RETURN));
    }
    return dir;
  }

  int GetNavigationDirection() {
    auto dir = GetNavigationDirection2D();
    if (dir.y) return dir.y;
    return dir.x;
  }

  vec2i CheckButtons(const Button &left, const Button &right, const Button &up,
                     const Button &down, const Button &action) {
    vec2i dir = mathfu::kZeros2i;
    if (left.went_up()) dir.x = -1;
    if (right.went_up()) dir.x = 1;
    if (up.went_up()) dir.y = -1;
    if (down.went_up()) dir.y = 1;
    if (action.went_up()) gamepad_event = kEventWentUp;
    if (action.went_down()) gamepad_event = kEventWentDown;
    if (action.is_down()) gamepad_event = kEventIsDown;
    if (dir.x || dir.y || gamepad_event != kEventHover)
      persistent_.is_last_event_pointer_type = false;
    return dir;
  }

  HashedId NextInteractiveElement(int start, int direction) {
    auto range = static_cast<int>(elements_.size());
    auto count = range;
    for (auto i = start; count > 0; --count) {
      i = (i + direction + range) % range;
      if (elements_[i].interactive) return elements_[i].hash;
    }
    return kNullHash;
  }

  void ColorBackground(const vec4 &color) {
    if (!layout_pass_) {
      RenderQuad(color_shader_, color, position_, GroupSize());
    }
  }

  void ImageBackground(const Texture &tex) {
    if (!layout_pass_) {
      tex.Set(0);
      RenderQuad(image_shader_, mathfu::kOnes4f, position_, GroupSize());
    }
  }

  void ImageBackgroundNinePatch(const Texture &tex, const vec4 &patch_info) {
    RenderTextureNinePatch(tex, patch_info, position_, GroupSize());
  }

  // Enable/Disable SDF generation.
  void EnableTextSDF(bool inner_sdf, bool outer_sdf, float threshold) {
    glyph_flags_ = (inner_sdf ? kGlyphFlagsInnerSDF : kGlyphFlagsNone) |
                   (outer_sdf ? kGlyphFlagsOuterSDF : kGlyphFlagsNone);
    sdf_threshold_ = threshold;
  }

  // Enable/Disable hyphenation.
  void EnableTextHyphenation(bool enable) {
    enable_hyphenation_ = enable;
  }

  void SetTextOuterColor(const mathfu::vec4 &color, float size,
                         const mathfu::vec2 &offset) {
    // Outer SDF need to be enabled to use the feature.
    assert(glyph_flags_ | kGlyphFlagsOuterSDF);
    text_outer_color_ = color;
    text_outer_color_size_ = size;
    text_outer_color_offset = offset;
  }

  // Set Image's color tint.
  void SetImageColor(const vec4 &color) { image_color_ = color; }

  // Set Label's text color.
  void SetTextColor(const vec4 &color) { text_color_ = color; }

  // Get Label's text color
  vec4 GetTextColor() { return text_color_; }

  void SetCaretColor(const vec4 &color) { caret_color_ = color; }

  vec4 GetCaretColor() { return caret_color_; }

  // Set Label's font.
  bool SetTextFont(const char *font_name) {
    return fontman_.SelectFont(font_name);
  }
  bool SetTextFont(const char *font_names[], int32_t count) {
    return fontman_.SelectFont(font_names, count);
  }

  // Set a locale used for the text rendering.
  void SetTextLocale(const char *locale) { fontman_.SetLocale(locale); }

  // Override text layout direction that is set by SetTextLanguage() API.
  void SetTextDirection(TextLayoutDirection direction) {
    fontman_.SetLayoutDirection(direction);
  }

  // Set a line height scaling used in the text rendering.
  void SetTextLineHeightScale(float scale) { text_line_height_scale_ = scale; }

  // Set a kerning scaling used in the text rendering.
  void SetTextKerningScale(float scale) { text_kerning_scale_ = scale; }

  // Set ellipsis characters used in the text rendering.
  void SetTextEllipsis(const char *ellipsis) {
    fontman_.SetTextEllipsis(ellipsis);
  }

  void SetGlobalListener(
      const std::function<void(HashedId id, Event event)> &callback) {
    global_listener_ = callback;
  }

  // Return the version of the FlatUI Library
  const FlatUIVersion *GetFlatUIVersion() const { return version_; }

 private:
  vec2i GetPointerDelta() { return pointer_delta_[0]; }

  vec2i GetPointerPosition() { return pointer_pos_[0]; }

  bool default_projection_;

  bool depth_test_;

  fplbase::AssetManager &matman_;
  fplbase::Renderer &renderer_;
  InputSystem &input_;
  FontManager &fontman_;
  Shader *image_shader_;
  Shader *color_shader_;
  FontShader font_shaders_[kFontShaderTypeCount][2];
  motive::MotiveEngine *motive_engine_;

  // Expensive rendering commands can check if they're inside this rect to
  // cull themselves inside a scrolling group.
  vec2i clip_position_;
  vec2i clip_size_;
  bool clip_mouse_inside_[InputSystem::kMaxSimultanuousPointers];
  bool clip_inside_;

  // Widget properties.
  mathfu::vec4 image_color_;
  mathfu::vec4 text_color_;
  mathfu::vec4 caret_color_;
  // Text's outer color setting
  mathfu::vec4 text_outer_color_;
  float text_outer_color_size_;
  mathfu::vec2 text_outer_color_offset;

  // Text metrices.
  float text_line_height_scale_;
  float text_kerning_scale_;

  // Flags that indicates glyph generation parameters, such as SDF.
  GlyphFlags glyph_flags_;
  // Threshold value for SDF rendering.
  float sdf_threshold_;

  bool enable_hyphenation_;

  int pointer_max_active_index_;
  const Button *pointer_buttons_[InputSystem::kMaxSimultanuousPointers];
  vec2i pointer_pos_[InputSystem::kMaxSimultanuousPointers];
  vec2i pointer_delta_[InputSystem::kMaxSimultanuousPointers];
  bool gamepad_has_focus_element;
  int32_t default_focus_element_;
  Event gamepad_event;

  // Drag operations.
  float scroll_speed_drag_;
  float scroll_speed_wheel_;
  float scroll_speed_gamepad_;
  vec2i drag_start_threshold_;

  // The latest pointer that returned an event.
  int32_t current_pointer_;

  // Cache the latest event so that multiple call to CheckEvent() can be safe.
  Event latest_event_;
  size_t latest_event_element_idx_;

  std::function<void(HashedId id, Event event)> global_listener_;

  // Intra-frame persistent state.
  static struct PersistentState {
    PersistentState()
        : is_last_event_pointer_type(true),
          initialized(false),
          sprite_sequence_number(0) {
      // This is effectively a global, so no memory allocation or other
      // complex initialization here.
      for (int i = 0; i < InputSystem::kMaxSimultanuousPointers; i++) {
        pointer_element[i] = kNullHash;
      }
      input_focus_ = input_capture_ = mouse_capture_ = kNullHash;
      dragging_pointer_ = kPointerIndexInvalid;
    }

    // For each pointer, the element id that last received a down event.
    HashedId pointer_element[InputSystem::kMaxSimultanuousPointers];
    // The element the gamepad is currently "over", simulates the mouse
    // hovering over an element.
    HashedId input_focus_;

    // The element that capturing the keyboard.
    HashedId input_capture_;

    // The element that capturing the pointer.
    // The element continues to recieve mouse events until it releases the
    // capture.
    HashedId mouse_capture_;

    // Simple text edit handler for an edit box.
    MicroEdit text_edit_;

    // Keep tracking a pointer position of a drag start.
    vec2i drag_start_position_;
    int32_t dragging_pointer_;

    // If true, then touch/mouse, else gamepad/keyboard.
    bool is_last_event_pointer_type;

    // If true, then InternalState has been initialized.
    bool initialized;

    // HashMap for storing animations.
    std::unordered_map<HashedId, Anim> animations;

    // HashMap for storing animations.
    std::unordered_map<HashedId, PendingTarget> pending_targets;

    // Vector for storing sprites.
    std::vector<Sprite> sprites;

    // Variable to keep track of how many sprites have been added.
    SequenceId sprite_sequence_number;
  } persistent_;

  // Disable copy constructor.
  InternalState(const InternalState &);
  InternalState &operator=(const InternalState &);
};

InternalState::PersistentState InternalState::persistent_;

void Run(fplbase::AssetManager &assetman, FontManager &fontman,
         fplbase::InputSystem &input, motive::MotiveEngine *motive_engine,
         const std::function<void()> &gui_definition) {
  // Create our new temporary state.
  InternalState internal_state(assetman, fontman, input, motive_engine);

  // run through persistent_.animations, removing animation that has
  // anim_state as kNotCalledLastFrame or KHasTargetButNoStartingValues and
  // set all "anim_state" to be kNotCalledLastFrame
  state->Clean();

  // Run two passes, one for layout, one for rendering.
  // First pass:
  gui_definition();

  // Second pass:
  internal_state.StartRenderPass();

  auto &renderer = assetman.renderer();
  renderer.SetBlendMode(fplbase::kBlendModeAlpha);
  renderer.SetDepthFunction(internal_state.depth_test()
                                ? fplbase::kDepthFunctionLess
                                : fplbase::kDepthFunctionDisabled);

  gui_definition();

  internal_state.CheckGamePadFocus();
}

void Run(fplbase::AssetManager &assetman, FontManager &fontman,
         fplbase::InputSystem &input,
         const std::function<void()> &gui_definition) {
  Run(assetman, fontman, input, NULL, gui_definition);
}

InternalState *Gui() {
  assert(state);
  return state;
}

void Image(const Texture &texture, float size, const char *id) {
  Gui()->Image(texture, size, id);
}

void SetImageColor(const mathfu::vec4 &color) {
  Gui()->SetImageColor(color);
}

void Label(const char *text, float font_size, HashedId label_id) {
  auto size = vec2(0, font_size);
  Gui()->Label(text, font_size, size, kTextAlignmentLeft, label_id);
}

void Label(const char *text, float font_size, const vec2 &size,
           HashedId label_id) {
  Gui()->Label(text, font_size, size, kTextAlignmentLeft, label_id);
}

void Label(const char *text, float font_size, const vec2 &size,
           TextAlignment alignment, HashedId label_id) {
  Gui()->Label(text, font_size, size, alignment, label_id);
}

void HtmlLabel(const char *html, float ysize, const mathfu::vec2 &label_size,
               TextAlignment alignment, const char *id) {
  Gui()->HtmlLabel(html, ysize, label_size, alignment, id);
}

Event Edit(float ysize, const mathfu::vec2 &size, const char *id,
           EditStatus *status, std::string *string) {
  return Gui()->Edit(ysize, size, kTextAlignmentLeft, id, status, string);
}

Event Edit(float ysize, const mathfu::vec2 &size, TextAlignment alignment,
           const char *id, EditStatus *status, std::string *string) {
  return Gui()->Edit(ysize, size, alignment, id, status, string);
}

void StartGroup(Layout layout, float spacing, const char *id) {
  Gui()->StartGroup(GetDirection(layout), GetAlignment(layout), spacing,
                    HashId(id));
}

void EndGroup() { Gui()->EndGroup(); }

void SetMargin(const Margin &margin) { Gui()->SetMargin(margin); }

void StartScroll(const vec2 &size, vec2 *offset) {
  Gui()->StartScroll(size, offset);
}

void EndScroll() { Gui()->EndScroll(); }

void StartSlider(Direction direction, float scroll_margin, float *value) {
  Gui()->StartSlider(direction, scroll_margin, value);
}

void EndSlider() { Gui()->EndSlider(); }

void CustomElement(
    const vec2 &virtual_size, const char *id,
    const std::function<void(const vec2i &pos, const vec2i &size)> renderer) {
  Gui()->Element(virtual_size, id, renderer);
}

void RenderTexture(const Texture &tex, const vec2i &pos, const vec2i &size) {
  Gui()->RenderTexture(tex, pos, size);
}

void RenderTexture(const Texture &tex, const vec2i &pos, const vec2i &size,
                   const vec4 &color) {
  Gui()->RenderTexture(tex, pos, size, color);
}

void RenderTextureNinePatch(const Texture &tex, const vec4 &patch_info,
                            const vec2i &pos, const vec2i &size) {
  Gui()->RenderTextureNinePatch(tex, patch_info, pos, size);
}

void EnableTextSDF(bool inner_sdf, bool outer_sdf, float threshold) {
  Gui()->EnableTextSDF(inner_sdf, outer_sdf, threshold);
}

void EnableTextHyphenation(bool enable) {
  Gui()->EnableTextHyphenation(enable);
}

void SetTextOuterColor(const mathfu::vec4 &color, float size,
                       const mathfu::vec2 &offset) {
  Gui()->SetTextOuterColor(color, size, offset);
}

void SetTextColor(const mathfu::vec4 &color) { Gui()->SetTextColor(color); }

vec4 GetTextColor() { return Gui()->GetTextColor(); }

void SetCaretColor(const mathfu::vec4 &color) { Gui()->SetCaretColor(color); }

vec4 GetCaretColor() { return Gui()->GetCaretColor(); }

bool SetTextFont(const char *font_name) {
  return Gui()->SetTextFont(font_name);
}
bool SetTextFont(const char *font_names[], int32_t count) {
  return Gui()->SetTextFont(font_names, count);
}

void SetTextLocale(const char *locale) { Gui()->SetTextLocale(locale); }
void SetTextDirection(const TextLayoutDirection direction) {
  Gui()->SetTextDirection(direction);
}

void SetTextLineHeightScale(float scale) {
  Gui()->SetTextLineHeightScale(scale);
}

void SetTextKerningScale(float scale) { Gui()->SetTextKerningScale(scale); }

void SetTextEllipsis(const char *ellipsis) { Gui()->SetTextEllipsis(ellipsis); }

Event CheckEvent() { return Gui()->CheckEvent(false); }
Event CheckEvent(bool check_dragevent_only) {
  return Gui()->CheckEvent(check_dragevent_only);
}

void SetDefaultFocus() { Gui()->SetDefaultFocus(); }

ssize_t GetCapturedPointerIndex() { return Gui()->GetCapturedPointerIndex(); }

void ModalGroup() { Gui()->ModalGroup(); }

void ColorBackground(const vec4 &color) { Gui()->ColorBackground(color); }

void ImageBackground(const Texture &tex) { Gui()->ImageBackground(tex); }

void ImageBackgroundNinePatch(const Texture &tex, const vec4 &patch_info) {
  Gui()->ImageBackgroundNinePatch(tex, patch_info);
}

void SetVirtualResolution(float virtual_resolution) {
  Gui()->SetVirtualResolution(virtual_resolution);
}

vec2 GetVirtualResolution() { return Gui()->GetVirtualResolution(); }

void PositionGroup(Alignment horizontal, Alignment vertical,
                   const vec2 &offset) {
  Gui()->PositionGroup(horizontal, vertical, offset);
}

void UseExistingProjection(const vec2i &canvas_size) {
  Gui()->UseExistingProjection(canvas_size);
}

void SetDepthTest(bool enable) { Gui()->SetDepthTest(enable); }

mathfu::vec2i VirtualToPhysical(const mathfu::vec2 &v) {
  return Gui()->VirtualToPhysical(v);
}

mathfu::vec2 PhysicalToVirtual(const mathfu::vec2i &v) {
  return Gui()->PhysicalToVirtual(v);
}

float GetScale() { return Gui()->GetScale(); }

void CapturePointer(const char *element_id) {
  Gui()->CapturePointer(HashId(element_id));
}

void ReleasePointer() { Gui()->CapturePointer(kNullHash); }

void SetScrollSpeed(float scroll_speed_drag, float scroll_speed_wheel,
                    float scroll_speed_gamepad) {
  Gui()->SetScrollSpeed(scroll_speed_drag, scroll_speed_wheel,
                        scroll_speed_gamepad);
}

void SetDragStartThreshold(int drag_start_threshold) {
  Gui()->SetDragStartThreshold(drag_start_threshold);
}

vec2 GroupPosition() {
  return Gui()->PhysicalToVirtual(Gui()->GroupPosition());
}
vec2 GroupSize() { return Gui()->PhysicalToVirtual(Gui()->GroupSize()); }

bool IsLastEventPointerType() { return Gui()->IsLastEventPointerType(); }

void ApplyCustomTransform(const mat4 &imvp) {
  Gui()->ApplyCustomTransform(imvp);
}

void SetGlobalListener(
    const std::function<void(HashedId id, Event event)> &callback) {
  Gui()->SetGlobalListener(callback);
}

const FlatUIVersion *GetFlatUIVersion() { return Gui()->GetFlatUIVersion(); }

namespace details {

const float *Animatable(HashedId id, const float *starting_values,
                        int dimensions) {
  return Gui()->Animatable(id, starting_values, dimensions);
}

void StartAnimation(HashedId id, const float *target_values,
                    const float *target_velocities, int dimensions,
                    const AnimCurveDescription &description) {
  Gui()->StartAnimation(id, target_values, target_velocities, dimensions,
                        description);
}

}  // namespace details

// Find the time remaining for the animation specified by id.
double AnimationTimeRemaining(HashedId id) {
  return Gui()->AnimationTimeRemaining(id);
}

int NumActiveSprites(HashedId id) { return Gui()->NumActiveSprites(id);}

SequenceId AddSprite(const char *id,
                     const std::function<bool(SequenceId seq)> &draw) {
  return Gui()->AddSprite(id, draw);
}

void DrawSprites(const char *id) { Gui()->DrawSprites(id); }

}  // namespace flatui


<|start_filename|>include/flatui/internal/glyph_cache.h<|end_filename|>
// Copyright 2015 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef GLYPH_CACH_H
#define GLYPH_CACH_H

#include <assert.h>
#include <list>
#include <map>
#include <set>
#include <tuple>
#include <unordered_map>

#include "flatui_util.h"
#include "fplbase/texture.h"
#include "fplbase/utilities.h"
#include "mathfu/constants.h"

using fplbase::LogInfo;

namespace flatui {

/// @cond FLATUI_INTERNAL

// The glyph cache maintains a list of GlyphCacheRow. Each row has a fixed sizes
// of height, which is determined at a row creation time. A row can include
// multiple GlyphCacheEntry with a same or smaller height and they can have
// variable width. In a row,
// GlyphCacheEntry are stored from left to right in the order of registration
// and won't be evicted per entry, but entire row is flushed when necessary to
// make a room for new GlyphCacheEntry.
// The purpose of this design is to cache as many glyphs and to achieve high
// caching perfomance estimating same size of glphys tends to be stored in a
// cache at same time. (e.g. Caching a string in a same size.)
//
// When looking up a cached entry, the API looks up unordered_map which is O(1)
// operation.
// If there is no cached entry for given code point, the caller needs to invoke
// Set() API to fill in a cache.
// Set() operation takes
// O(log N (N=# of rows)) when there is a room in the cache for the request,
// + O(N (N=# of rows)) to look up and evict least recently used row with
// sufficient height.

// Enable tracking stats in Debug build.
#ifdef _DEBUG
#define GLYPH_CACHE_STATS (1)
#endif

// Forward decl.
class FontBuffer;
class GlyphCache;
class GlyphCacheRow;
class GlyphCacheEntry;
class GlyphKey;
class GlyphCacheBufferBase;
template <typename T>
class GlyphCacheBuffer;

// Constants for a cache entry size rounding up and padding between glyphs.
// Adding a padding between cached glyph images to avoid sampling artifacts of
// texture fetches.  Padding can be configured for each GlyphCache.
const int32_t kGlyphCacheHeightRound = 4;
const int32_t kDefaultGlyphCachePaddingX = 1;
const int32_t kDefaultGlyphCachePaddingY = 1;
const int32_t kGlyphCachePaddingSDF = 4;

// Flags that controls glyph image generation.
enum GlyphFlags {
  kGlyphFlagsNone = 0,      // Normal glyph.
  kGlyphFlagsOuterSDF = 1,  // Glyph image with an outer SDF information.
  kGlyphFlagsInnerSDF = 2,  // Glyph image with an inner SDF information.
};

// Flags that indicates glyph cache image format.
enum GlyphFormats {
  kGlyphFormatsMono = 0,            // Single channel monochrome glyph.
  kGlyphFormatsColor = 0x80000000,  // Color glyph.
};

const float kSDFThresholdDefault = 16.0f / 255.0f;

// A constant indicating the glyph cache has been never flushed.
const int32_t kNeverFlushed = -1;

// Defining a cache clear value for debugging use (e.g. changing it to other
// values and check if the entrie is cleared correctly.)
const int32_t kCacheClearValue = 0;

// Bitwise OR operator for GlyphFlags enumeration.
inline GlyphFlags operator|(GlyphFlags a, GlyphFlags b) {
  return static_cast<GlyphFlags>(static_cast<int>(a) | static_cast<int>(b));
}

// Struct tracking glyph cache usage stats for a debug support.
struct GlyphCacheStats {
#ifdef GLYPH_CACHE_STATS
  // Variables to track usage stats.
  int32_t lookup_;     // Number of cache look-up happened.
  int32_t hit_;        // Number of cache hit happened during cache look-up.
  int32_t row_flush_;  // Number of cache row flush happened.
  int32_t set_fail_;   // Occurance of cache look up failure.
                       // This would involve full cache flush.
#endif                 // GLYPH_CACHE_STATS
};

// Class that includes glyph parameters.
class GlyphKey {
 public:
  // Constructors.
  GlyphKey()
      : font_id_(kNullHash),
        code_point_(0),
        glyph_size_(0),
        flags_(kGlyphFlagsNone) {}
  GlyphKey(const HashedId font_id, uint32_t code_point, uint32_t glyph_size,
           GlyphFlags flags) {
    font_id_ = font_id;
    code_point_ = code_point;
    glyph_size_ = glyph_size;
    flags_ = flags;
  }

  // Compare operators.
  bool operator==(const GlyphKey &other) const {
    return (code_point_ == other.code_point_ && font_id_ == other.font_id_ &&
            glyph_size_ == other.glyph_size_ && flags_ == other.flags_);
  }

  /// @brief The compare operator for FontBufferParameters.
  bool operator()(const GlyphKey &lhs, const GlyphKey &rhs) const {
    return std::tie(lhs.font_id_, lhs.code_point_, lhs.glyph_size_,
                    lhs.flags_) <
           std::tie(rhs.font_id_, rhs.code_point_, rhs.glyph_size_, rhs.flags_);
  }

  // Hash function.
  size_t operator()(const GlyphKey &key) const {
    // Note that font_id_ is an already hashed value.
    auto value = HashCombine<uint32_t>(key.font_id_, key.code_point_);
    value = HashCombine<uint32_t>(value, key.glyph_size_);
    value = HashCombine<int32_t>(value, key.flags_);
    return value;
  }

 private:
  HashedId font_id_;
  uint32_t code_point_;
  uint32_t glyph_size_;
  GlyphFlags flags_;
};

// Cache entry for a glyph.
class GlyphCacheEntry {
 public:
  // Typedef for cache entry map's iterator.
  typedef std::map<GlyphKey, std::unique_ptr<GlyphCacheEntry>,
                   GlyphKey>::iterator iterator;
  typedef std::list<GlyphCacheRow>::iterator iterator_row;

  GlyphCacheEntry()
      : code_point_(0), size_(0, 0), offset_(0.f, 0.f), advance_(0, 0),
        color_glyph_(false) {}

  // Setter/Getter of code point.
  // Code point is an entry in a font file, not a direct transform of Unicode.
  uint32_t get_code_point() const { return code_point_; }
  void set_code_point(uint32_t code_point) { code_point_ = code_point; }

  // Setter/Getter of cache entry size.
  mathfu::vec2i get_size() const { return size_; }
  void set_size(const mathfu::vec2i &size) { size_ = size; }

  // Setter/Getter of cache entry offset.
  mathfu::vec2 get_offset() const { return offset_; }
  void set_offset(const mathfu::vec2 &offset) { offset_ = offset; }

  // Setter/Getter of cache entry advance.
  mathfu::vec2i get_advance() const { return advance_; }
  void set_advance(const mathfu::vec2i &advance) { advance_ = advance; }

  // Setter/Getter of the cache entry position.
  mathfu::vec3i get_pos() const { return pos_; }
  void set_pos(const mathfu::vec3i &pos) { pos_ = pos; }

  // Setter/Getter of UV
  mathfu::vec4 get_uv() const { return uv_; }
  void set_uv(const mathfu::vec4 &uv) { uv_ = uv; }

  MATHFU_DEFINE_CLASS_SIMD_AWARE_NEW_DELETE

  // Setter/Getter of a glyph cache row holding the entry.
  GlyphCacheEntry::iterator_row get_row() const { return it_row_; }
  void set_row(GlyphCacheEntry::iterator_row row) { it_row_ = row; }

  // Setter/Getter of color glyph information.
  bool get_color_glyph() const { return color_glyph_; }
  void set_color_glyph(bool b) { color_glyph_ = b; }

 private:
  // Friend class, GlyphCache needs an access to internal variables of the
  // class.
  friend class GlyphCache;

  // Code point of the glyph.
  uint32_t code_point_;

  // Cache entry sizes.
  mathfu::vec2i size_;

  // Glyph image's offset value relative to font metrics origin.
  mathfu::vec2 offset_;

  // Glyph image's advance value relative to font metrics origin.
  mathfu::vec2i advance_;

  // Glyph image's UV in the texture atlas.
  mathfu::vec4 uv_;

  // Glyph image's position value in the buffer.
  mathfu::vec3i pos_;

  // Iterator to the row entry.
  GlyphCacheEntry::iterator_row it_row_;

  // Iterator to the row LRU entry.
  std::list<GlyphCacheEntry::iterator_row>::iterator it_lru_row_;

  // Pointer to the GlyphCacheBufferBase.
  GlyphCacheBufferBase *buffer_;

  // Flag indicating if the glyph is color glyph.
  bool color_glyph_;
};

// Single row in a cache. A row correspond to a horizontal slice of a texture.
// (e.g if a texture has a 256x256 of size, and a row has a max glyph height of
// 16, the row corresponds to 256x16 pixels of the overall texture.)
//
// One cache row contains multiple GlyphCacheEntry with a same or smaller
// height. GlyphCacheEntry entries are stored from left to right and not evicted
// per glyph, but entire row at once for a performance reason.
// GlyphCacheRow is an internal class for GlyphCache.
class GlyphCacheRow {
 public:
  GlyphCacheRow() { Initialize(0, 0, mathfu::vec2i(0, 0)); }
  // Constructor with an arguments.
  // slice : an index indicating a glyph cache slice.
  // y_pos : vertical position of the row in the buffer.
  // witdh : width of the row. Typically same value of the buffer width.
  // height : height of the row.
  GlyphCacheRow(int32_t slice, int32_t y_pos, const mathfu::vec2i &size) {
    Initialize(slice, y_pos, size);
  }
  ~GlyphCacheRow() {}

  // Initialize the row width and height.
  void Initialize(int32_t slice, int32_t y_pos, const mathfu::vec2i &size) {
    slice_ = slice;
    last_used_counter_ = 0;
    y_pos_ = y_pos;
    remaining_width_ = size.x;
    size_ = size;
    cached_entries_.clear();
  }

  // Check if the row has a room for a requested width and height.
  bool DoesFit(const mathfu::vec2i &size) const {
    return !(size.x > remaining_width_ || size.y > size_.y);
  }

  // Reserve an area in the row.
  int32_t Reserve(const GlyphCacheEntry::iterator it,
                  const mathfu::vec2i &size) {
    assert(DoesFit(size));

    // Update row info.
    int32_t pos = size_.x - remaining_width_;
    remaining_width_ -= size.x;
    cached_entries_.push_back(it);
    return pos;
  }

  // Setter/Getter of last used counter.
  uint32_t get_last_used_counter() const { return last_used_counter_; }
  void set_last_used_counter(uint32_t counter) { last_used_counter_ = counter; }

  // Setter/Getter of row size.
  mathfu::vec2i get_size() const { return size_; }
  void set_size(const mathfu::vec2i size) { size_ = size; }

  // Setter/Getter of row y pos.
  int32_t get_y_pos() const { return y_pos_; }
  void set_y_pos(int32_t y_pos) { y_pos_ = y_pos; }

  // Setter/Getter of row's slice.
  int32_t get_slice() const { return slice_; }
  void set_slice(int32_t slice) { slice_ = slice; }

  // Getter of cached glyphs.
  size_t get_num_glyphs() const { return cached_entries_.size(); }

  // Setter/Getter of iterator to row LRU.
  const std::list<GlyphCacheEntry::iterator_row>::iterator get_it_lru_row()
      const {
    return it_lru_row_;
  }
  void set_it_lru_row(
      const std::list<GlyphCacheEntry::iterator_row>::iterator it_lru_row) {
    it_lru_row_ = it_lru_row;
  }

  // Setter/Getter of iterator to row height map.
  std::multimap<int32_t, GlyphCacheEntry::iterator_row>::iterator
  get_it_row_height_map() const {
    return it_row_height_map_;
  }
  void set_it_row_height_map(const std::multimap<
      int32_t, GlyphCacheEntry::iterator_row>::iterator it_row_height_map) {
    it_row_height_map_ = it_row_height_map;
  }

  // Getter of cached glyph entries.
  std::vector<GlyphCacheEntry::iterator> &get_cached_entries() {
    return cached_entries_;
  }

  // Add a reference to the glyph cache row.
  void AddRef(FontBuffer *p) { ref_.insert(p); }

  // Release a reference from the glyph cache row.
  void Release(FontBuffer *p) { ref_.erase(p); }

  // Invalidate FontBuffers that are referencing the cache row when the row
  // becomes invalid.
  void InvalidateReferencingBuffers();

  // Release a reference to the row from existing FontBuffers.
  void ReleaseReferencesFromFontBuffers();

 private:
  // Last used counter value of the entry. The value is used to determine
  // if the entry can be evicted from the cache.
  uint32_t last_used_counter_;

  // Remaining width of the row.
  // As new contents are added to the row, remaining width decreases.
  int32_t remaining_width_;

  // Index indicating a glyph cache slice.
  int32_t slice_;

  // Size of the row.
  mathfu::vec2i size_;

  // Vertical position of the row in the entire cache buffer.
  uint32_t y_pos_;

  // Iterator to the row LRU list.
  std::list<GlyphCacheEntry::iterator_row>::iterator it_lru_row_;

  // Iterator to the row height map.
  std::multimap<int32_t, GlyphCacheEntry::iterator_row>::iterator
      it_row_height_map_;

  // Tracking cached entries in the row.
  // When flushing the row, entries in the map is removed using the vector.
  std::vector<GlyphCacheEntry::iterator> cached_entries_;

  // Set holding pointers of FontBuffer that is referencing the cache row.
  std::set<FontBuffer *> ref_;
};

// The base class of GlyphCacheBuffer.
// The class maintains cache rows.
class GlyphCacheBufferBase {
 public:
  GlyphCacheBufferBase() : max_slices_(0), dirty_(false) {
    size_ = mathfu::kZeros2i;
  }
  virtual ~GlyphCacheBufferBase() {}

  virtual void Initialize(GlyphCache *cache, const mathfu::vec2i &size,
                          int32_t max_slices);
  const mathfu::vec2i &get_size() const { return size_; }

  bool FindRow(int32_t req_width, int32_t req_height,
               GlyphCacheEntry::iterator_row *it_found);

  // Insert new row to the row list with a given size.
  // It tries to merge 2 rows if next row is also empty one.
  void InsertNewRow(int32_t slice, int32_t y_pos, const mathfu::vec2i &size,
                    const GlyphCacheEntry::iterator_row pos);

  // Update the row LRU list.
  void UpdateRowLRU(std::list<GlyphCacheEntry::iterator_row>::iterator it);

  // Update dirty rect.
  void UpdateDirtyRect(int32_t slice, const mathfu::vec4i &rect);

  // Getter of dirty rect.
  const mathfu::vec4i &get_dirty_rect(int32_t slice) const {
    return dirty_rects_[slice];
  }

  // Getter/Setter of dirty state.
  bool get_dirty_state() const {
    return dirty_;
  }
  void set_dirty_state(bool dirty) { dirty_ = dirty; }

  // Virtual functions to retrieve buffer parameters.
  virtual fplbase::TextureFormat get_texture_format() = 0;
  virtual int32_t get_num_slices() const = 0;
  virtual uint8_t *get(int32_t slice) const = 0;
  virtual int32_t get_element_size() const = 0;
  virtual void CopyImage(const mathfu::vec3i &pos, const uint8_t *const image,
                         const GlyphCacheEntry *entry) = 0;
  virtual void ClearPaddingRegion(const mathfu::vec3i &pos,
                                  const mathfu::vec2i &padding,
                                  const GlyphCacheEntry *entry) = 0;
  virtual void ResolveDirtyRect() = 0;

 protected:
  // list of rows in the cache.
  std::list<GlyphCacheRow> list_row_;

  // LRU entries of the row. Tracks iterator to list_row_.
  std::list<GlyphCacheEntry::iterator_row> lru_row_;

  // Map to row entries to have O(log N) access to a row entry.
  // Tracks iterator to list_row_.
  // Using multimap because multiple rows can have same row height.
  // Key: height of the row. With the map, an API can have quick access to a row
  // with a given height.
  std::multimap<int32_t, GlyphCacheEntry::iterator_row> map_row_;

  // Size of the buffer.
  mathfu::vec2i size_;

  // # of slices in the buffer.
  int32_t max_slices_;

  // Flag indicates if the cache is dirty. If it's dirty, corresponding font
  // atlas texture needs to be uploaded.
  bool dirty_;

  // Dirty region in the buffer.
  std::vector<mathfu::vec4i> dirty_rects_;

  // Pointer to the cache class to retrieve cache system parameters.
  GlyphCache *cache_;
};

template <typename T>
class GlyphCacheBuffer : public GlyphCacheBufferBase {
 public:
  virtual void Initialize(GlyphCache *cache, const mathfu::vec2i &size,
                          int32_t max_slices) {
    GlyphCacheBufferBase::Initialize(cache, size, max_slices);
    AllocateTextureInfo();
  }

  // Copy glyph image into the buffer.
  void CopyImage(const mathfu::vec3i &pos, const uint8_t *const src,
                 const GlyphCacheEntry *entry) {
    auto dest = buffers_[pos.z].get();
    auto src_stride = entry->get_size().x * sizeof(T);
    assert(pos.x < size_.x);
    assert(pos.y < size_.y);
    for (int32_t y = 0; y < entry->get_size().y; ++y) {
      memcpy(dest + pos.x + (pos.y + y) * size_.x, src + y * src_stride,
             src_stride);
    }
  }

  void ClearPaddingRegion(const mathfu::vec3i &pos,
                          const mathfu::vec2i &padding,
                          const GlyphCacheEntry *entry) {
    auto dest = buffers_[pos.z].get();
    assert(pos.x + entry->get_size().x + padding.x <= size_.x);
    assert(pos.y + entry->get_size().y + padding.y <= size_.y);

    // Clear vertical padding region.
    for (int32_t y = 0; y < entry->get_size().y; ++y) {
      const int32_t dest_y = (pos.y + y) * size_.x;
      for (int32_t x = 0; x < padding.x; ++x) {
        const int32_t dest_x = pos.x + entry->get_size().x + x;
        *(dest + dest_x + dest_y) = 0;
      }
    }
    // Clear horizontal padding region.
    for (int32_t y = 0; y < padding.y; ++y) {
      const int32_t dest_y = (pos.y + entry->get_size().y + y) * size_.x;
      for (int32_t x = 0; x < entry->get_size().x; ++x) {
        const int32_t dest_x = pos.x + x;
        *(dest + dest_x + dest_y) = 0;
      }
    }

    // Clear regions between cache rows.
    if (pos.y > 0 && padding.y) {
      const int32_t dest_y = (pos.y - 1) * size_.x;
      for (int32_t x = 0; x < entry->get_size().x; ++x) {
        const int32_t dest_x = pos.x + x;
        *(dest + dest_x + dest_y) = 0;
      }
    }
    if (pos.x > 0 && padding.x) {
      const int32_t dest_x = pos.x - 1;
      for (int32_t y = 0; y < entry->get_size().y; ++y) {
        const int32_t dest_y = (pos.y + y) * size_.x;
        *(dest + dest_x + dest_y) = 0;
      }
    }
  }

  // Insert new buffer in the buffer list.
  void InsertNewBuffer() {
    // Increase the buffer size.
    int32_t new_index = static_cast<int32_t>(buffers_.size());
    buffers_.resize(new_index + 1);
    dirty_rects_.resize(new_index + 1, mathfu::kZeros4i);

    // Allocate the glyph cache buffer.
    // A buffer format can be 8/32 bpp (32 bpp is mostly used for Emoji).
    buffers_[new_index].reset(new T[size_.x * size_.y]);

    // Clearing allocated buffer.
    memset(buffers_[new_index].get(), kCacheClearValue,
           size_.x * size_.y * sizeof(T));

    // Create first (empty) row entry.
    auto index = new_index | buffer_format();
    InsertNewRow(index, 0, size_, list_row_.end());

#ifdef GLYPH_CACHE_STATS
    LogInfo("Cached glyphs: new buffer is allocated.\nCurrent buffer size:%d",
            new_index + 1);
#endif  // GLYPH_CACHE_STATS
  }

  // Pre allocate texture structure.
  void AllocateTextureInfo() {
    for (auto i = 0; i < max_slices_; ++i) {
      textures_.push_back(
          fplbase::Texture(nullptr, get_texture_format(),
                           fplbase::kTextureFlagsClampToEdge));
    }
  }

  // Resolve the dirty state of the cache. If the cache has any dirty rect,
  // the API copies the rect to the texture using FPLBase API.
  void ResolveDirtyRect() {
    auto slices = get_num_slices();
    for (auto i = 0; i < slices; ++i) {
      auto rect = get_dirty_rect(i);
      if (!fplbase::ValidTextureHandle(textures_[i].id())) {
        // Give a texture size but don't have to clear the texture here.
        textures_[i].LoadFromMemory(nullptr, get_size(), get_texture_format());
      }
      if (rect.z - rect.x > 0 && rect.w - rect.y > 0) {
        textures_[i].UpdateTexture(
            0, get_texture_format(), 0, rect.y, get_size().x, rect.w - rect.y,
            get(i) + get_element_size() * get_size().x * rect.y);
      }
    }
    set_dirty_state(false);
  }

  bool PurgeCache(int32_t req_height);

  void Reset() {
    // Release reference from FontBuffers to GlyphCacheRows.
    auto begin = list_row_.begin();
    auto end = list_row_.end();
    while (begin != end) {
      begin->ReleaseReferencesFromFontBuffers();
      begin++;
    }

    buffers_.clear();
    lru_row_.clear();
    list_row_.clear();
    map_row_.clear();
    dirty_ = false;
    dirty_rects_.clear();

    // Create first (empty) row entry.
    InsertNewBuffer();
  }

  int32_t get_num_slices() const {
    return static_cast<int32_t>(buffers_.size());
  }
  int32_t get_num_max_slices() const { return max_slices_; }
  uint8_t *get(int32_t slice) const {
    return reinterpret_cast<uint8_t *>(buffers_[slice].get());
  }
  int32_t get_element_size() const { return sizeof(T); }
  fplbase::Texture *get_texture(int32_t slice) { return &textures_[slice]; }

  fplbase::TextureFormat get_texture_format() {
    if (sizeof(T) == 4) {
      return fplbase::kFormat8888;
    } else if (sizeof(T) == 3) {
      return fplbase::kFormat888;
    }
    // Single channel cache.
    return fplbase::kFormatLuminance;
  }

  // Retrieve the buffer format.
  GlyphFormats buffer_format() {
    if (sizeof(T) > 1) {
      // 8 bit buffers are treated as a monochrome buffer (used for regular and
      // SDF glyphs).
      return kGlyphFormatsColor;
    }
    return kGlyphFormatsMono;
  }

 private:
  std::vector<std::unique_ptr<T[]> > buffers_;
  std::vector<fplbase::Texture> textures_;
};

class GlyphCache {
 public:
  // Constructor with parameters.
  // width: width of the glyph cache texture. Rounded up to power of 2.
  // height: height of the glyph cache texture. Rounded up to power of 2.
  // max_slices: max number of slices in the cache.
  GlyphCache(const mathfu::vec2i &size, int32_t max_slices);
  ~GlyphCache() {};

  // Look up a cached entries.
  // Return value: A pointer to a cached glyph entry.
  // nullptr if not found.
  const GlyphCacheEntry *Find(const GlyphKey &key);

  // Set an entry to the cache.
  // Return value: true if caching succeeded. false if there is no room in the
  // cache for a requested entry.
  // Returns a pointer to inserted entry.
  const GlyphCacheEntry *Set(const void *const image, const GlyphKey &key,
                             const GlyphCacheEntry &entry);

  // Flush all cache entries.
  bool Flush();

  // Increment a cycle counter of the cache.
  // Invoke this API for each rendering cycle.
  // The counter is used to determine which cache entries can be evicted when
  // cache entries are full.
  void Update() { counter_++; }

  // Resolve dirty rects in the glyph cache. The API invoke FPLBase API to
  // update textures.
  void ResolveDirtyRect() {
    buffers_.ResolveDirtyRect();
    color_buffers_.ResolveDirtyRect();
  }

  // Return dirty state of the glyph cache.
  bool get_dirty_state() const {
    return buffers_.get_dirty_state() || color_buffers_.get_dirty_state();
  }

  // Return number of cache slices in the cache.
  int32_t get_num_slices() const {
    return buffers_.get_num_slices() + color_buffers_.get_num_slices();
  }

  // Return number of the max cache slices in the cache.
  int32_t get_num_max_slices() const {
    return buffers_.get_num_max_slices() + color_buffers_.get_num_max_slices();
  }

  // Retrieve a cycle counter of the cache.
  uint32_t get_counter() const { return counter_; }

  // Debug API to show cache statistics.
  const GlyphCacheStats &Status();

  // Enable color glyph cache in the cache.
  void EnableColorGlyph();

  bool SupportsColorGlyphs() const { return color_buffers_.get_size().x > 0; }

  // Getter of allocated glyph cache.
  GlyphCacheBuffer<uint8_t> *get_monochrome_buffer() { return &buffers_; }
  GlyphCacheBuffer<uint32_t> *get_color_buffer() { return &color_buffers_; }

  // Getter of the counters.
  int32_t get_revision() const { return revision_; }
  int32_t get_last_flush_revision() const { return last_flushed_revision_; }

  // Getter of the cache size.
  const mathfu::vec2i &get_size() const { return size_; }

  // Getter/Setter of the glyph padding
  const mathfu::vec2i &get_padding() const { return padding_; }
  void set_padding(const mathfu::vec2i &padding) { padding_ = padding; }

 private:
  // Friend class, GlyphCacheBuffer needs an access to internal variables of the
  // class.
  template <typename T>
  friend class GlyphCacheBuffer;

  // Flush cached entries in the buffer.
  void FlushCachedEntries(std::vector<GlyphCacheEntry::iterator> &entries);

#ifdef GLYPH_CACHE_STATS
  void ResetStats();
#endif  // GLYPH_CACHE_STATS

  // A time counter of the cache.
  // In each rendering cycle, the counter is incremented.
  // The counter is used if some cache entry can be evicted in current rendering
  // cycle.
  uint32_t counter_;

  // Size of the glyph cache. Rounded to power of 2.
  mathfu::vec2i size_;

  // Padding between glyphs.
  mathfu::vec2i padding_;

  // Cache buffers for monochrome (1 channel) glyphs.
  GlyphCacheBuffer<uint8_t> buffers_;

  // Ceche buffer for color glyphs. The buffer is initialized when
  // EnableColorGlyphs() API is invoked.
  GlyphCacheBuffer<uint32_t> color_buffers_;

  // Max number of cache buffers. As a buffer gets full, the glyph cache tries
  // to increase number of cache buffers up to the number;
  int32_t max_slices_;

  // Map to the cache entries
  // This map is the primary place to look up the cache entries.
  // Key: a structure that contains glyph parameters such as a code point, font
  // id, glyph size etc.
  // Note that the code point is an index in the font file and not a Unicode
  // value.
  std::map<GlyphKey, std::unique_ptr<GlyphCacheEntry>, GlyphKey> map_entries_;

  // Revision of the buffer.
  // Each time one or more cache entry is evicted, a revision of the cache is
  // updated.
  // The revision  is used to determine if caller can make sure referencing
  // glyph cache entries are still in the cache.
  // Note that the revision is not changed when new glyph entries are added
  // because existing entries are still valid in that case.
  int32_t revision_;

  // A cache revision when the cache is flushed last time.
  int32_t last_flushed_revision_;

  // Variables to track usage stats.
  GlyphCacheStats stats_;
};

// The API tries to increase a buffer size up to specified slice first and if
// threre is no room, then try to evict a row entry with or larger than
// 'req_height' which is not used in current rendering cycle.
// (Note that rows used by 'reference counting' FontBuffers are never evicted)
// Rows are stored in the queue in the order of it's size (and LRU if there are
// multiple rows in the same size).
// API returns true if it allocates new buffer or the purge operation was
// successful. Otherwise the API returns false which would initiate multi pass
// glyph rendering in FlatUI (or the user may needs to increase cache size in
// other system).
template <typename T>
bool GlyphCacheBuffer<T>::PurgeCache(int32_t req_height) {
  // Try to expand the cache if we can allocate more slices.
  if (get_num_slices() < max_slices_) {
    InsertNewBuffer();
    return true;
  }

  // Try to find a row that is not used in current cycle and has enough
  // height from LRU list.
  for (auto row_it = lru_row_.begin(); row_it != lru_row_.end(); ++row_it) {
    auto &row = *row_it;
    if (row->get_last_used_counter() == cache_->get_counter()) {
      // The row is being used in current rendering cycle.
      // We can not evict the row.
      continue;
    }
    if (row->get_size().y >= req_height) {
      // Now flush & initialize the row.
      // Invalidate FontBuffers that are referencing the row.
      row->InvalidateReferencingBuffers();
      cache_->FlushCachedEntries(row->get_cached_entries());
      row->Initialize(row->get_slice(), row->get_y_pos(), row->get_size());

      // Try to call the function recursively.
      return true;
    }
  }
  return false;
}

/// @endcond

}  // namespace flatui

#endif  // GLYPH_CACH_H


<|start_filename|>include/flatui/flatui_common.h<|end_filename|>
// Copyright 2015 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef FPL_FLATUI_COMMON_H
#define FPL_FLATUI_COMMON_H

#include "flatui.h"
#include "fplbase/material.h"

namespace flatui {

/// @file
/// @addtogroup flatui_common_widgets
/// @{
///
/// @brief This file contains some common widgets that will be widely
/// useful.
///
/// FlatUI is structured to be very compositional, meaning that no one widget
/// will always be universally useful for every UI. This means that these
/// widgets are intended for the average UI, but may not be sufficient for
/// custom use cases that extend beyond the scope of these widgets. Therefore
/// these widgets may also serve as building blocks, or as examples, to easily
/// build your own specialized widgets.

/// @enum ButtonProperty
///
/// @brief Specifies the button properties used in common widgets.
///
/// `kButtonPropertyDisabled` disables buttons (`CheckEvent()` calls will be
/// skipped).
/// `kButtonPropertyImageLeft` shows the specified image to the left of the text
/// label in a TextButton.
/// `kButtonPropertyImageRight` shows the specified image to the right of the
/// text label in a TextButton.
enum ButtonProperty {
  kButtonPropertyDisabled = 1,
  kButtonPropertyImageLeft = 2,
  kButtonPropertyImageRight = 4,
};

/// @brief The bitwise OR operator for ButtonProperties.
///
/// E.g. `c = a | b;`
///
/// @param[in] a The first ButtonProperty whose `int` value should be used in
/// the bitwise OR.
/// @param[in] b The second ButtonProperty whose `int` value should be used in
/// the bitwise OR.
///
/// @return Returns a new ButtonProperty that is formed from the result of the
/// bitwise OR of the two input ButtonProperties' `int` values.
inline ButtonProperty operator|(ButtonProperty a, ButtonProperty b) {
  return static_cast<ButtonProperty>(static_cast<int>(a) | static_cast<int>(b));
}

/// @brief The bitwise OR assignment operator for ButtonProperties.
///
/// E.g. `a |= b;`
///
/// @param[in] a The modifiable ButtonProperty lvalue whose `int` value should
/// be used in the bitwise OR. It also captures the return value of the
/// function.
/// @param[in] b The second ButtonProperty whose `int` value is used in the
/// bitwise OR.
///
/// @return Returns a new ButtonProperty that is formed from the result of the
/// bitwise OR of the two input ButtonProperties' `int` values.
inline ButtonProperty operator|=(ButtonProperty &a, const ButtonProperty &b) {
  a = a | b;
  return a;
}

/// @brief Some of the widgets provide user feedback by rendering a transparent
/// background color to signal that the user is hovering over or
/// clicking/touching/interacting with the widget.
///
/// Be default, these are a dark grey and a medium grey, respectively. Both
/// are semi-transparent.
///
/// @param[in] hover_color A const vec4 reference to the RGBA color values to
/// use when hovering over a widget.
/// @param[in] click_color A const vec4 reference to the RGBA color values to
/// use when clicking on a widget.
void SetHoverClickColor(const mathfu::vec4 &hover_color,
                        const mathfu::vec4 &click_color);

/// @brief A simple button showing clickable text.
///
/// @note Uses the colors that are set via `SetHoverClickColor`.
///
/// @param[in] text A C-string of text to display on the button.
/// @param[in] size A float indicating the vertical height.
/// @param[in] margin A Margin that should be placed around the text.
///
/// @return Returns the Event type for the button.
Event TextButton(const char *text, float size, const Margin &margin);

/// @brief A simple button showing a clickable image.
///
/// @note Uses the colors that are set via `SetHoverClickColor`.
///
/// @param[in] texture The Texture of the image to display.
/// @param[in] size A float indicating the vertical height.
/// @param[in] margin A Margin around the `texture`.
/// @param[in] id A C-string to uniquely identify the button.
///
/// @return Returns the Event type for the button.
Event ImageButton(const fplbase::Texture &texture, float size,
                  const Margin &margin, const char *id);

/// @brief A button showing a different image when clicked or not.
///
/// @param[in] up_texture The Texture of the image to display if not clicked.
/// @param[in] down_texture The Texture of the image to display if clicked.
/// @param[in] size A float indicating the vertical height.
/// @param[in] margin A Margin around the `texture`.
/// @param[in] id A C-string to uniquely identify the button.
///
/// @return Returns the Event type for the button.
Event ToggleImageButton(const fplbase::Texture &up_texture,
                          const fplbase::Texture &down_texture, float size,
                  const Margin &margin, const char *id);

/// @brief A simple button showing clickable text with an image shown beside it.
///
/// @note Uses the colors that are set via `SetHoverClickColor`.
///
/// @param[in] texture The Texture of the image to display beside the button.
/// @param[in] texture_margin The Margin around the `texture`.
/// @param[in] text A C-string of text to display on the button.
/// @param[in] size A float indicating the vertical height.
/// @param[in] margin A Margin that should be placed around the text.
/// @param[in] property A ButtonProperty enum corresponding to where the
/// image should be placed, relative to the button.
///
/// @return Returns the Event type for the button.
Event TextButton(const fplbase::Texture &texture, const Margin &texture_margin,
                 const char *text, float size, const Margin &margin,
                 const ButtonProperty property);

/// @brief A checkbox with a label next to it.
///
/// @param[in] texture_checked A const reference to the Texture for when the
/// box is checked.
/// @param[in] texture_unchecked A const reference to the Texture for when the
/// box is unchecked.
/// @param[in] label A C-string to be used as the label that appears next to the
/// checkbox.
/// @param[in] size A float corresponding to the size of the checkbox and label.
/// @param[in] margin A Margin around the checkbox.
/// @param[in,out] is_checked A pointer to a bool determining if the checkbox is
/// checked or not. It will capture the output of the `kEventWentUp` as well.
///
/// @return Returns the Event type for the checkbox.
Event CheckBox(const fplbase::Texture &texture_checked,
               const fplbase::Texture &texture_unchecked, const char *label,
               float size, const Margin &margin, bool *is_checked);

/// @brief A clider to change a numeric value.
///
/// @param[in] tex_bar The Texture for the slider. Typically this is a bar of
/// some kind as a ninepatch texture.
/// @param[in] tex_knob The Texture for the knob to move on top of the
/// `tex_bar`.
/// @param[in] size A const vec2 reference to specify the whole size, including
/// the margin, and relative size of the slider.
/// @param[in] bar_height A float corresponding the the Y ratio of the bar
/// (usually 0.5).
/// @param[in] id A C-string to uniquely identify the slider.
/// @param[out] slider_value A pointer to a float between 0.0 and 1.0 inclusive,
/// which contains the position of the slider.
Event Slider(const fplbase::Texture &tex_bar, const fplbase::Texture &tex_knob,
             const mathfu::vec2 &size, float bar_height, const char *id,
             float *slider_value);

/// @brief A scrollbar to indicate position in a scroll view.
///
/// @note The background and foreground Textures must be a ninepatch texture.
///
/// @param[in] tex_background A const Texture reference for the background.
/// @param[in] tex_foreground A const Texture reference for the foreground.
/// @param[in] size A const vec2 reference to specify the whole size, including
/// the margin, and the relative size of the scroll bar.
/// @param[in] bar_size A float corresponding to the size of the scroll bar.
/// @param[in] id A C-string to uniquely identify the scroll bar.
/// @param[out] scroll_value A pointer to a float between 0.0 and 1.0 inclusive,
/// which contains the position of the slider.
///
/// @return Returns the Event type for the scroll bar.
Event ScrollBar(const fplbase::Texture &tex_background,
                const fplbase::Texture &tex_foreground,
                const mathfu::vec2 &size, float bar_size, const char *id,
                float *scroll_value);

/// @brief Sets a background color of the widget based on the event status.
///
/// If the event is `kEventIsDown`, the background color used will be the
/// `click_color`. If the event is `kEventHover`, the background color used
/// will be `hover_color`.
///
/// @param[in] event The Event type used to determine the background color.
void EventBackground(Event event);

/// @brief A text button that renders extra contents when toggled.
///
/// @param[in] label A C-string of text to display on the button.
/// @param[in] ysize A float indicating the text button's vertical height.
/// @param[in] margin A Margin that should be placed around the text.
/// @param[in] id A C-string of text that provides the ID for the button.
/// @param[in] contents A void function that executes while the menu is expanded
/// @param[out] expand A pointer to a bool that stores whether or not the
/// group is expanded.
/// which contains the position of the slider.
///
/// @return Returns the Event type for the scroll bar.
Event CollapsibleGroup(const char *label, float ysize, const Margin &margin,
                       const char *id, const std::function<void()> &contents,
                       bool *expand);
/// @}

}  // namespace flatui

#endif  // FPL_FLATUI_COMMON_H


<|start_filename|>include/flatui/internal/fast_antialias_distance_computer.h<|end_filename|>
// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef FLATUI_INTERNAL_FAST_ANTIALIAS_DISTANCE_COMPUTER_H
#define FLATUI_INTERNAL_FAST_ANTIALIAS_DISTANCE_COMPUTER_H

#include "flatui/internal/distance_computer.h"

namespace flatui {

using mathfu::vec2;
using mathfu::vec2i;

// The FastAntialiasDistanceComputer is a tweak of flatui's
// AntialiasDistanceComputer that makes a few concessions to quality
// for speed.
//
// Document describing tradeoffs:
// https://docs.google.com/document/d/1H09JZsB4OyJf7uqJ8q4hkr6SvsEIVn7lP6LOvIhZMts/edit#
template <typename T, typename FundamentalType = T>
class FastAntialiasDistanceComputer :
    public flatui::DistanceComputer<T, FundamentalType> {
 public:
  FastAntialiasDistanceComputer() : any_distance_changed_(false) {}
  ~FastAntialiasDistanceComputer() {}

  // Computes and returns a Grid containing signed distances for a Grid
  // representing a grayscale image.  Each pixel's value ("signed distance") is
  // the distance from the center of that pixel to the nearest boundary/edge,
  // signed so that pixels inside the boundary are negative and those outside
  // the boundary are positive.
  void Compute(const Grid<T, FundamentalType>& image,
               Grid<T, FundamentalType>* dest,
               GlyphFlags flag) override {
    auto original_size = image.GetOriginalSize();
    if (original_size.x == 0 || original_size.y == 0) {
      return;
    }
    auto genearate_inner_distance = flag & kGlyphFlagsInnerSDF;
    auto size = image.GetSize();
    auto width = size.x;
    auto height = size.y;
    // Compute the local gradients in both dimensions.
    gradients_.SetSize(size, mathfu::kZeros2f);
    distances_to_edges_.SetSize(size, mathfu::kZeros2i);
    outer_distances_.SetSize(size, 0.0f);
    distances_ = &outer_distances_;
    image_ = &image;
    destination_ = dest;
    ComputeGradients();

    // Store everything in a struct to pass to the main computation function.
    InitializeDistanceGrid();
    ComputeDistances();

    if (genearate_inner_distance) {
      // Flip the image & gradient, and recalculate a distance field.
      Grid<T, FundamentalType>& inverted_image =
          const_cast<Grid<T, FundamentalType>&>(image);
      auto invert = inverted_image.get_invert();
      inverted_image.invert(true, 0xff);
      gradients_.invert(true, mathfu::kZeros2f);

      distances_ = &inner_distances_;
      inner_distances_.SetSize(size, 0.0f);
      distances_to_edges_.SetSize(size, mathfu::kZeros2i);
      InitializeDistanceGrid();
      ComputeDistances();
      inverted_image.invert(invert, 0xff);
      gradients_.invert(false, mathfu::kZeros2f);
    }

    // Copy the value to the destination buffer.
    for (auto y = 0; y < height; ++y) {
      for (auto x = 0; x < width; ++x) {
        auto pos = vec2i(x, y);
        // Don't return negative distances.
        auto mid =
            static_cast<float>((std::numeric_limits<FundamentalType>::max() +
                                std::numeric_limits<FundamentalType>::min()) /
                               2);
        auto value = distances_->Get(pos);
        if (genearate_inner_distance) {
          value = outer_distances_.Get(pos) - value;
        }
        const float kSDFMultiplier = -16.0f;
        value = mathfu::Clamp(
            value * kSDFMultiplier + mid,
            static_cast<float>(std::numeric_limits<FundamentalType>::min()),
            static_cast<float>(std::numeric_limits<FundamentalType>::max()));
        destination_->Set(pos, T(static_cast<FundamentalType>(value)));
      }
    }
    return;
  }

 private:
  // Computes the local gradients of an image in the X and Y dimensions and
  // returns them as an Array2<Vector2d>.
  void ComputeGradients() {
    const auto w = image_->GetWidth();
    const auto h = image_->GetHeight();

    // This computes the local gradients at pixels near black/white boundaries
    // in the image using convolution filters. The gradient is not needed at
    // other pixels, where it's mostly zero anyway.

    // The 3x3 kernel does not work at the edges, so skip those pixels.
    // TODO: Use a subset of the filter kernels for edge pixels.
    for (auto y = 1; y < h - 1; ++y) {
      for (auto x = 1; x < w - 1; ++x) {
        auto pos = vec2i(x, y);
        auto value = image_->Get(pos);
        // If the pixel is fully on or off, leave the gradient as (0,0).
        // Otherwise, compute it.
        if (value > std::numeric_limits<T>::min() &&
            value < std::numeric_limits<T>::max()) {
          gradients_.Set(pos, FilterPixel(pos));
        }
      }
    }
    return;
  }

  // Applies a 3x3 filter kernel to an image pixel to get the gradients.
  const vec2 FilterPixel(const vec2i& pos) {
    // 3x3 filter kernel. The X gradient uses the array as is and the Y gradient
    // uses the transpose.
    static const float kSqrt2 = sqrtf(2.0f);
    static const float kFilter[3][3] = {
        {-1.0f, 0.0f, 1.0f}, {-kSqrt2, 0.0f, kSqrt2}, {-1.0f, 0.0f, 1.0f}};

    vec2 filtered(0.0f, 0.0f);
    for (int i = 0; i < 3; ++i) {
      for (int j = 0; j < 3; ++j) {
        float val = image_->Get(pos + vec2i(j - 1, i - 1));
        filtered[0] += kFilter[i][j] * val;
        filtered[1] += kFilter[j][i] * val;
      }
    }
    return filtered.Normalized();
  }

  // Creates and initializes a grid containing the distances.
  void InitializeDistanceGrid() {
    const auto w = image_->GetWidth();
    const auto h = image_->GetHeight();
    for (auto y = 0; y < h; ++y) {
      for (auto x = 0; x < w; ++x) {
        auto pos = vec2i(x, y);
        auto v = static_cast<float>(image_->Get(pos)) /
                 std::numeric_limits<T>::max();
        auto dist = v <= 0.0f ? kLargeDistance
                              : v >= 1.0f ? 0.0f : ApproximateDistanceToEdge(
                                                       v, gradients_.Get(pos));
        distances_->Set(pos, dist);
      }
    }
  }

  // Approximates the distance to an image edge from a pixel using the pixel
  // value and the local gradient.
  float ApproximateDistanceToEdge(float value, const vec2& gradient) {
    if (gradient[0] == 0.0f || gradient[1] == 0.0f) {
      // Approximate the gradient linearly using the middle of the range.
      return 0.5f - value;
    } else {
      // Since the gradients are symmetric with respect to both sign and X/Y
      // transposition, do the work in the first octant (positive gradients, x
      // gradient >= y gradient) for simplicity.
      vec2 g = vec2(fabsf(gradient[0]), fabsf(gradient[1])).Normalized();
      // The following isnan checks are needed because if the gradients_ Grid
      // is inverted then gradients_->Get will do a FLT_MAX - value, which will
      // mean |gradient| will contain FLT_MAX, which will cause Normalized()
      // to return NaNs on some platforms (iOS being one).  This is a very
      // common case, not a rare case, and happens for pixels near the glyph
      // edge.  If we hit this case, then we approximate linearly as above.
      if (isnan(g.x) || isnan(g.y)) {
        return 0.5f - value;
      }
      if (g.x < g.y) std::swap(g.x, g.y);
      const auto gradient_value = static_cast<float>(0.5f * g.y / g.x);
      float dist;
      if (value < gradient_value) {
        // 0 <= value < gradient_value.
        dist = 0.5f * (g.x + g.y) - sqrtf(2.0f * g.x * g.y * value);
      } else if (value < 1.0f - gradient_value) {
        // gradient_value <= value <= 1 - gradient_value.
        dist = (0.5f - value) * g.x;
      } else {
        // 1 - gradient_value < value <= 1.
        dist = -0.5f * (g.x + g.y) + sqrtf(2.0f * g.x * g.y * (1.0f - value));
      }
      return dist;
    }
  }

  // Computes and returns the distances.
  void ComputeDistances() {
    const auto width = image_->GetWidth();
    const auto height = image_->GetHeight();

    // Keep processing while distances are being modified.
    do {
      any_distance_changed_ = false;

      // Propagate from top down, starting with the second row.
      for (auto y = 1; y < height; ++y) {
        // Propagate distances to the right.
        for (auto x = 1; x < width; ++x) {
          auto pos = vec2i(x, y);
          auto dist = distances_->Get(pos);
          if (dist > 0.0f) {
            UpdateDistance(pos, vec2i(0, -1), &dist);
          }
        }

        // Propagate distances to the left (skip the rightmost pixel).
        for (auto x = width - 2; x >= 0; --x) {
          auto pos = vec2i(x, y);
          auto dist = distances_->Get(pos);
          if (dist > 0.0f) {
            UpdateDistance(pos, vec2i(1, 0), &dist);
          }
        }
      }
    } while (any_distance_changed_);

    do {
      any_distance_changed_ = false;
      // Propagate from bottom up, starting with the second row from the bottom.
      for (auto y = height - 2; y >= 0; --y) {
        // Propagate distances to the left.
        for (auto x = width - 1; x >= 0; --x) {
          auto pos = vec2i(x, y);
          auto dist = distances_->Get(pos);
          if (dist > 0.0) {
            UpdateDistance(pos, vec2i(0, 1), &dist);
          }
        }

        // Propagate distances to the right (skip the leftmost pixel).
        for (auto x = 1; x < width; ++x) {
          auto pos = vec2i(x, y);
          auto dist = distances_->Get(pos);
          if (dist > 0.0) {
            UpdateDistance(pos, vec2i(-1, 0), &dist);
          }
        }
      }
    } while (any_distance_changed_);
  }

  // Computes the distance from data->cur_pixel to an edge pixel based on the
  // information at the pixel at (data->cur_pixel + offset). If the new
  // distance is smaller than the current distance (dist), this modifies dist
  // and sets data->any_distance_changed to true.
  void UpdateDistance(const vec2i& pos, const vec2i& offset, float* dist) {
    const vec2i test_pixel = pos + offset;
    const vec2i xy_dist = distances_to_edges_.Get(test_pixel);
    const vec2i edge_pixel = test_pixel - xy_dist;
    const vec2i new_xy_dist = xy_dist - offset;
    const auto new_dist = ComputeDistanceToEdge(edge_pixel, new_xy_dist);
    static const float kEpsilon = 1e-3f;
    if (new_dist < *dist - kEpsilon) {
      distances_->Set(pos, new_dist);
      distances_to_edges_.Set(pos, new_xy_dist);
      *dist = new_dist;
      any_distance_changed_ = true;
    }
  }

  // Computes the new distance from a pixel to an edge pixel based on previous
  // information.
  float ComputeDistanceToEdge(const vec2i& pixel,
                              const vec2i& vec_to_edge_pixel) {
    auto pixel_value = image_->Get(pixel);

    // If the pixel value is negative or 0, return kLargeDistance so that
    // processing will continue.
    if (pixel_value == 0) return kLargeDistance;

    // The returned value is expected in range of 0.0 - 1.0.
    const auto normalized_value =
        static_cast<float>(pixel_value) / std::numeric_limits<T>::max();

    vec2 vec_to_edge_pixel_f = vec2(vec_to_edge_pixel);

    // Use the length of the vector to the edge pixel to estimate the real
    // distance to the edge.
    // Optimization: This code was reworked to avoid fcmp since it is expensive.
    float length;
    bool should_estimate_based_on_direction_to_edge;
    if (vec_to_edge_pixel.x == 0) {
      length = abs(vec_to_edge_pixel.y);
      should_estimate_based_on_direction_to_edge = vec_to_edge_pixel.y != 0;
    } else if (vec_to_edge_pixel.y == 0) {
      length = abs(vec_to_edge_pixel.x);
      should_estimate_based_on_direction_to_edge = vec_to_edge_pixel.x != 0;
    } else {
      length = vec_to_edge_pixel_f.Length();
      should_estimate_based_on_direction_to_edge = length > 0.0f;
    }

    const auto dist =
        should_estimate_based_on_direction_to_edge ?
            // Estimate based on direction to edge (accurate for large vectors).
            ApproximateDistanceToEdge(normalized_value, vec_to_edge_pixel_f)
                      :
                      // Estimate based on local gradient only.
            ApproximateDistanceToEdge(normalized_value, gradients_.Get(pixel));
    return length + dist;
  }

  // The original monochrome image data, as floats (0 - 1).
  const Grid<T>* image_;
  // Destination buffer.
  Grid<T>* destination_;
  // Local gradients in X and Y.
  Grid<vec2, float> gradients_;
  // Current pixel distances in X and Y to edges.
  Grid<vec2i, int> distances_to_edges_;
  // Final distance values.
  Grid<float>* distances_;
  Grid<float> inner_distances_;
  Grid<float> outer_distances_;

  // This is set to true when a value in the distances grid is modified.
  bool any_distance_changed_;
};

}  // namespace vr

#endif  // FLATUI_INTERNAL_FAST_ANTIALIAS_DISTANCE_COMPUTER_H


<|start_filename|>include/flatui/internal/distance_computer.h<|end_filename|>
// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef FLATUI_INTERNAL_DISTANCE_COMPUTER_H
#define FLATUI_INTERNAL_DISTANCE_COMPUTER_H

#include "flatui/internal/glyph_cache.h"

namespace flatui {

using mathfu::vec2;
using mathfu::vec2i;

/// @cond FLATUI_INTERNAL
// A helper template class to access buffer data with padding.
template <typename T, typename FundamentalType = T>
class Grid {
 public:
  // Constructors.
  Grid()
      : data_(nullptr),
        size_(mathfu::kZeros2i),
        padding_(0),
        stride_(0),
        inverted_(false),
        p_(nullptr) {}
  Grid(T* data, const vec2i& size, int32_t padding, size_t stride)
      : data_(data),
        size_(size),
        padding_(padding),
        stride_(static_cast<int32_t>(stride)),
        inverted_(false),
        p_(nullptr) {}

  // Set up the grid with a buffer allocation.
  void SetSize(const vec2i& size, T initial_value) {
    size_ = size;
    padding_ = 0;
    stride_ = size.x;
    if (p_ == nullptr) {
      p_ = std::unique_ptr<std::vector<T>>(new std::vector<T>());
    } else {
      p_->clear();
    }
    p_->resize(size.x * size.y, initial_value);
    data_ = &(*p_)[0];
    return;
  }

  int32_t GetWidth() const { return size_.x + padding_ * 2; }
  int32_t GetHeight() const { return size_.y + padding_ * 2; }
  const vec2i GetSize() const {
    return size_ + vec2i(padding_ * 2, padding_ * 2);
  }
  const vec2i GetOriginalSize() const { return size_; }

  // Setter to the buffer.
  void Set(const vec2i& pos, T value) {
    vec2i p = pos - vec2i(padding_, padding_);
    if (!mathfu::InRange2D(p, mathfu::kZeros2i, size_)) {
      return;
    }
    data_[p.x + p.y * stride_] = value;
  }

  // Getter to the buffer.
  T Get(const vec2i& pos) const {
    vec2i p = pos - vec2i(padding_, padding_);
    if (inverted_) {
      if (!mathfu::InRange2D(p, mathfu::kZeros2i, size_)) {
        return T(std::numeric_limits<FundamentalType>::max());
      }
      return T(std::numeric_limits<FundamentalType>::max()) -
             data_[p.x + p.y * stride_];
    } else {
      if (!mathfu::InRange2D(p, mathfu::kZeros2i, size_)) {
        return T(std::numeric_limits<FundamentalType>::min());
      }
      return data_[p.x + p.y * stride_];
    }
  }

  // Change an inversion setting in the grid when retrieving a data.
  void invert(bool b, const T& invert_reference) {
    inverted_ = b;
    invert_reference_ = invert_reference;
  }
  bool get_invert() const { return inverted_; }

  std::unique_ptr<std::vector<T>> get_buffer() {
    return p_;
  };

 protected:
  T* data_;
  vec2i size_;
  int32_t padding_;
  int32_t stride_;
  bool inverted_;
  T invert_reference_;
  std::unique_ptr<std::vector<T>> p_;
};

// Represents a large distance during computation.
static const float kLargeDistance = 1e6;

/// @cond FLATUI_INTERNAL
//
// The DistanceComputer class implements the main functions to compute signed
// distance fields.
//
template <typename T, typename FundamentalType = T>
class DistanceComputer {
 public:
  virtual ~DistanceComputer() {}

  // Computes and returns a Grid containing signed distances for a Grid
  // representing a grayscale image.  Each pixel's value ("signed distance") is
  // the distance from the center of that pixel to the nearest boundary/edge,
  // signed so that pixels inside the boundary are negative and those outside
  // the boundary are positive.
  virtual void Compute(const Grid<T, FundamentalType>& image,
                       Grid<T, FundamentalType>* dest,
                       GlyphFlags flag) = 0;

};

}  // namespace flatui

#endif  // FLATUI_INTERNAL_DISTANCE_COMPUTER_H
