Commit e2b8042b authored by Paul Asmuth's avatar Paul Asmuth
Browse files

split bidi text into runs using fribidi

parent 33c442ca
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -59,7 +59,7 @@ file(GLOB source_files "core/*.cc" "core/**/*.cc" "elements/*.cc" "elements/**/*
list(REMOVE_ITEM source_files "core/cli.cc")
add_library(fviz STATIC ${source_files})
set_target_properties(fviz PROPERTIES PUBLIC_HEADER "core/fviz.h")
set(fviz_LDFLAGS fviz ${CAIRO_LIBRARIES} ${FREETYPE_LIBRARIES} ${HARFBUZZ_LIBRARIES} ${HARFBUZZ_ICU_LIBRARIES} ${PNG_LIBRARIES} ${FONTCONFIG_LIBRARIES} ${Boost_LIBRARIES} fmt)
set(fviz_LDFLAGS fviz ${CAIRO_LIBRARIES} ${FREETYPE_LIBRARIES} ${HARFBUZZ_LIBRARIES} ${HARFBUZZ_ICU_LIBRARIES} ${PNG_LIBRARIES} ${FONTCONFIG_LIBRARIES} ${Boost_LIBRARIES} fmt fribidi)


# Build: CLI
+44 −0
Original line number Diff line number Diff line
@@ -26,6 +26,50 @@ enum class TextDirection {
  LTR, RTL
};

/**
 * A text span represents a discrete, non-breakable piece of text. Text spans are
 * the smallest unit of text for layout.
 *
 * If any kind of line wrapping is desired, the input text must be split into
 * text spans so that line breaking can be performed on a per span basis. One
 * important caveat of this is that text shaping will also be performed on a
 * per-span basis, so the spans should be as large as possible for the best text
 * shaping results.
 *
 * In latin scripts, text spans should probably correspond to word boundaries.
 * In other scripts, text spans should correspond to whatever unit of text is
 * small enough to allow for text breaking on span-level but large enough so that
 * per-span shaping of text is sufficient.
 *
 * It is allowed to break the line after or before each text span, i.e. spans
 * must be given so that it is legal to place each text span at the beginning of
 * a new line. However it is *not* allowed to break a span itself into smaller
 * pieces; all text runs within the span must be put onto the same line.
 *
 * This interface should enable us to have a high degree of decoupling between
 * the text shaping and layout parts. However, one tradeoff is that it does not
 * allow users to implement some advanced features such as line breaking at
 * character boundaries (i.e breaking and hyphenization of words).
 *
 */
struct TextSpan {
  /**
   * The `text_runs` member contains the spans text runs as a UTF-8 strings.
   * Note that the runs are given in logical order and the characters within
   * the runs are also stored in logical order.
   *
   * Non-bidirectional text spans should usually have exactly one text run while
   * bidirectional text should have N + 1 runs where N is the number of writing
   * direction boundaries in the text span.
   */
  std::vector<std::string> text_runs;

  /**
   * Stores the bidi text direction for each run
   */
  std::vector<TextDirection> text_directions;
};

struct TextStyle {
  TextStyle();
  TextDirection direction;
+38 −0
Original line number Diff line number Diff line
/**
 * This file is part of the "fviz" project
 *   Copyright (c) 2018 Paul Asmuth
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * 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.
 */
#pragma once
#include <string>
#include <unordered_map>
#include <functional>

#include "text.h"

namespace fviz::text {

/**
 * Analyze a UTF-8 input string in logical character order and produce a text
 * span. The output text span will contain all text runs within the span in
 * logical order together with their corresponding directionality information.
 *
 * Note that this method returns a single text span, i.e. a non-breakable unit
 * of text. If line breaking is desired, identification of possible breakpoints
 * (spans) must be handled by a mechanism higher up in the stack.
 */
ReturnCode text_analyze_bidi_span(
    const std::string& text,
    TextDirection text_direction,
    TextSpan* text_span);

} // namespace fviz::text
+90 −0
Original line number Diff line number Diff line
/**
 * This file is part of the "fviz" project
 *   Copyright (c) 2018 Paul Asmuth
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * 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 "text_backend.h"

#include <fribidi/fribidi.h>

namespace fviz::text {

ReturnCode text_analyze_bidi_span(
    const std::string& text,
    TextDirection text_direction,
    TextSpan* text_span) {
  FriBidiParType fb_basedir;
  switch (text_direction) {
    case TextDirection::LTR:
      fb_basedir = FRIBIDI_PAR_LTR;
      break;
    case TextDirection::RTL:
      fb_basedir = FRIBIDI_PAR_RTL;
      break;
  }

  // convert to fribidi string
  std::vector<FriBidiChar> fb_str(text.size(), 0);
  auto fb_str_len = fribidi_charset_to_unicode(
      FRIBIDI_CHAR_SET_UTF8,
      text.data(),
      text.size(),
      fb_str.data());

  // find character directionalities and embedding levels using fribidi
  std::vector<FriBidiCharType> fb_types(fb_str_len, 0);
  std::vector<FriBidiLevel> fb_levels(fb_str_len, 0);
  fribidi_get_bidi_types(fb_str.data(), fb_str_len, fb_types.data());

  auto fribidi_rc = fribidi_get_par_embedding_levels(
        fb_types.data(),
        fb_str_len,
        &fb_basedir,
        fb_levels.data());

  if (!fribidi_rc) {
    return errorf(ERROR, "error while performing bidi layout using fribidi");
  }

  // find the bidi runs in the output
  std::vector<size_t> run_bounds;
  for (size_t i = 0; i < fb_str_len; ++i) {
    if (i > 0 && fb_levels[i] != fb_levels[i - 1]) {
      run_bounds.emplace_back(i);
    }
  }

  for (size_t i = 0; i < run_bounds.size() + 1; ++i) {
    auto run_begin = i == 0 ? 0 : run_bounds[i - 1];
    auto run_end = i == run_bounds.size() ? fb_str_len : run_bounds[i];
    auto run_len = run_end - run_begin;

    std::string run(run_len * 4 + 1, 0);
    run.resize(fribidi_unicode_to_charset(
        FRIBIDI_CHAR_SET_UTF8,
        fb_str.data() + run_begin,
        run_len,
        run.data()));

    auto run_direction =
        int(fb_levels[run_begin]) & 1 ?
            TextDirection::RTL :
            TextDirection::LTR;

    text_span->text_runs.emplace_back(run);
    text_span->text_directions.emplace_back(run_direction);
  }

  return OK;
}

} // namespace fviz::text
+25 −3
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@
 * limitations under the License.
 */
#include "fviz.h"
#include "graphics/text_backend.h"
#include "graphics/text_layout.h"
#include "graphics/text_shaper.h"

@@ -19,16 +20,37 @@ namespace fviz {
namespace text {

Status text_layout_hspan(
    const std::string& text,
    const std::string& text_logical,
    const TextDirection text_direction,
    const FontInfo& font_info,
    double font_size,
    double dpi,
    std::vector<GlyphSpan>* glyph_spans,
    Rectangle* bbox) {
  TextSpan text_span;
  text_analyze_bidi_span(text_logical, text_direction, &text_span);

  for (size_t i = 0; i < text_span.text_runs.size(); ++i) {
    std::string text_run_dir;
    switch (text_span.text_directions[i]) {
      case TextDirection::LTR:
        text_run_dir = "ltr";
        break;
      case TextDirection::RTL:
        text_run_dir = "rtl";
        break;
    }

    std::cerr
        << "t=" << text_span.text_runs[i]
        << " "
        << "d=" << text_run_dir
        << std::endl;
  }

  std::vector<GlyphInfo> glyph_list;
  auto shaping_rc = text_shape_with_font_fallback(
      text,
  auto shaping_rc = text_shape_run_with_font_fallback(
      text_logical,
      text_direction,
      font_info,
      font_size,
Loading