rockbox/firmware/bidi.c

299 lines
8.6 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2005 by Gadi Cohen
*
* Largely based on php_hebrev by Zeev Suraski <zeev@php.net>
* Heavily modified by Gadi Cohen aka Kinslayer <dragon@wastelands.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "file.h"
#include "lcd.h"
#include "rbunicode.h"
#include "arabjoin.h"
#include "scroll_engine.h"
#include "bidi.h"
/* #define _HEB_BUFFER_LENGTH (MAX_PATH + LCD_WIDTH/2 + 3 + 2 + 2) * 2 */
#define _HEB_BLOCK_TYPE_ENG 1
#define _HEB_BLOCK_TYPE_HEB 0
#define _HEB_ORIENTATION_LTR 1
#define _HEB_ORIENTATION_RTL 0
#define ischar(c) ((c > 0x0589 && c < 0x0700) || \
(c >= 0xfb50 && c <= 0xfefc) ? 1 : 0)
#define _isblank(c) ((c==' ' || c=='\t') ? 1 : 0)
#define _isnewline(c) ((c=='\n' || c=='\r') ? 1 : 0)
#define XOR(a,b) ((a||b) && !(a&&b))
#ifndef BOOTLOADER
static const arab_t * arab_lookup(unsigned short uchar)
{
if (uchar >= 0x621 && uchar <= 0x63a)
return &(jointable[uchar - 0x621]);
if (uchar >= 0x640 && uchar <= 0x64a)
return &(jointable[uchar - 0x621 - 5]);
if (uchar >= 0x671 && uchar <= 0x6d5)
return &(jointable[uchar - 0x621 - 5 - 38]);
if (uchar == 0x200D) /* Support for the zero-width joiner */
return &zwj;
return 0;
}
static void arabjoin(unsigned short * stringprt, int length)
{
bool connected = false;
unsigned short * writeprt = stringprt;
const arab_t * prev = 0;
const arab_t * cur;
const arab_t * ligature = 0;
short uchar;
int i;
for (i = 0; i <= length; i++) {
cur = arab_lookup(uchar = *stringprt++);
/* Skip non-arabic chars */
if (cur == 0) {
if (prev) {
/* Finish the last char */
if (connected) {
*writeprt++ = prev->final;
connected = false;
} else
*writeprt++ = prev->isolated;
prev = 0;
*writeprt++ = uchar;
} else {
*writeprt++ = uchar;
}
continue;
}
/* nothing to do for arabic char if the previous was non-arabic */
if (prev == 0) {
prev = cur;
continue;
}
/* if it's LAM, check for LAM+ALEPH ligatures */
if (prev->isolated == 0xfedd) {
switch (cur->isolated) {
case 0xfe8d:
ligature = &(lamaleph[0]);
break;
case 0xfe87:
ligature = &(lamaleph[1]);
break;
case 0xfe83:
ligature = &(lamaleph[2]);
break;
case 0xfe81:
ligature = &(lamaleph[3]);
}
}
if (ligature) { /* replace the 2 glyphs by their ligature */
prev = ligature;
ligature = 0;
} else {
if (connected) { /* previous char has something connected to it */
if (prev->medial && cur->final) /* Can we connect to it? */
*writeprt++ = prev->medial;
else {
*writeprt++ = prev->final;
connected = false;
}
} else {
if (prev->initial && cur->final) { /* Can we connect to it? */
*writeprt++ = prev->initial;
connected = true;
} else
*writeprt++ = prev->isolated;
}
prev = cur;
}
}
}
#endif /* !BOOTLOADER */
unsigned short *bidi_l2v(const unsigned char *str, int orientation)
{
static unsigned short utf16_buf[SCROLL_LINE_SIZE];
unsigned short *target, *tmp;
#ifndef BOOTLOADER
static unsigned short bidi_buf[SCROLL_LINE_SIZE];
unsigned short *heb_str; /* *broken_str */
int block_start, block_end, block_type, block_length, i;
int length = utf8length(str);
length=length>=SCROLL_LINE_SIZE?SCROLL_LINE_SIZE-1:length;
#endif
/*
long max_chars=0;
int begin, end, char_count, orig_begin;
tmp = str;
*/
target = tmp = utf16_buf;
while (*str && target < &utf16_buf[SCROLL_LINE_SIZE-1])
str = utf8decode(str, target++);
*target = 0;
#ifdef BOOTLOADER
(void)orientation;
return utf16_buf;
#else /* !BOOTLOADER */
if (target == utf16_buf) /* empty string */
return target;
/* properly join any arabic chars */
arabjoin(utf16_buf, length);
block_start=block_end=block_length=0;
heb_str = bidi_buf;
if (orientation) {
target = heb_str;
} else {
target = heb_str + length;
*target = 0;
target--;
}
if (ischar(*tmp))
block_type = _HEB_BLOCK_TYPE_HEB;
else
block_type = _HEB_BLOCK_TYPE_ENG;
do {
while((XOR(ischar(*(tmp+1)),block_type)
|| _isblank(*(tmp+1)) || ispunct((int)*(tmp+1))
|| *(tmp+1)=='\n')
&& block_end < length-1) {
tmp++;
block_end++;
block_length++;
}
if (block_type != orientation) {
while ((_isblank(*tmp) || ispunct((int)*tmp))
&& *tmp!='/' && *tmp!='-' && block_end>block_start) {
tmp--;
block_end--;
}
}
for (i=block_start; i<=block_end; i++) {
*target = (block_type == orientation) ?
*(utf16_buf+i) : *(utf16_buf+block_end-i+block_start);
if (block_type!=orientation) {
switch (*target) {
case '(':
*target = ')';
break;
case ')':
*target = '(';
break;
default:
break;
}
}
target += orientation ? 1 : -1;
}
block_type = !block_type;
block_start=block_end+1;
} while(block_end<length-1);
*target = 0;
#if 0 /* Is this code really necessary? */
broken_str = utf16_buf;
begin=end=length-1;
target = broken_str;
while (1) {
char_count=0;
while ((!max_chars || char_count<max_chars) && begin>0) {
char_count++;
begin--;
if (begin<=0 || _isnewline(heb_str[begin])) {
while(begin>0 && _isnewline(heb_str[begin-1])) {
begin--;
char_count++;
}
break;
}
}
if (char_count==max_chars) { /* try to avoid breaking words */
int new_char_count = char_count;
int new_begin = begin;
while (new_char_count>0) {
if (_isblank(heb_str[new_begin]) ||
_isnewline(heb_str[new_begin])) {
break;
}
new_begin++;
new_char_count--;
}
if (new_char_count>0) {
char_count=new_char_count;
begin=new_begin;
}
}
orig_begin=begin;
/* if (_isblank(heb_str[begin])) {
heb_str[begin]='\n';
} */
/* skip leading newlines */
while (begin<=end && _isnewline(heb_str[begin])) {
begin++;
}
/* copy content */
for (i=begin; i<=end; i++) {
*target = heb_str[i];
target++;
}
for (i=orig_begin; i<=end && _isnewline(heb_str[i]); i++) {
*target = heb_str[i];
target++;
}
begin=orig_begin;
if (begin<=0) {
*target = 0;
break;
}
begin--;
end=begin;
}
return broken_str;
#endif
return heb_str;
#endif /* !BOOTLOADER */
}