Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/raqm-sections.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ raqm_add_font_feature
raqm_layout
raqm_get_glyphs
raqm_get_par_resolved_direction
raqm_get_par_detected_direction
raqm_get_direction_at_index
raqm_index_to_position
raqm_position_to_index
Expand Down
80 changes: 80 additions & 0 deletions src/raqm.c
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,37 @@ raqm_get_par_resolved_direction (raqm_t *rq)
return rq->resolved_dir;
}

static raqm_direction_t
_raqm_detect_direction (raqm_t *rq);

/**
* raqm_get_par_detected_direction:
* @rq: a #raqm_t.
*
* Gets the natural direction of the text as detected from its content
* following the Unicode Bidirectional Algorithm, regardless of the base
* direction set with raqm_set_par_direction(). Unlike
* raqm_get_par_resolved_direction(), this does not require raqm_layout() to
* have been called, and can report that the text is direction neutral.
*
* Return value:
* #RAQM_DIRECTION_LTR or #RAQM_DIRECTION_RTL if the text has a strong
* direction, or #RAQM_DIRECTION_DEFAULT if the text is direction neutral (has
* no strong characters) or no text has been set. #RAQM_DIRECTION_TTB is never
* returned, as vertical direction is a layout property, not a property of the
* text content.
*
* Since: 0.11
*/
raqm_direction_t
raqm_get_par_detected_direction (raqm_t *rq)
{
if (!rq || !rq->text || rq->text_len == 0)
return RAQM_DIRECTION_DEFAULT;

return _raqm_detect_direction (rq);
}

/**
* raqm_get_direction_at_index:
* @rq: a #raqm_t.
Expand Down Expand Up @@ -1608,6 +1639,31 @@ _raqm_bidi_itemize (raqm_t *rq, size_t *run_count)

return runs;
}

static raqm_direction_t
_raqm_detect_direction (raqm_t *rq)
{
raqm_direction_t dir = RAQM_DIRECTION_DEFAULT;
size_t isolate = 0;

/* UBA: P2 and P3. */
for (size_t i = 0; i < rq->text_len; i++)
{
SBBidiType type = SBCodepointGetBidiType (rq->text[i]);

if (type == SBBidiTypeLRI || type == SBBidiTypeRLI || type == SBBidiTypeFSI)
isolate++;
else if (type == SBBidiTypePDI && isolate > 0)
isolate--;
else if (isolate == 0 && SBBidiTypeIsStrong (type))
{
dir = (type == SBBidiTypeL) ? RAQM_DIRECTION_LTR : RAQM_DIRECTION_RTL;
break;
}
}

return dir;
}
#else
static void
_raqm_reverse_run (_raqm_bidi_run *run, const size_t len)
Expand Down Expand Up @@ -1761,6 +1817,30 @@ _raqm_bidi_itemize (raqm_t *rq, size_t *run_count)

return runs;
}

static raqm_direction_t
_raqm_detect_direction (raqm_t *rq)
{
raqm_direction_t dir = RAQM_DIRECTION_DEFAULT;
FriBidiParType par_type;
FriBidiCharType *types;

types = calloc (rq->text_len, sizeof (FriBidiCharType));
if (types)
{
fribidi_get_bidi_types (rq->text, rq->text_len, types);
par_type = fribidi_get_par_direction (types, rq->text_len);

if (par_type == FRIBIDI_PAR_LTR)
dir = RAQM_DIRECTION_LTR;
else if (par_type == FRIBIDI_PAR_RTL)
dir = RAQM_DIRECTION_RTL;

free (types);
}

return dir;
}
#endif

