---
title: 【Unite Tokyo 2019】ゼロから始めるアラビア語レンダリング
tags:  #unite #unity #unitetokyo #unite tokyo 2019  
author: [Unity Technologies Japan](https://convert.docswell.com/user/UnityJapan)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/PLE1LRLYEG.jpg?width=480
description: 2019/9/25-6に開催されたUnite Tokyo 2019の講演スライドです。 今福 文章（KLab株式会社）  こんな人におすすめ ・テキストレンダリングの仕組みや拡張について知りたいプログラマ ・フォントやレイアウトエンジンについて興味があるプログラマ ・アラビア語展開を考えているデベロッパー  受講者が得られる知見 ・アラビア語に関する知識 ・テキストレンダリングに関する知識、技術  Unityのイベント資料はこちらから： https://www.slideshare.net/UnityTechnologiesJapan/clipboards
published: September 26, 19
canonical: https://convert.docswell.com/s/UnityJapan/KE9D1K-unite-tokyo-2019-176302676
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/PLE1LRLYEG.jpg)

ゼロから始める
アラビア語レンダリング
KLab株式会社
今福 文章 (Fumiaki Imafuku)


# Page. 2

![Page Image](https://bcdn.docswell.com/page/8GEWV1V1J2.jpg)

今福 文章 (Fumiaki Imafuku)
KLab株式会社 技術統括部
UIライブラリ、スクリプトエンジン、
開発支援ツールなどを開発。
時々ゲーム開発。


# Page. 3

![Page Image](https://bcdn.docswell.com/page/W47Z4P4XJ3.jpg)

アラビア語は……


# Page. 4

![Page Image](https://bcdn.docswell.com/page/KYJ63M3PJV.jpg)

読めません！
書けません！
話せません！


# Page. 5

![Page Image](https://bcdn.docswell.com/page/5GJ5LZLP74.jpg)

経緯 – キャプテン翼 アラビア語対応


# Page. 6

![Page Image](https://bcdn.docswell.com/page/V9E2LRLZJR.jpg)

文字が表示できるなんて
当たり前じゃないか


# Page. 7

![Page Image](https://bcdn.docswell.com/page/5D7YQDQNJM.jpg)

文字が表示できるなんて
当たり前じゃないか


# Page. 8

![Page Image](https://bcdn.docswell.com/page/2VENK6KVE8.jpg)

レイアウトエンジンが
対応していない文字は
正しく表示できない


# Page. 9

![Page Image](https://bcdn.docswell.com/page/GY79VLVVE3.jpg)

正
誤


# Page. 10

![Page Image](https://bcdn.docswell.com/page/5G78PXP47D.jpg)

お品書き
—
アラビア語について
—
アラビア語を表示する際の課題
—
アラビア語を表示する方法


# Page. 11

![Page Image](https://bcdn.docswell.com/page/ML7L181V7R.jpg)

アラビア語について


# Page. 12

![Page Image](https://bcdn.docswell.com/page/54EMW6WN7W.jpg)

2.3
27
3


# Page. 13

![Page Image](https://bcdn.docswell.com/page/ZPERYPY479.jpg)

2.3
27
3
使用人口(億人)
公用語としている国の数
使用されている
国や地域の多さ
引用：Wikipediaのアラビア語の頁より


# Page. 14

![Page Image](https://bcdn.docswell.com/page/NP7XG3GZJX.jpg)

アラビア語
— 西アジア、北アフリカの国や地域で
多く話されている
— 国連の公用語にもなっている


# Page. 15

![Page Image](https://bcdn.docswell.com/page/637K4Y4VJD.jpg)

アラビア文字
— 基本的に右から左への横書き
— アラビア語で使うのは、28文字
— 筆記体のように文字を繋げる
— 文字を繋げる際に形状が変化する
— アラビア語以外でも使われる


# Page. 16

![Page Image](https://bcdn.docswell.com/page/4LJ3L9DQE5.jpg)

アラビア語を表示する際の課題


# Page. 17

![Page Image](https://bcdn.docswell.com/page/V8JDNGPWEG.jpg)

課題 1


# Page. 18

![Page Image](https://bcdn.docswell.com/page/4VEP53NXE8.jpg)

右から左


# Page. 19

![Page Image](https://bcdn.docswell.com/page/L27VP4K37Q.jpg)

基本的に
右から左


# Page. 20

![Page Image](https://bcdn.docswell.com/page/Z5JGP1QYJL.jpg)

例外あります


# Page. 21

![Page Image](https://bcdn.docswell.com/page/V47Q2DK6EP.jpg)

右から左と
左から右が混在


# Page. 22

![Page Image](https://bcdn.docswell.com/page/DKE4LZPME1.jpg)

アラビア文字は、双方向テキスト
— アラビア文字、英数字などが一つのテキスト内に混在した場合、
それぞれを適切な方向にレンダリングしなければならない


# Page. 23

![Page Image](https://bcdn.docswell.com/page/PL71LR9Y7G.jpg)



# Page. 24

![Page Image](https://bcdn.docswell.com/page/8G7WV1Q1E2.jpg)



# Page. 25

![Page Image](https://bcdn.docswell.com/page/W4JZ4PWXE3.jpg)

アラビア文字は、双方向テキスト
— アラビア文字、英数字などが一つのテキスト内に混在した場合、
それぞれを適切な方向にレンダリングしなければならない
右から左
左から右
アラビア文字
アラビア文字以外の文字
記号
アラビア文字の数字
数字


# Page. 26

![Page Image](https://bcdn.docswell.com/page/KYE63MGPEV.jpg)

アラビア文字は、双方向テキスト
— アラビア文字、英数字などが一つのテキスト内に混在した場合、
それぞれを適切な方向にレンダリングしなければならない
右から左
左から右
アラビア文字
アラビア文字以外の文字
記号
アラビア文字の数字
数字


# Page. 27

![Page Image](https://bcdn.docswell.com/page/5GE5LZVPJ4.jpg)

アラビア文字の数字
0
1
2
3
4
5
6
7
8
9
٠
١
٢
٣
٤
٥
٦
٧
٨
٩
2019/9/26
٢٠١٩/٩/٢٦


# Page. 28

![Page Image](https://bcdn.docswell.com/page/V972LR3ZER.jpg)

()


# Page. 29

![Page Image](https://bcdn.docswell.com/page/5DJYQD1NEM.jpg)

双方向テキストでの括弧
— 左括弧 ‘（’ 、右括弧 ‘)’ の文字コードは、向きに関わらず同じ
— 右から左にレイアウトする場合、表示を切り替える


# Page. 30

![Page Image](https://bcdn.docswell.com/page/2V7NK62V78.jpg)

課題 2


# Page. 31

![Page Image](https://bcdn.docswell.com/page/GYJ9VL1V73.jpg)

文字の変形


# Page. 32

![Page Image](https://bcdn.docswell.com/page/5GJ8PX44JD.jpg)

変形ルール
— 単語内での記述位置
— 特定文字の組み合わせ


# Page. 33

![Page Image](https://bcdn.docswell.com/page/MLJL186VJR.jpg)

単語内での記述位置
— 単独
— 先頭
— 中間
— 末尾
末尾
中間
先頭
単独
‫ﻞ‬
‫ﻠ‬
‫ﻟ‬
‫ﻝ‬


# Page. 34

![Page Image](https://bcdn.docswell.com/page/547MW65NJW.jpg)

単語内での記述位置(特殊)
— 単独
— 先頭
— 中間
— 末尾
末尾
中間
先頭
単独
‫ﺎ‬
-
-
‫ﺍ‬


# Page. 35

![Page Image](https://bcdn.docswell.com/page/ZP7RYP34J9.jpg)

単語内での記述位置(特殊)
— 先頭に来た場合、単独を使用
— 単語の中間に来た場合、末尾を使用
— 次に続く文字は、繋げられなくなるため、先頭を使用
末尾
中間
先頭
単独
‫ﺎ‬
-
-
‫ﺍ‬


# Page. 36

![Page Image](https://bcdn.docswell.com/page/NPJXG34ZEX.jpg)

単語内での記述位置(超特殊)
— ハムザ
— 声門破裂音
— 文字として記述する場合、単独のみ
末尾
中間
先頭
単独
-
-
-
‫ﺀ‬


# Page. 37

![Page Image](https://bcdn.docswell.com/page/63JK4YZVED.jpg)

特定文字の組み合わせ
—
‘‫’ﻝ‬と‘‫’ﺍ‬が連続した場合、
‘‫’ﻻ‬という文字になる


# Page. 38

![Page Image](https://bcdn.docswell.com/page/4LE3L9XQ75.jpg)

課題 3


# Page. 39

![Page Image](https://bcdn.docswell.com/page/V8EDNG2W7G.jpg)

シャクル


# Page. 40

![Page Image](https://bcdn.docswell.com/page/4V7P531X78.jpg)

発音記号


# Page. 41

![Page Image](https://bcdn.docswell.com/page/L2JVP4Y3JQ.jpg)

アラビア文字は
子音のみで表記


# Page. 42

![Page Image](https://bcdn.docswell.com/page/Z5EGP19YEL.jpg)

Tsubasa


# Page. 43

![Page Image](https://bcdn.docswell.com/page/V4JQ2DW67P.jpg)

Tsubasa
TBS


# Page. 44

![Page Image](https://bcdn.docswell.com/page/DK74LZKM71.jpg)

読み方が
わからない


# Page. 45

![Page Image](https://bcdn.docswell.com/page/PLJ1LRXYJG.jpg)

シャクルの扱い
— 基本的には、アラビア文字に慣れていない初心者向け
–
子供向けの書籍
–
教科書
–
教典
— 原則、省略不可なものもある
–
ハムザ など


# Page. 46

![Page Image](https://bcdn.docswell.com/page/8GJWV1N172.jpg)

シャクルの付け方
— 文字の上下に付く
— 複数個付くパターンもある
— 文字ごとにつける位置が決まっている


# Page. 47

![Page Image](https://bcdn.docswell.com/page/W4EZ4P3X73.jpg)

アラビア語を表示する方法


# Page. 48

![Page Image](https://bcdn.docswell.com/page/KY763MXP7V.jpg)

GPOS/GSUB


# Page. 49

![Page Image](https://bcdn.docswell.com/page/5G75LZXPE4.jpg)

取得できない


# Page. 50

![Page Image](https://bcdn.docswell.com/page/V9J2LRXZ7R.jpg)

GPOS - Glyph Positioning
— 文字の位置調整に使用されるデータ
— シャクルの位置を決める際に必要


# Page. 51

![Page Image](https://bcdn.docswell.com/page/5DEYQD8N7M.jpg)

GSUB - Glyph Substitution
— 文字の置換に使用されるデータ
— 文字の変形に必要


# Page. 52

![Page Image](https://bcdn.docswell.com/page/2VJNK6QVJ8.jpg)

普段使ってる文字は、
よしなに処理されて表示される


# Page. 53

![Page Image](https://bcdn.docswell.com/page/GYE9VLDVJ3.jpg)

どうするのか？


# Page. 54

![Page Image](https://bcdn.docswell.com/page/5GE8PX34ED.jpg)

Presentation Forms


# Page. 55

![Page Image](https://bcdn.docswell.com/page/MLEL18VVER.jpg)

Presentation Forms
— 変形済みのアラビア文字が定義されている
— シャクル付きの文字も定義されている
–
全てのシャクルには対応していない


# Page. 56

![Page Image](https://bcdn.docswell.com/page/54JMW6GNEW.jpg)

先に結論から


# Page. 57

![Page Image](https://bcdn.docswell.com/page/ZPJRYPZ4E9.jpg)

文字列をよしなに処理して、
鏡に写したようにレイアウトする


# Page. 58

![Page Image](https://bcdn.docswell.com/page/NPEXG39Z7X.jpg)

アラビア語を表示するまでの手順


# Page. 59

![Page Image](https://bcdn.docswell.com/page/63EK4YPV7D.jpg)

手順
—
テキストデータの処理
—
頂点データの処理


# Page. 60

![Page Image](https://bcdn.docswell.com/page/4L73L94QJ5.jpg)

テキストデータの処理


# Page. 61

![Page Image](https://bcdn.docswell.com/page/V87DNGQWJG.jpg)

テキストデータの処理手順
— 文字を種類ごとに分類する
— 分類した文字の向きを判断する
— 文字を並び替える
— 文字コードを表示用に変換する


# Page. 62

![Page Image](https://bcdn.docswell.com/page/4VJP53LXJ8.jpg)

文字を種類ごとに分類


# Page. 63

![Page Image](https://bcdn.docswell.com/page/L2EVP4Q3EQ.jpg)

右から左
左から右
アラビア文字
アラビア文字以外の文字
記号
アラビア文字の数字
数字


# Page. 64

![Page Image](https://bcdn.docswell.com/page/Z57GP1WY7L.jpg)

右から左
左から右
アラビア文字
アラビア文字以外の文字
アラビア文字の数字
数字
どちらの向きもあり得る
括弧
記号
空白


# Page. 65

![Page Image](https://bcdn.docswell.com/page/V4EQ2D36JP.jpg)

文字の分け方
— アラビア文字
— アラビア文字の数字
— アラビア文字以外の文字
— 数字
— 空白
— 制御文字
— 左括弧
— 右括弧
— その他


# Page. 66

![Page Image](https://bcdn.docswell.com/page/DKJ4LZ1MJ1.jpg)

アラビア文字の判定方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bool IsArabicWithoutNumber(char character)
{
int code = (int) character;
// アラビア文字のブロック内にいる
if (code &gt;= 0x0600 &amp;&amp; code &lt;= 0x06FF)
{
// アラビア文字の数字は別扱い
return code &lt; 0x0660 || code &gt; 0x0669;
}
// プレゼンテーションフォームのブロック内にいる
if (code &gt;= 0xFB50 &amp;&amp; code &lt;= 0xFEFF)
{
return true;
}
}
return false;


# Page. 67

![Page Image](https://bcdn.docswell.com/page/PLE1LRGYEG.jpg)

文字や数字の判定
— char.IsLetter
— char.IsNumber
— char.IsWhiteSpace
— char.IsControl


# Page. 68

![Page Image](https://bcdn.docswell.com/page/8GEWV1K1J2.jpg)

括弧の判定
— 括弧として対応するものを定義して、判定する
— 一方の括弧と対になる括弧の組合せも定義しておく
— 不等号も扱う必要がある


# Page. 69

![Page Image](https://bcdn.docswell.com/page/W47Z4PZXJ3.jpg)

文字の分類方法
— 文字列内の文字を１文字ずつ判定
— 同種の文字が続く範囲をトークン化


# Page. 70

![Page Image](https://bcdn.docswell.com/page/KYJ63MZPJV.jpg)

トークンの定義
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Token
{
// 文字列内での開始インデックス
public readonly int startIndex;
// 長さ
public readonly int length;
// 種類
public readonly TokenType type;
}
// 右から左にレイアウトするものかどうか
public bool isRightToLeft;


# Page. 71

![Page Image](https://bcdn.docswell.com/page/5GJ5LZWP74.jpg)

例1
(
‫ا‬
‫ل‬
‫ع‬
‫ر‬
)
a
b
c


# Page. 72

![Page Image](https://bcdn.docswell.com/page/4LE3L94675.jpg)

例1
(
左括弧
‫ا‬
‫ل‬
‫ع‬
アラビア文字
‫ر‬
)
右括弧
a
空白
b
c
アラビア文字以外の文字


# Page. 73

![Page Image](https://bcdn.docswell.com/page/V8EDNGQM7G.jpg)

分類した文字の向きを判断する


# Page. 74

![Page Image](https://bcdn.docswell.com/page/4V7P53LQ78.jpg)

右から左
左から右
アラビア文字
アラビア文字以外の文字
アラビア文字の数字
数字
どちらの向きもあり得る
括弧
記号
空白


# Page. 75

![Page Image](https://bcdn.docswell.com/page/L2JVP4QPJQ.jpg)

例1
(
左括弧
‫ا‬
‫ل‬
‫ع‬
アラビア文字
‫ر‬
)
右括弧
a
空白
b
c
アラビア文字以外の文字


# Page. 76

![Page Image](https://bcdn.docswell.com/page/Z5EGP1WQEL.jpg)

例1
(
‫ا‬
‫ل‬
‫ع‬
‫ر‬
)
a
b
c
左括弧
アラビア文字
右括弧
空白
アラビア文字以外の文字
←
←
←
←
→


# Page. 77

![Page Image](https://bcdn.docswell.com/page/V4JQ2D3W7P.jpg)

右から左
左から右
アラビア文字
アラビア文字以外の文字
アラビア文字の数字
数字
どちらの向きもあり得る
アラビア語以外の文字が来ない限りは、
括弧
基本的に右から左
記号
空白


# Page. 78

![Page Image](https://bcdn.docswell.com/page/DK74LZ1171.jpg)

例2
‫ا‬
‫ل‬
‫ع‬
‫ر‬
(
a
b
c
)
アラビア文字
左括弧
アラビア文字以外
空白
アラビア
文字以外
右括弧
？
？
？
？
？
？


# Page. 79

![Page Image](https://bcdn.docswell.com/page/PLJ1LRG5JG.jpg)

例2
‫ا‬
‫ل‬
‫ع‬
‫ر‬
(
a
b
c
)
アラビア文字
左括弧
アラビア文字以外
空白
アラビア
文字以外
右括弧
？
？
？
？
？
？


# Page. 80

![Page Image](https://bcdn.docswell.com/page/8GJWV1KW72.jpg)

単純に判断できる部分を埋める
‫ا‬
‫ل‬
‫ع‬
‫ر‬
(
a
b
c
)
アラビア文字
左括弧
アラビア文字以外
空白
アラビア
文字以外
右括弧
←
？
→
？
→
？


# Page. 81

![Page Image](https://bcdn.docswell.com/page/W4EZ4PZ273.jpg)

英字の間の空白は？
‫ا‬
‫ل‬
‫ع‬
‫ر‬
(
a
b
c
)
アラビア文字
左括弧
アラビア文字以外
空白
アラビア
文字以外
右括弧
←
？
→
？
→
？
“ab c”? “c ab”?


# Page. 82

![Page Image](https://bcdn.docswell.com/page/KY763M867V.jpg)

英字の間の空白は？
‫ا‬
‫ل‬
‫ع‬
‫ر‬
(
a
b
c
)
アラビア文字
左括弧
アラビア文字以外
空白
アラビア
文字以外
右括弧
←
？
→
→
→
？
“ab c”? “c ab”?


# Page. 83

![Page Image](https://bcdn.docswell.com/page/5G75LZ95E4.jpg)

どちらの向きもあり得る文字の向き
— 前後の文字や間に含まれる文字の向きで決まる
— アラビア文字以外の文字の後で、アラビア文字以外の文字、
数字などの挟まれた範囲は、左から右
— 括弧で囲まれた範囲に、アラビア文字、アラビア文字の数字
が入っていない場合は、左から右


# Page. 84

![Page Image](https://bcdn.docswell.com/page/V9J2LR2G7R.jpg)

括弧の向きは？
‫ا‬
‫ل‬
‫ع‬
‫ر‬
(
a
b
c
)
アラビア文字
左括弧
アラビア文字以外
空白
アラビア
文字以外
右括弧
←
？
→
→
→
？


# Page. 85

![Page Image](https://bcdn.docswell.com/page/5DEYQDYG7M.jpg)

括弧の間にアラビア語はないので、左から右
‫ا‬
‫ل‬
‫ع‬
‫ر‬
(
a
b
c
)
アラビア文字
左括弧
アラビア文字以外
空白
アラビア
文字以外
右括弧
←
→
→
→
→
→


# Page. 86

![Page Image](https://bcdn.docswell.com/page/2VJNK6P8J8.jpg)

レンダリングするとこうなる
‫ا‬
‫ل‬
‫ع‬
‫ر‬
(
a
b
c
)
アラビア文字
左括弧
アラビア文字以外
空白
アラビア
文字以外
右括弧
←
→
→
→
→
→


# Page. 87

![Page Image](https://bcdn.docswell.com/page/GYE9VL3XJ3.jpg)

文字を並び替える
文字コードを表示用に変換する


# Page. 88

![Page Image](https://bcdn.docswell.com/page/5GE8PXM9ED.jpg)

という話の前に……


# Page. 89

![Page Image](https://bcdn.docswell.com/page/MLEL183MER.jpg)

右から左にレイアウトする文字を
どうやってレンダリングするか？


# Page. 90

![Page Image](https://bcdn.docswell.com/page/54JMW6D6EW.jpg)

通常のテキストは、左から右


# Page. 91

![Page Image](https://bcdn.docswell.com/page/ZPJRYP4NE9.jpg)

右から左のレンダリング結果を得る方法
— データを逆にする
— レンダリングを逆にする


# Page. 92

![Page Image](https://bcdn.docswell.com/page/NPEXG3287X.jpg)

データを逆にする


# Page. 93

![Page Image](https://bcdn.docswell.com/page/63EK4YML7D.jpg)

入力データを逆順で格納し直す
0
1
2
3
4
あ
い
う
え
お
0
1
2
3
4
お
え
う
い
あ


# Page. 94

![Page Image](https://bcdn.docswell.com/page/4L73L9Y6J5.jpg)

あれ、もしかして、解決？


# Page. 95

![Page Image](https://bcdn.docswell.com/page/V87DNG5MJG.jpg)

そうは問屋が卸さない


# Page. 96

![Page Image](https://bcdn.docswell.com/page/4VJP53GQJ8.jpg)

幅が足りずに改行が行われた場合
入力
あいうえお
逆順化
おえういあ
期待
えういあ
お
「お」が改行されてほしい


# Page. 97

![Page Image](https://bcdn.docswell.com/page/L2EVP46PEQ.jpg)

幅が足りずに改行が行われた場合
入力
あいうえお
逆順化
おえういあ
期待
現実
えういあ
お
おえうい
あ


# Page. 98

![Page Image](https://bcdn.docswell.com/page/Z57GP1NQ7L.jpg)

レンダリングを逆にする


# Page. 99

![Page Image](https://bcdn.docswell.com/page/V4EQ2DQWJP.jpg)

ポリゴンの左右を逆にする時、
どうしますか？


# Page. 100

![Page Image](https://bcdn.docswell.com/page/DKJ4LZN1J1.jpg)

Y軸を180度回転させる


# Page. 101

![Page Image](https://bcdn.docswell.com/page/PLE1LR55EG.jpg)



# Page. 102

![Page Image](https://bcdn.docswell.com/page/8GEWV15WJ2.jpg)



# Page. 103

![Page Image](https://bcdn.docswell.com/page/W47Z4PN2J3.jpg)

文字も逆で読めない


# Page. 104

![Page Image](https://bcdn.docswell.com/page/KYJ63M96JV.jpg)

文字毎に再度180度回転


# Page. 105

![Page Image](https://bcdn.docswell.com/page/5GJ5LZN574.jpg)



# Page. 106

![Page Image](https://bcdn.docswell.com/page/V9E2LR5GJR.jpg)

右から左は正しくなった


# Page. 107

![Page Image](https://bcdn.docswell.com/page/5D7YQDKGJM.jpg)

左から右の文字は
どうするのか？


# Page. 108

![Page Image](https://bcdn.docswell.com/page/2VENK6R8E8.jpg)

この状態で左から右を正しく処理するには？
— データを逆にする
— レンダリングを180度反転させた位置にずらす


# Page. 109

![Page Image](https://bcdn.docswell.com/page/GY79VLMXE3.jpg)

この状態で左から右を正しく処理するには？
— データを逆にする
— レンダリングを180度反転させた位置にずらす


# Page. 110

![Page Image](https://bcdn.docswell.com/page/5G78PXL97D.jpg)

文字を並び替える
文字コードを表示用に変換する


# Page. 111

![Page Image](https://bcdn.docswell.com/page/ML7L18LM7R.jpg)

文字を並び替えた場合の利点


# Page. 112

![Page Image](https://bcdn.docswell.com/page/54EMW6M67W.jpg)

レンダリング処理が簡単


# Page. 113

![Page Image](https://bcdn.docswell.com/page/ZPERYPVN79.jpg)

改行が行われない文字列に対しては
こちらの方が効率がいい


# Page. 114

![Page Image](https://bcdn.docswell.com/page/NP7XG3Z8JX.jpg)

文字コードを表示用に変換する


# Page. 115

![Page Image](https://bcdn.docswell.com/page/637K4Y8LJD.jpg)

変換が必要な文字
— アラビア文字
— 右から左の括弧


# Page. 116

![Page Image](https://bcdn.docswell.com/page/4LJ3L926E5.jpg)

アラビア文字の変形
— 単語内での記述位置
— 特定文字の組み合わせ


# Page. 117

![Page Image](https://bcdn.docswell.com/page/V8JDNGZMEG.jpg)

アラビア文字の変形
— 単語内での記述位置
— 特定文字の組み合わせ
(
左括弧
‫ا‬
‫ل‬
‫ع‬
アラビア文字
‫ر‬
)
右括弧
a
空白
b
c
アラビア文字以外の文字


# Page. 118

![Page Image](https://bcdn.docswell.com/page/4VEP53DQE8.jpg)

アラビア文字の変形
— 単語内での記述位置
— 特定文字の組み合わせ
(
左括弧
‫ا‬
‫ل‬
‫ع‬
アラビア文字
単語
‫ر‬
)
右括弧
a
空白
b
c
アラビア文字以外の文字


# Page. 119

![Page Image](https://bcdn.docswell.com/page/L27VP41P7Q.jpg)

変形の手順
— 入力データをプレゼンテーションフォームに変換
— 文字の並びに合わせて、適切な形状に置換


# Page. 120

![Page Image](https://bcdn.docswell.com/page/Z5JGP12QJL.jpg)

入力データをプレゼンテーションフォームに変換
— 単独系のプレゼンテーションフォームと紐付けるだけ
— ‘‫’ﻝ‬と‘‫’ﺍ‬の連続は、‘‫’ﻻ‬に変換


# Page. 121

![Page Image](https://bcdn.docswell.com/page/V47Q2DPWEP.jpg)

シャクルを外す


# Page. 122

![Page Image](https://bcdn.docswell.com/page/DKE4LZY1E1.jpg)

入力データをプレゼンテーションフォームに変換
— 単独系のプレゼンテーションフォームと紐付けるだけ
— ‘‫’ﻝ‬と‘‫’ﺍ‬の連続は、‘‫’ﻻ‬に変換
— シャクルの除外
–
GPOS がないので正しい位置に出せない
–
一部の省略できないものは、プレゼンテーションフォーム側でシャ
クル付きの文字が定義されているので、そちらを使う


# Page. 123

![Page Image](https://bcdn.docswell.com/page/PL71LR657G.jpg)

各文字の変形情報を定義
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ArabicFormSet
{
/// &lt;summary&gt;単独&lt;/summary&gt;
public readonly int isolated;
/// &lt;summary&gt;末尾&lt;/summary&gt;
public readonly int final;
/// &lt;summary&gt;先頭&lt;/summary&gt;
public readonly int initial;
/// &lt;summary&gt;中間&lt;/summary&gt;
public readonly int medial;
/// &lt;summary&gt;先頭、中間を持つかどうか&lt;/summary&gt;
public readonly bool hasInitialAndMedial;
}
/// &lt;summary&gt;尾字を持つかどうか(基本的にHamza用の例外)&lt;/summary&gt;
public readonly bool hasFinal;


# Page. 124

![Page Image](https://bcdn.docswell.com/page/8G7WV1WWE2.jpg)

各形状は規則的に定義されている
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ArabicFormSet
{
// フィールド
}
ArabicFormSet(ArabicLetter.PresentationForms isolated, bool hasInitialAndMedial)
{
this.isolated = (int) isolated;
// Unicode上で末尾は単独から1つ、先頭は2つ、中間は3つズレた位置に格納されている
// 先頭、中間を持たない文字の場合は、中抜けになるわけではなく、次の文字が詰めて配置されている
final = this.isolated + 1;
initial = hasInitialAndMedial ? this.isolated + 2 : 0xFFFF;
medial = hasInitialAndMedial ? this.isolated + 3 : 0xFFFF;
this.hasInitialAndMedial = hasInitialAndMedial;
hasFinal = true;
}


# Page. 125

![Page Image](https://bcdn.docswell.com/page/W4JZ4P52E3.jpg)

文字の並びに合わせて、適切な形状に置換
— 末尾かどうか
— 先頭かどうか
— 中間かどうか


# Page. 126

![Page Image](https://bcdn.docswell.com/page/KYE63MV6EV.jpg)

単語の先頭、末尾以外の位置でも、
先頭、末尾の形状になる文字もある


# Page. 127

![Page Image](https://bcdn.docswell.com/page/5GE5LZD5J4.jpg)



# Page. 128

![Page Image](https://bcdn.docswell.com/page/V972LR8GER.jpg)

直前が末尾なので、先頭
先頭の形状がないので、単独
中間の形状がないので、末尾


# Page. 129

![Page Image](https://bcdn.docswell.com/page/5DJYQD6GEM.jpg)

末尾かどうか
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bool IsFinal(ArabicFormSet prev, ArabicFormSet current, ArabicFormSet next)
{
// 変形できない
if (!current.hasFinal)
{
return false;
}
// 頭字、中字からの続きではない
if (prev == null || !prev.hasInitialAndMedial)
{
return false;
}
// 自身が中間の形状を持っていて、次の文字に末尾の形状がある
if (current.hasInitialAndMedial &amp;&amp; next != null &amp;&amp; next.hasFinal)
{
return false;
}
}
return true;


# Page. 130

![Page Image](https://bcdn.docswell.com/page/2V7NK6Z878.jpg)

先頭かどうか
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bool IsInitial(ArabicFormSet prev, ArabicFormSet current, ArabicFormSet next)
{
// そもそも変形できない
if (!current.hasInitialAndMedial)
{
return false;
}
// 先頭、中間から続く
if (prev != null &amp;&amp; prev.hasInitialAndMedial)
{
return false;
}
// 中間、末尾に続かない
if (next == null || (!next.hasInitialAndMedial &amp;&amp; !next.hasFinal))
{
return false;
}
}
return true;


# Page. 131

![Page Image](https://bcdn.docswell.com/page/GYJ9VLZX73.jpg)

中間かどうか
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bool IsMedial(ArabicFormSet prev, ArabicFormSet current, ArabicFormSet next)
{
// そもそも変形できない
if (!current.hasInitialAndMedial)
{
return false;
}
// 先頭、中間から続かない
if (prev == null || !prev.hasInitialAndMedial)
{
return false;
}
// 末尾に続かない
if (next == null || !next.hasFinal)
{
return false;
}
}
return true;


# Page. 132

![Page Image](https://bcdn.docswell.com/page/5GJ8PX19JD.jpg)

右から左の括弧の変換
— 括弧の開きと閉じは、アラビア語でも文字コードが同じ
(
左括弧
‫ا‬
‫ل‬
‫ع‬
アラビア文字
‫ر‬
)
右括弧
a
空白
b
c
アラビア文字以外の文字


# Page. 133

![Page Image](https://bcdn.docswell.com/page/MLJL18QMJR.jpg)



# Page. 134

![Page Image](https://bcdn.docswell.com/page/547MW616JW.jpg)

‫(‬
‫ا‬
‫ﻟ‬
‫ﻌ‬
‫ﺮ‬
‫)‬
‫)‬
‫ر‬
‫ع‬
‫ل‬
‫ا‬
‫(‬


# Page. 135

![Page Image](https://bcdn.docswell.com/page/ZP7RYPLNJ9.jpg)

‫)‬
‫ا‬
‫ﻟ‬
‫ﻌ‬
‫ﺮ‬
‫(‬
‫)‬
‫ر‬
‫ع‬
‫ل‬
‫ا‬
‫(‬


# Page. 136

![Page Image](https://bcdn.docswell.com/page/NPJXG3W8EX.jpg)

これでテキスト部分の処理は完了です


# Page. 137

![Page Image](https://bcdn.docswell.com/page/63JK4YVLED.jpg)

‫‪c‬‬
‫‪b‬‬
‫‪a‬‬
‫)‬
‫ر‬
‫ع‬
‫ل‬
‫ا‬
‫(‬


# Page. 138

![Page Image](https://bcdn.docswell.com/page/4LE3L9M675.jpg)

‫‪c‬‬
‫‪b‬‬
‫‪a‬‬
‫(‬
‫ﺮ‬
‫ﻌ‬
‫ﻟ‬
‫ا‬
‫)‬


# Page. 139

![Page Image](https://bcdn.docswell.com/page/V8EDNG6M7G.jpg)

いざ、表示


# Page. 140

![Page Image](https://bcdn.docswell.com/page/4V7P53YQ78.jpg)



# Page. 141

![Page Image](https://bcdn.docswell.com/page/L2JVP4GPJQ.jpg)

……あれ？


# Page. 142

![Page Image](https://bcdn.docswell.com/page/Z5EGP18QEL.jpg)

理想
現実


# Page. 143

![Page Image](https://bcdn.docswell.com/page/V4JQ2D9W7P.jpg)

レンダリングは
左から右のまま


# Page. 144

![Page Image](https://bcdn.docswell.com/page/DK74LZX171.jpg)

‫‪c‬‬
‫‪b‬‬
‫‪a‬‬
‫(‬
‫ﺮ‬
‫ﻌ‬
‫ﻟ‬
‫ا‬
‫)‬


# Page. 145

![Page Image](https://bcdn.docswell.com/page/PLJ1LRN5JG.jpg)

頂点データの処理


# Page. 146

![Page Image](https://bcdn.docswell.com/page/8GJWV14W72.jpg)

理想
現実


# Page. 147

![Page Image](https://bcdn.docswell.com/page/W4EZ4P2273.jpg)

頂点データの処理
— 右から左 : 左右の位置を文字単位で反転
— 左から右 : 左右の位置をグループごと移動して反転


# Page. 148

![Page Image](https://bcdn.docswell.com/page/KY7634667V.jpg)



# Page. 149

![Page Image](https://bcdn.docswell.com/page/5G75LQ65E4.jpg)

頂点データの処理手順
— 頂点データの生成
— 文字ごとの頂点データの設定


# Page. 150

![Page Image](https://bcdn.docswell.com/page/V9J2LPMG7R.jpg)

Text コンポーネントを拡張したクラスを用意
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ArabicText : UnityEngine.UI.Text
{
protected override void OnPopulateMesh(VertexHelper vertexHelper)
{
// ここに頂点データの処理を記述する
}
}


# Page. 151

![Page Image](https://bcdn.docswell.com/page/5DEYQ5XG7M.jpg)

頂点データの生成


# Page. 152

![Page Image](https://bcdn.docswell.com/page/2VJNKN18J8.jpg)

TextGenerator で頂点データを生成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ArabicText : UnityEngine.UI.Text
{
protected override void OnPopulateMesh(VertexHelper vertexHelper)
{
Vector2 extents = rectTransform.rect.size;
var settings = GetGenerationSettings(extents);
// workerText には、アラビア語用に処理されたテキストが入っている
cachedTextGenerator.PopulateWithErrors(workerText, settings, gameObject);
}
}
// 頂点、行の情報を取得
IList&lt;UIVertex&gt; verts = cachedTextGenerator.verts;
IList&lt;UILineInfo&gt; lines = cachedTextGenerator.lines;


# Page. 153

![Page Image](https://bcdn.docswell.com/page/GYE9VR5XJ3.jpg)

TextGenerator
— テキストの頂点情報や文字、行の情報を生成してキャッシュ
することができる
— 生成した際にフォントテクスチャに必要なフォントが書き込
まれる
— GPOSのデータに対応したデータが作られる(全てではない)
— スペース区切りでの改行位置調整


# Page. 154

![Page Image](https://bcdn.docswell.com/page/5GE8PWY9ED.jpg)

文字ごとの頂点データの設定


# Page. 155

![Page Image](https://bcdn.docswell.com/page/MLEL1N4MER.jpg)

使用するデータ
— 頂点情報
— 行情報
— 文字を分類した際のトークン
–
処理する文字の向きで処理方法が変わるため


# Page. 156

![Page Image](https://bcdn.docswell.com/page/54JMWXK6EW.jpg)

トークン毎に処理


# Page. 157

![Page Image](https://bcdn.docswell.com/page/ZPJRYNXNE9.jpg)

右から左の文字の処理方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
UIVertex[] buffer = new UIVertex[4];
for (int i = 0; i &lt; batchVertexCount; ++i)
{
int bufferIndex = i &amp; 3;
// 頂点の順番は、左下から反時計回り
int indexOffset = (i &amp; 1) == 0 ? 1 : -1;
buffer[bufferIndex] = verts[batchStartVertexIndex + i];
// 文字の見た目も反転してしまうので、見た目は本来の通りになるように、左右を反転させる
buffer[bufferIndex].position.x = verts[batchStartVertexIndex + i + indexOffset].position.x;
buffer[bufferIndex].position.x += (center - buffer[bufferIndex].position.x) * 2;
// unitsPerPixel, roundingOffset の処理は、Text コンポーネントと同じなので割愛
}
if (bufferIndex == 3)
{
vertexHelper.AddUIVertexQuad(buffer);
}


# Page. 158

![Page Image](https://bcdn.docswell.com/page/NPEXGN687X.jpg)

左から右の文字の処理方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
UIVertex[] buffer = new UIVertex[4];
float blockMin = verts[batchStartVertexIndex].position.x;
float blockMax = verts[batchStartVertexIndex + (batchCharacterCount - 1) * 4 + 1].position.x;
float blockCenter = blockMin + (blockMax - blockMin) / 2;
float xOffset = (center - blockCenter) * 2;
for (int i = 0; i &lt; batchVertexCount; ++i)
{
int bufferIndex = i &amp; 3;
buffer[bufferIndex] = verts[batchStartVertexIndex + i];
buffer[bufferIndex].position.x += xOffset;
// unitsPerPixel, roundingOffset の処理は、Text コンポーネントと同じなので割愛
}
if (bufferIndex == 3)
{
vertexHelper.AddUIVertexQuad(buffer);
}


# Page. 159

![Page Image](https://bcdn.docswell.com/page/63EK4N1L7D.jpg)

これで頂点部分の処理は完了です


# Page. 160

![Page Image](https://bcdn.docswell.com/page/4L73LVQ6J5.jpg)



# Page. 161

![Page Image](https://bcdn.docswell.com/page/V87DN89MJG.jpg)

リトライ


# Page. 162

![Page Image](https://bcdn.docswell.com/page/4VJP58ZQJ8.jpg)



# Page. 163

![Page Image](https://bcdn.docswell.com/page/L2EVPNWPEQ.jpg)

お、それっぽい……


# Page. 164

![Page Image](https://bcdn.docswell.com/page/Z57GPKDQ7L.jpg)

が


# Page. 165

![Page Image](https://bcdn.docswell.com/page/V4EQ2N1WJP.jpg)

ココ


# Page. 166

![Page Image](https://bcdn.docswell.com/page/DKJ4LGR1J1.jpg)

隙間が空いてる


# Page. 167

![Page Image](https://bcdn.docswell.com/page/PLE1LDQ5EG.jpg)

なぜ？


# Page. 168

![Page Image](https://bcdn.docswell.com/page/8GEWVYMWJ2.jpg)

処理が足りていない


# Page. 169

![Page Image](https://bcdn.docswell.com/page/W47Z4XQ2J3.jpg)

フォントのレイアウトデータ


# Page. 170

![Page Image](https://bcdn.docswell.com/page/KYJ634N6JV.jpg)

アセンダーライン
キャップライン
ミーンライン
ベースライン
ディセンダーライン
a
セット
セット幅


# Page. 171

![Page Image](https://bcdn.docswell.com/page/5GJ5LQ5574.jpg)

サイドベアリング
アセンダーライン
キャップライン
ミーンライン
ベースライン
ディセンダーライン
a
セット
セット幅


# Page. 172

![Page Image](https://bcdn.docswell.com/page/4LE3LVN575.jpg)

サイドベアリングは
左右均一とは限らない


# Page. 173

![Page Image](https://bcdn.docswell.com/page/V8EDN8WY7G.jpg)

左右反転するとズレる


# Page. 174

![Page Image](https://bcdn.docswell.com/page/4V7P589278.jpg)

CharacterInfo
— 文字のレンダリング情報が取得できる


# Page. 175

![Page Image](https://bcdn.docswell.com/page/L2JVPNMXJQ.jpg)

サイドベアリングの取得方法
— 左側
–
CharacterInfo.bearing
— 右側
–
取得できません


# Page. 176

![Page Image](https://bcdn.docswell.com/page/Z5EGPKGREL.jpg)

取得できる値から逆算


# Page. 177

![Page Image](https://bcdn.docswell.com/page/V4JQ2NXY7P.jpg)

CharacterInfo.bearing
a
CharacterInfo.glyphWidth ?
CharacterInfo.advance ?


# Page. 178

![Page Image](https://bcdn.docswell.com/page/DK74LG6Z71.jpg)

全ての文字、入力パターンに対して、
正しいわけではなさそう


# Page. 179

![Page Image](https://bcdn.docswell.com/page/PLJ1LDVDJG.jpg)

空白など一部を除き、おおよそ
それらしき値が取れる


# Page. 180

![Page Image](https://bcdn.docswell.com/page/8GJWVYL872.jpg)

サイドベアリングの取得方法
— 左側
–
CharacterInfo.bearing
— 右側
–
advance - glyphWidth - bearing (仮定)


# Page. 181

![Page Image](https://bcdn.docswell.com/page/W4EZ4XM973.jpg)

文字の処理に当てはめる


# Page. 182

![Page Image](https://bcdn.docswell.com/page/KY7634ND7V.jpg)

CharacterInfo の取得
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
CharacterInfo info;
font.GetCharacterInfo(character, out info, fontSize, fontStyle);


# Page. 183

![Page Image](https://bcdn.docswell.com/page/5G75LQ58E4.jpg)

右から左の文字の処理方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// すでに処理済みのテキストの長さ
int alreadyPutLength;
for (int i = 0; i &lt; batchVertexCount; ++i)
{
// インデックスの計算
int bearingOffset = 0;
if (bufferIndex == 0)
{
int characterIndex = i / 4;
char character = workerText[alreadyPutLength + characterIndex];
CharacterInfo info;
font.GetCharacterInfo(character, out info, fontSize, fontStyle);
int leftBearing = info.bearing;
int rightBearing = info.advance - info.glyphWidth - info.bearing;
bearingOffset = leftBearing - rightBearing;
}
// 頂点の処理
buffer[bufferIndex].position.x += bearingOffset;
}
// unitsPerPixel, roundingOffset の処理、VertexHelper に頂点を追加


# Page. 184

![Page Image](https://bcdn.docswell.com/page/V9J2LPNV7R.jpg)

左から右の文字の処理方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// すでに処理済みのテキストの長さ
int alreadyPutLength;
float blockMin = verts[batchStartVertexIndex].position.x – info.bearing;
float blockMax = verts[batchStartVertexIndex + (batchCharacterCount - 1) * 4 + 1].position.x;
// 最初の文字の左側のサイドベアリング分を補正
char firstCharacter = workerText[alreadyPutLength];
CharacterInfo info;
font.GetCharacterInfo(firstCharacter, out info, fontSize, fontStyle);
blockMin -= info.bearing;
// 最後の文字の右側のサイドベアリング分を補正
char lastCharacter = workerText[alrealdyPutLength + batchCharacterCount - 1];
font.GetCharacterInfo(lastCharacter, out info, fontSize, fontStyle);
blockMax += (info.advance – info.glyphWidth – info.bearing);
float blockCenter = blockMin + (blockMax - blockMin) / 2;
float xOffset = (center - blockCenter) * 2;
for (int i = 0; i &lt; batchVertexCount; ++i)
{
// 頂点の処理
}


# Page. 185

![Page Image](https://bcdn.docswell.com/page/5DEYQ5PQ7M.jpg)

もう一度、リトライ


# Page. 186

![Page Image](https://bcdn.docswell.com/page/2VJNKN52J8.jpg)



# Page. 187

![Page Image](https://bcdn.docswell.com/page/GYE9VRYDJ3.jpg)

隙間消滅


# Page. 188

![Page Image](https://bcdn.docswell.com/page/5GE8PW65ED.jpg)

検証


# Page. 189

![Page Image](https://bcdn.docswell.com/page/MLEL1N93ER.jpg)

文字を入れ替えて
レンダリングはそのまま
表示したものと比較


# Page. 190

![Page Image](https://bcdn.docswell.com/page/54JMWXZMEW.jpg)



# Page. 191

![Page Image](https://bcdn.docswell.com/page/ZPJRYN6LE9.jpg)

ピッタリ


# Page. 192

![Page Image](https://bcdn.docswell.com/page/NPEXGND67X.jpg)

注意点


# Page. 193

![Page Image](https://bcdn.docswell.com/page/63EK4NXG7D.jpg)

反転する際の基準点を RectTranform の
中心から求めるとアライメントが逆転する


# Page. 194

![Page Image](https://bcdn.docswell.com/page/4L73LV55J5.jpg)

ともあれ……


# Page. 195

![Page Image](https://bcdn.docswell.com/page/V87DN8DYJG.jpg)

最低限正しく表示できる


# Page. 196

![Page Image](https://bcdn.docswell.com/page/4VJP58X2J8.jpg)

感想


# Page. 197

![Page Image](https://bcdn.docswell.com/page/L2EVPNLXEQ.jpg)

超絶しんどいです！！


# Page. 198

![Page Image](https://bcdn.docswell.com/page/Z57GPKXR7L.jpg)

ご清聴ありがとうございました！


