Commit 72d98a9d authored by Paul Asmuth's avatar Paul Asmuth
Browse files

refactor the text layout interface so that bidi reordering of lines becomes an external concern

parent a0037ecd
Loading
Loading
Loading
Loading
+23 −41
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
#include <graphics/text.h>
#include <graphics/text_shaper.h>
#include "graphics/text_layout.h"
#include "graphics/text_support.h"
#include <graphics/layer.h>

namespace fviz {
@@ -22,35 +23,36 @@ TextStyle::TextStyle() :
    direction(TextDirection::LTR) {}

Status drawTextLabel(
    const text::TextSpan* text_begin,
    const text::TextSpan* text_end,
    const std::string& text,
    const Point& position,
    HAlign align_x,
    VAlign align_y,
    double rotate,
    const TextStyle& style,
    Layer* layer) {
  std::string text;
  for (auto text_iter = text_begin; text_iter != text_end; ++text_iter) {
    text += text_iter->text;
  text::TextSpan span;
  span.text_direction = style.direction;
  span.text = text;
  span.font = style.font;
  span.font_size = style.font_size;
  span.span_id = 0;

  text::TextLine line;
  line.spans.push_back(span);
  line.base_direction = style.direction;

  if (auto rc = text_reorder_bidi_line(&line); !rc) {
    return ERROR;
  }

  Rectangle bbox;
  std::vector<text::GlyphPlacementGroup> glyphs;
  auto rc = text::text_layout_line(
      text_begin,
      text_end,
      style.direction,
      layer->dpi,
      &glyphs,
      &bbox);

  auto offset = layout_align(bbox, position, align_x, align_y);

  if (rc != OK) {
  if (auto rc = text::text_layout_line(line, layer->dpi, &glyphs, &bbox); !rc) {
    return rc;
  }

  auto offset = layout_align(bbox, position, align_x, align_y);

  for (auto& gg : glyphs) {
    for (auto& g : gg.glyphs) {
      g.x += offset.x;
@@ -73,24 +75,6 @@ Status drawTextLabel(
  return layer->apply(op);
}

Status drawTextLabel(
    const std::string& text,
    const Point& position,
    HAlign align_x,
    VAlign align_y,
    double rotate,
    const TextStyle& style,
    Layer* layer) {
  text::TextSpan span;
  span.text_direction = style.direction;
  span.text = text;
  span.font = style.font;
  span.font_size = style.font_size;
  span.span_id = 0;

  return drawTextLabel(&span, &span + 1, position, align_x, align_y, 0, style, layer);
}

Status drawTextLabel(
    const std::string& text,
    const Point& position,
@@ -115,13 +99,11 @@ Status text_measure_label(
  span.font_size = font_size;
  span.span_id = 0;

  return text_layout_line(
      &span,
      &span + 1,
      text_direction_base,
      dpi,
      nullptr,
      bbox);
  text::TextLine line;
  line.spans.push_back(span);
  line.base_direction = text_direction_base;

  return text_layout_line(line, dpi, nullptr, bbox);
}

} // namespace fviz
+0 −10
Original line number Diff line number Diff line
@@ -37,16 +37,6 @@ struct TextStyle {
  Color color;
};

Status drawTextLabel(
    const text::TextSpan* text_begin,
    const text::TextSpan* text_end,
    const Point& position,
    HAlign align_x,
    VAlign align_y,
    double rotate,
    const TextStyle& text_style,
    Layer* layer);

Status drawTextLabel(
    const std::string& text,
    const Point& position,
+5 −104
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@
namespace fviz {
namespace text {

Status text_place_hrun(
Status text_layout_span(
    const TextSpan& span,
    double dpi,
    std::vector<GlyphPlacement>* glyphs,
@@ -60,7 +60,7 @@ Status text_place_hrun(
  return OK;
}

Status text_place_hline(
Status text_layout_line(
    const TextLine& text_line,
    double dpi,
    std::vector<GlyphPlacementGroup>* glyphs,
@@ -68,11 +68,11 @@ Status text_place_hline(
  double line_top = 0.0;
  double line_bottom = 0.0;
  double line_length = 0;
  for (auto i : text_line.visual_order) {
  for (const auto& span : text_line.spans) {
    double span_length = 0.0;
    std::vector<GlyphPlacement> span_glyphs;
    auto rc = text_place_hrun(
        text_line.spans[i],
    auto rc = text_layout_span(
        span,
        dpi,
        &span_glyphs,
        &span_length,
@@ -126,105 +126,6 @@ Status text_place_hline(
  return OK;
}

/**
 * Determine the visual order of text runs in a line according to the unicode
 * bidi algorithm
 *
 *   "From the highest level found in the text to the lowest odd level on each
 *    line, including intermediate levels not actually present in the text,
 *    reverse any contiguous sequence of characters that are at that level or
 *    higher."
 *
 * Source: https://unicode.org/reports/tr9/#Reordering_Resolved_Levels
 *
 */
std::vector<size_t> text_reorder_line(const std::vector<int>& bidi_levels) {
  std::vector<size_t> visual_order(bidi_levels.size(), 0);

  std::iota(
      visual_order.begin(),
      visual_order.end(),
      0);

  size_t level_max = *std::max_element(
      bidi_levels.begin(),
      bidi_levels.end());

  for (size_t level_cur = level_max; level_cur >= 1; --level_cur) {
    for (size_t range_begin = 0; range_begin < bidi_levels.size(); ) {
      // find the next contiguous range where level >= level_cur starting at
      // begin
      auto range_end = range_begin;
      for (;
          bidi_levels[range_end] >= level_cur &&
          range_end != bidi_levels.size();
          ++range_end);

      // if no such sequence starts at begin, try searching from the next index
      if (range_end == range_begin) {
        ++range_begin;
        continue;
      }

      // reverse runs in the range
      std::reverse(
          visual_order.begin() + range_begin,
          visual_order.begin() + range_end);

      // continue searching from the end of the swapped range
      range_begin = range_end;
    }
  }

  return visual_order;
}

Status text_layout_line(
    const TextSpan* text_begin,
    const TextSpan* text_end,
    const TextDirection text_direction_base,
    double dpi,
    std::vector<GlyphPlacementGroup>* glyphs,
    Rectangle* bbox) {
  // prepare a new text line for layout
  TextLine text_line;
  text_line.base_direction = text_direction_base;

  // split spans using the unicode bidi algorithm and compute visual span order
  std::vector<int> bidi_levels;
  if (auto rc =
        text_analyze_bidi_line(
            text_begin,
            text_end,
            text_direction_base,
            &text_line.spans,
            &bidi_levels);
      !rc) {
    return ERROR;
  }

  text_line.visual_order = text_reorder_line(bidi_levels);

  // if the base direction is RTL, reverse the direction of all runs so that
  // the "first" element in the visual order array is the one at the begining
  // of the line in our base writing direction
  switch (text_direction_base) {
    case TextDirection::LTR:
      break;
    case TextDirection::RTL:
      std::reverse(
          text_line.visual_order.begin(),
          text_line.visual_order.end());
  }

  // place the text glyphs on the screen
  return text_place_hline(
      text_line,
      dpi,
      glyphs,
      bbox);
}

} // namespace text
} // namespace fviz
+1 −3
Original line number Diff line number Diff line
@@ -103,9 +103,7 @@ struct GlyphPlacementGroup {
 * writing direction is not yet implemented.
 */
Status text_layout_line(
    const TextSpan* text_begin,
    const TextSpan* text_end,
    TextDirection text_direction_base,
    const TextLine& text_line,
    double dpi,
    std::vector<GlyphPlacementGroup>* glyphs,
    Rectangle* bbox);
+105 −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 "text_support.h"
#include "text_layout.h"

#include <numeric>

namespace fviz::text {

ReturnCode text_reorder_bidi_line(TextLine* line) {
  std::vector<TextSpan> spans;

  // Split spans of the line using the unicode bidi algorithm and extract the
  // per-span bidi embedding levels
  std::vector<int> bidi_levels;
  if (auto rc =
        text_analyze_bidi_line(
            &*line->spans.begin(),
            &*line->spans.end(),
            line->base_direction,
            &spans,
            &bidi_levels);
      !rc) {
    return rc;
  }

  // Determine the correct visual order of spans according to the unicode spec
  //
  //   "From the highest level found in the text to the lowest odd level on each
  //    line, including intermediate levels not actually present in the text,
  //    reverse any contiguous sequence of characters that are at that level or
  //    higher."
  //
  // Source: https://unicode.org/reports/tr9/#Reordering_Resolved_Levels
  std::vector<size_t> visual_order(bidi_levels.size(), 0);

  std::iota(
      visual_order.begin(),
      visual_order.end(),
      0);

  size_t level_max = *std::max_element(
      bidi_levels.begin(),
      bidi_levels.end());

  for (size_t level_cur = level_max; level_cur >= 1; --level_cur) {
    for (size_t range_begin = 0; range_begin < bidi_levels.size(); ) {
      // find the next contiguous range where level >= level_cur starting at
      // begin
      auto range_end = range_begin;
      for (;
          bidi_levels[range_end] >= level_cur &&
          range_end != bidi_levels.size();
          ++range_end);

      // if no such sequence starts at begin, try searching from the next index
      if (range_end == range_begin) {
        ++range_begin;
        continue;
      }

      // reverse runs in the range
      std::reverse(
          visual_order.begin() + range_begin,
          visual_order.begin() + range_end);

      // continue searching from the end of the swapped range
      range_begin = range_end;
    }
  }

  // if the base direction is RTL, reverse the direction of all runs so that
  // the "first" element in the visual order array is the one at the begining
  // of the line in our base writing direction
  switch (line->base_direction) {
    case TextDirection::LTR:
      break;
    case TextDirection::RTL:
      std::reverse(visual_order.begin(), visual_order.end());
  }

  // store the re-ordered spans back into the line
  line->spans.clear();

  for (auto i : visual_order) {
    line->spans.push_back(spans[i]);
  }

  return OK;
}

} // namespace fviz::text
Loading