static bool
Expand Down
3 changes: 3 additions & 0 deletions src/raqm.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ raqm_get_glyphs (raqm_t *rq,
RAQM_API raqm_direction_t
raqm_get_par_resolved_direction (raqm_t *rq);

RAQM_API raqm_direction_t
raqm_get_par_detected_direction (raqm_t *rq);

RAQM_API raqm_direction_t
raqm_get_direction_at_index (raqm_t *rq,
size_t index);
Expand Down
96 changes: 96 additions & 0 deletions tests/detected-direction-test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Detected paragraph direction test.
*
* Verifies that raqm_get_par_detected_direction reports the natural direction
* of the text as detected from its content, regardless of the base direction
* set by the user, and reports direction-neutral text as
* RAQM_DIRECTION_DEFAULT.
*/

#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include "raqm.h"

static raqm_direction_t
detect (const char *utf8, raqm_direction_t base)
{
raqm_direction_t dir;
raqm_t *rq = raqm_create ();
assert (rq);
assert (raqm_set_text_utf8 (rq, utf8, strlen (utf8)));
assert (raqm_set_par_direction (rq, base));
dir = raqm_get_par_detected_direction (rq);
raqm_destroy (rq);
return dir;
}

int
main (void)
{
const char *ltr = "English";
const char *rtl = "عربي";
const char *neutral = "123.!";

/* Detected direction follows the content. */
assert (detect (ltr, RAQM_DIRECTION_DEFAULT) == RAQM_DIRECTION_LTR);
assert (detect (rtl, RAQM_DIRECTION_DEFAULT) == RAQM_DIRECTION_RTL);
assert (detect (neutral, RAQM_DIRECTION_DEFAULT) == RAQM_DIRECTION_DEFAULT);

/* ... regardless of the base direction set by the user. */
assert (detect (ltr, RAQM_DIRECTION_RTL) == RAQM_DIRECTION_LTR);
assert (detect (rtl, RAQM_DIRECTION_LTR) == RAQM_DIRECTION_RTL);
assert (detect (neutral, RAQM_DIRECTION_RTL) == RAQM_DIRECTION_DEFAULT);
assert (detect (neutral, RAQM_DIRECTION_LTR) == RAQM_DIRECTION_DEFAULT);

/* It is decided by the first strong character (Unicode Bidi rule P2). */
assert (detect ("A عربي", RAQM_DIRECTION_DEFAULT) == RAQM_DIRECTION_LTR);
assert (detect ("عربي A", RAQM_DIRECTION_DEFAULT) == RAQM_DIRECTION_RTL);

/* Leading weak/neutral characters are skipped to the first strong one. */
assert (detect ("123 English", RAQM_DIRECTION_DEFAULT) == RAQM_DIRECTION_LTR);
assert (detect ("123 عربي", RAQM_DIRECTION_DEFAULT) == RAQM_DIRECTION_RTL);

/* A vertical base direction is a layout property, not a content one, so it
* is ignored and never reported back. */
assert (detect (ltr, RAQM_DIRECTION_TTB) == RAQM_DIRECTION_LTR);
assert (detect (rtl, RAQM_DIRECTION_TTB) == RAQM_DIRECTION_RTL);
assert (detect (neutral, RAQM_DIRECTION_TTB) == RAQM_DIRECTION_DEFAULT);

/* The direction is read from the text content whichever setter was used. */
{
/* UTF-16: Arabic ARE (U+0639 U+0631 U+0628 U+064A), all in the BMP. */
const uint16_t utf16_rtl[] = { 0x0639, 0x0631, 0x0628, 0x064A };
/* UTF-32: an astral strong-LTR character (U+1D400, outside the BMP). */
const uint32_t utf32_ltr[] = { 0x1D400 };
raqm_t *rq;

rq = raqm_create ();
assert (rq);
assert (raqm_set_text_utf16 (rq, utf16_rtl, 4));
assert (raqm_get_par_detected_direction (rq) == RAQM_DIRECTION_RTL);
raqm_destroy (rq);

rq = raqm_create ();
assert (rq);
assert (raqm_set_text (rq, utf32_ltr, 1));
assert (raqm_get_par_detected_direction (rq) == RAQM_DIRECTION_LTR);
raqm_destroy (rq);
}

/* Text that was never set, explicitly empty text, or a NULL handle is all
* direction neutral. */
{
raqm_t *rq = raqm_create ();
assert (rq);
assert (raqm_get_par_detected_direction (rq) == RAQM_DIRECTION_DEFAULT);
assert (raqm_set_text_utf8 (rq, "", 0));
assert (raqm_get_par_detected_direction (rq) == RAQM_DIRECTION_DEFAULT);
raqm_destroy (rq);
}
assert (raqm_get_par_detected_direction (NULL) == RAQM_DIRECTION_DEFAULT);

return 0;
}
15 changes: 15 additions & 0 deletions tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ test(
args: [files('GraphemeBreakTest.txt')],
)

detected_direction_test = executable(
'detected-direction-test',
'detected-direction-test.c',
include_directories: include_directories(['../src']),
link_with: libraqm_test,
override_options: ['b_ndebug=false'],
dependencies: deps,
install: false,
)

test(
'detected-direction',
detected_direction_test,
)

foreach filename : tests
testname = filename.split('.')[0]

Expand Down
Loading