Anas Altair
Committed by David PHAM-VAN

Implement Arabic writing support

@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 DART_SRC=$(shell find . -name '*.dart') 15 DART_SRC=$(shell find . -name '*.dart')
16 CLNG_SRC=$(shell find printing/ios -name '*.java' -o -name '*.m' -o -name '*.h') $(shell find printing/android -name '*.java' -o -name '*.m' -o -name '*.h') 16 CLNG_SRC=$(shell find printing/ios -name '*.java' -o -name '*.m' -o -name '*.h') $(shell find printing/android -name '*.java' -o -name '*.m' -o -name '*.h')
17 SWFT_SRC=$(shell find . -name '*.swift') 17 SWFT_SRC=$(shell find . -name '*.swift')
18 - FONTS=pdf/open-sans.ttf pdf/open-sans-bold.ttf pdf/roboto.ttf pdf/noto-sans.ttf pdf/genyomintw.ttf demo/assets/roboto1.ttf demo/assets/roboto2.ttf demo/assets/roboto3.ttf demo/assets/open-sans.ttf demo/assets/open-sans-bold.ttf 18 + FONTS=pdf/open-sans.ttf pdf/open-sans-bold.ttf pdf/roboto.ttf pdf/noto-sans.ttf pdf/genyomintw.ttf demo/assets/roboto1.ttf demo/assets/roboto2.ttf demo/assets/roboto3.ttf demo/assets/open-sans.ttf demo/assets/open-sans-bold.ttf pdf/hacen-tunisia.ttf
19 COV_PORT=9292 19 COV_PORT=9292
20 20
21 all: $(FONTS) demo/assets/logo.png demo/assets/profile.jpg format printing/example/.metadata get 21 all: $(FONTS) demo/assets/logo.png demo/assets/profile.jpg format printing/example/.metadata get
@@ -57,6 +57,9 @@ demo/assets/logo.png: @@ -57,6 +57,9 @@ demo/assets/logo.png:
57 demo/assets/profile.jpg: 57 demo/assets/profile.jpg:
58 curl -L "https://www.fakepersongenerator.com/Face/female/female20151024334209870.jpg" > $@ 58 curl -L "https://www.fakepersongenerator.com/Face/female/female20151024334209870.jpg" > $@
59 59
  60 +pdf/hacen-tunisia.ttf:
  61 + curl -L "https://arbfonts.com/font_files/hacen/Hacen Tunisia.ttf" > $@
  62 +
60 format: format-dart format-clang format-swift 63 format: format-dart format-clang format-swift
61 64
62 format-dart: $(DART_SRC) 65 format-dart: $(DART_SRC)
@@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
5 - Allow MultiPage to relayout individual pages with support for flex 5 - Allow MultiPage to relayout individual pages with support for flex
6 - Implement BoxShadow for rect and circle BoxDecorations 6 - Implement BoxShadow for rect and circle BoxDecorations
7 - Implement TextStyle.letterSpacing 7 - Implement TextStyle.letterSpacing
  8 +- Implement Arabic writing support [Anas Altair]
8 9
9 ## 1.8.1 10 ## 1.8.1
10 11
@@ -33,6 +33,7 @@ import 'io/interface.dart' @@ -33,6 +33,7 @@ import 'io/interface.dart'
33 if (dart.library.js) 'io/js.dart'; 33 if (dart.library.js) 'io/js.dart';
34 34
35 part 'src/annotation.dart'; 35 part 'src/annotation.dart';
  36 +part 'src/arabic.dart';
36 part 'src/array.dart'; 37 part 'src/array.dart';
37 part 'src/ascii85.dart'; 38 part 'src/ascii85.dart';
38 part 'src/border.dart'; 39 part 'src/border.dart';
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +// ignore_for_file: omit_local_variable_types
  18 +// https://github.com/agawish/Better-Arabic-Reshaper/tree/master/src/org/amr/arabic
  19 +// http://mpcabd.xyz/python-arabic-text-reshaper/
  20 +// https://github.com/Georeactor/alif-toolkit/tree/master/src
  21 +
  22 +part of pdf;
  23 +
  24 +class PdfArabic {
  25 + /// Arabic shape substitutions: char code => (isolated, final, initial, medial).
  26 + /// Arabic Substition A
  27 + static const Map<int, dynamic> _arabicSubstitionA = <int, dynamic>{
  28 +// 0x0652: <int>[0xFE7E, 0xFE7F],
  29 +
  30 + 0x0621: <int>[0xFE80], // ARABIC LETTER HAMZA
  31 + 0x0622: <int>[0xFE81, 0xFE82], // ARABIC LETTER ALEF WITH MADDA ABOVE
  32 + 0x0623: <int>[0xFE83, 0xFE84], // ARABIC LETTER ALEF WITH HAMZA ABOVE
  33 + 0x0624: <int>[0xFE85, 0xFE86], // ARABIC LETTER WAW WITH HAMZA ABOVE
  34 + 0x0625: <int>[0xFE87, 0xFE88], // ARABIC LETTER ALEF WITH HAMZA BELOW
  35 + 0x0626: <int>[
  36 + 0xFE89,
  37 + 0xFE8A,
  38 + 0xFE8B,
  39 + 0xFE8C
  40 + ], // ARABIC LETTER YEH WITH HAMZA ABOVE
  41 + 0x0627: <int>[0xFE8D, 0xFE8E], // ARABIC LETTER ALEF
  42 + 0x0628: <int>[0xFE8F, 0xFE90, 0xFE91, 0xFE92], // ARABIC LETTER BEH
  43 + 0x0629: <int>[0xFE93, 0xFE94], // ARABIC LETTER TEH MARBUTA
  44 + 0x062A: <int>[0xFE95, 0xFE96, 0xFE97, 0xFE98], // ARABIC LETTER TEH
  45 + 0x062B: <int>[0xFE99, 0xFE9A, 0xFE9B, 0xFE9C], // ARABIC LETTER THEH
  46 + 0x062C: <int>[0xFE9D, 0xFE9E, 0xFE9F, 0xFEA0], // ARABIC LETTER JEEM
  47 + 0x062D: <int>[0xFEA1, 0xFEA2, 0xFEA3, 0xFEA4], // ARABIC LETTER HAH
  48 + 0x062E: <int>[0xFEA5, 0xFEA6, 0xFEA7, 0xFEA8], // ARABIC LETTER KHAH
  49 + 0x062F: <int>[0xFEA9, 0xFEAA], // ARABIC LETTER DAL
  50 + 0x0630: <int>[0xFEAB, 0xFEAC], // ARABIC LETTER THAL
  51 + 0x0631: <int>[0xFEAD, 0xFEAE], // ARABIC LETTER REH
  52 + 0x0632: <int>[0xFEAF, 0xFEB0], // ARABIC LETTER ZAIN
  53 + 0x0633: <int>[0xFEB1, 0xFEB2, 0xFEB3, 0xFEB4], // ARABIC LETTER SEEN
  54 + 0x0634: <int>[0xFEB5, 0xFEB6, 0xFEB7, 0xFEB8], // ARABIC LETTER SHEEN
  55 + 0x0635: <int>[0xFEB9, 0xFEBA, 0xFEBB, 0xFEBC], // ARABIC LETTER SAD
  56 + 0x0636: <int>[0xFEBD, 0xFEBE, 0xFEBF, 0xFEC0], // ARABIC LETTER DAD
  57 + 0x0637: <int>[0xFEC1, 0xFEC2, 0xFEC3, 0xFEC4], // ARABIC LETTER TAH
  58 + 0x0638: <int>[0xFEC5, 0xFEC6, 0xFEC7, 0xFEC8], // ARABIC LETTER ZAH
  59 + 0x0639: <int>[0xFEC9, 0xFECA, 0xFECB, 0xFECC], // ARABIC LETTER AIN
  60 + 0x063A: <int>[0xFECD, 0xFECE, 0xFECF, 0xFED0], // ARABIC LETTER GHAIN
  61 + 0x0641: <int>[0xFED1, 0xFED2, 0xFED3, 0xFED4], // ARABIC LETTER FEH
  62 + 0x0642: <int>[0xFED5, 0xFED6, 0xFED7, 0xFED8], // ARABIC LETTER QAF
  63 + 0x0643: <int>[0xFED9, 0xFEDA, 0xFEDB, 0xFEDC], // ARABIC LETTER KAF
  64 + 0x0644: <int>[0xFEDD, 0xFEDE, 0xFEDF, 0xFEE0], // ARABIC LETTER LAM
  65 + 0x0645: <int>[0xFEE1, 0xFEE2, 0xFEE3, 0xFEE4], // ARABIC LETTER MEEM
  66 + 0x0646: <int>[0xFEE5, 0xFEE6, 0xFEE7, 0xFEE8], // ARABIC LETTER NOON
  67 + 0x0647: <int>[0xFEE9, 0xFEEA, 0xFEEB, 0xFEEC], // ARABIC LETTER HEH
  68 + 0x0648: <int>[0xFEED, 0xFEEE], // ARABIC LETTER WAW
  69 + 0x0649: <int>[0xFEEF, 0xFEF0, 64488, 64489], // ARABIC LETTER ALEF MAKSURA
  70 + 0x064A: <int>[0xFEF1, 0xFEF2, 0xFEF3, 0xFEF4], // ARABIC LETTER YEH
  71 + 0x0671: <int>[0xFB50, 0xFB51], // ARABIC LETTER ALEF WASLA
  72 + 0x0677: <int>[0xFBDD], // ARABIC LETTER U WITH HAMZA ABOVE
  73 + 0x0679: <int>[0xFB66, 0xFB67, 0xFB68, 0xFB69], // ARABIC LETTER TTEH
  74 + 0x067A: <int>[0xFB5E, 0xFB5F, 0xFB60, 0xFB61], // ARABIC LETTER TTEHEH
  75 + 0x067B: <int>[0xFB52, 0xFB53, 0xFB54, 0xFB55], // ARABIC LETTER BEEH
  76 + 0x067E: <int>[0xFB56, 0xFB57, 0xFB58, 0xFB59], // ARABIC LETTER PEH
  77 + 0x067F: <int>[0xFB62, 0xFB63, 0xFB64, 0xFB65], // ARABIC LETTER TEHEH
  78 + 0x0680: <int>[0xFB5A, 0xFB5B, 0xFB5C, 0xFB5D], // ARABIC LETTER BEHEH
  79 + 0x0683: <int>[0xFB76, 0xFB77, 0xFB78, 0xFB79], // ARABIC LETTER NYEH
  80 + 0x0684: <int>[0xFB72, 0xFB73, 0xFB74, 0xFB75], // ARABIC LETTER DYEH
  81 + 0x0686: <int>[0xFB7A, 0xFB7B, 0xFB7C, 0xFB7D], // ARABIC LETTER TCHEH
  82 + 0x0687: <int>[0xFB7E, 0xFB7F, 0xFB80, 0xFB81], // ARABIC LETTER TCHEHEH
  83 + 0x0688: <int>[0xFB88, 0xFB89], // ARABIC LETTER DDAL
  84 + 0x068C: <int>[0xFB84, 0xFB85], // ARABIC LETTER DAHAL
  85 + 0x068D: <int>[0xFB82, 0xFB83], // ARABIC LETTER DDAHAL
  86 + 0x068E: <int>[0xFB86, 0xFB87], // ARABIC LETTER DUL
  87 + 0x0691: <int>[0xFB8C, 0xFB8D], // ARABIC LETTER RREH
  88 + 0x0698: <int>[0xFB8A, 0xFB8B], // ARABIC LETTER JEH
  89 + 0x06A4: <int>[0xFB6A, 0xFB6B, 0xFB6C, 0xFB6D], // ARABIC LETTER VEH
  90 + 0x06A6: <int>[0xFB6E, 0xFB6F, 0xFB70, 0xFB71], // ARABIC LETTER PEHEH
  91 + 0x06A9: <int>[0xFB8E, 0xFB8F, 0xFB90, 0xFB91], // ARABIC LETTER KEHEH
  92 + 0x06AD: <int>[0xFBD3, 0xFBD4, 0xFBD5, 0xFBD6], // ARABIC LETTER NG
  93 + 0x06AF: <int>[0xFB92, 0xFB93, 0xFB94, 0xFB95], // ARABIC LETTER GAF
  94 + 0x06B1: <int>[0xFB9A, 0xFB9B, 0xFB9C, 0xFB9D], // ARABIC LETTER NGOEH
  95 + 0x06B3: <int>[0xFB96, 0xFB97, 0xFB98, 0xFB99], // ARABIC LETTER GUEH
  96 + 0x06BA: <int>[0xFB9E, 0xFB9F], // ARABIC LETTER NOON GHUNNA
  97 + 0x06BB: <int>[0xFBA0, 0xFBA1, 0xFBA2, 0xFBA3], // ARABIC LETTER RNOON
  98 + 0x06BE: <int>[
  99 + 0xFBAA,
  100 + 0xFBAB,
  101 + 0xFBAC,
  102 + 0xFBAD
  103 + ], // ARABIC LETTER HEH DOACHASHMEE
  104 + 0x06C0: <int>[0xFBA4, 0xFBA5], // ARABIC LETTER HEH WITH YEH ABOVE
  105 + 0x06C1: <int>[0xFBA6, 0xFBA7, 0xFBA8, 0xFBA9], // ARABIC LETTER HEH GOAL
  106 + 0x06C5: <int>[0xFBE0, 0xFBE1], // ARABIC LETTER KIRGHIZ OE
  107 + 0x06C6: <int>[0xFBD9, 0xFBDA], // ARABIC LETTER OE
  108 + 0x06C7: <int>[0xFBD7, 0xFBD8], // ARABIC LETTER U
  109 + 0x06C8: <int>[0xFBDB, 0xFBDC], // ARABIC LETTER YU
  110 + 0x06C9: <int>[0xFBE2, 0xFBE3], // ARABIC LETTER KIRGHIZ YU
  111 + 0x06CB: <int>[0xFBDE, 0xFBDF], // ARABIC LETTER VE
  112 + 0x06CC: <int>[0xFBFC, 0xFBFD, 0xFBFE, 0xFBFF], // ARABIC LETTER FARSI YEH
  113 + 0x06D0: <int>[0xFBE4, 0xFBE5, 0xFBE6, 0xFBE7], //ARABIC LETTER E
  114 + 0x06D2: <int>[0xFBAE, 0xFBAF], // ARABIC LETTER YEH BARREE
  115 + 0x06D3: <int>[0xFBB0, 0xFBB1], // ARABIC LETTER YEH BARREE WITH HAMZA ABOVE
  116 + };
  117 +
  118 + /*
  119 + var ligaturesSubstitutionA = {
  120 + 0xFBEA: []// ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF ISOLATED FORM
  121 + };
  122 + */
  123 +
  124 + static const Map<int, dynamic> _diacriticLigatures = <int, dynamic>{
  125 + 0x0651: <int, int>{
  126 + 0x064C: 0xFC5E, // Shadda + Dammatan
  127 + 0x064D: 0xFC5F, // Shadda + Kasratan
  128 + 0x064E: 0xFC60, // Shadda + Fatha
  129 + 0x064F: 0xFC61, // Shadda + Damma
  130 + 0x0650: 0xFC62, // Shadda + Kasra
  131 + },
  132 + };
  133 +
  134 + static const Map<int, dynamic> _ligatures = <int, dynamic>{
  135 + 0xFEDF: <int, int>{
  136 + 0xFE82:
  137 + 0xFEF5, // ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM
  138 + 0xFE84:
  139 + 0xFEF7, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM
  140 + 0xFE88:
  141 + 0xFEF9, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM
  142 + 0xFE8E: 0xFEFB // ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM
  143 + },
  144 + 0xFEE0: <int, int>{
  145 + 0xFE82:
  146 + 0xFEF6, // ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE FINAL FORM
  147 + 0xFE84:
  148 + 0xFEF8, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE FINAL FORM
  149 + 0xFE88:
  150 + 0xFEFA, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW FINAL FORM
  151 + 0xFE8E: 0xFEFC // ARABIC LIGATURE LAM WITH ALEF FINAL FORM
  152 + },
  153 +// 0xFE8D: <int, dynamic>{
  154 +// 0xFEDF: <int, dynamic>{
  155 +// 0xFEE0: <int, int>{0xFEEA: 0xFDF2}
  156 +// }
  157 +// }, // ALLAH
  158 + };
  159 +
  160 + static const List<int> _alfletter = <int>[1570, 1571, 1573, 1575];
  161 +
  162 + static const Map<int, int> _arabicDiacritics = <int, int>{
  163 + 1611: 1611, // Fathatan
  164 + 1612: 1612, // Dammatan
  165 + 1613: 1613, // Kasratan
  166 + 1614: 1614, // Fatha
  167 + 1615: 1615, // Damma
  168 + 1616: 1616, // Kasra
  169 + 1617: 1617,
  170 + 1618: 1618,
  171 +
  172 + 64606: 64606, // Shadda + Dammatan
  173 + 64607: 64607, // Shadda + Kasratan
  174 + 64608: 64608, // Shadda + Fatha
  175 + 64609: 64609, // Shadda + Damma
  176 + 64610: 64610, // Shadda + Kasra
  177 + // 1548: 1548,
  178 + };
  179 +
  180 + static const int _noChangeInForm = -1;
  181 + static const int _isolatedForm = 0;
  182 + static const int _finalForm = 1;
  183 + static const int _initialForm = 2;
  184 + static const int _medialForm = 3;
  185 +
  186 + static bool _isInArabicSubstitutionA(int letter) {
  187 + return _arabicSubstitionA.containsKey(letter);
  188 + }
  189 +
  190 + static bool _isArabicLetter(int letter) {
  191 + return (letter >= 0x0600 && letter <= 0x06FF) ||
  192 + (letter >= 0x0750 && letter <= 0x077F) ||
  193 + (letter >= 0x08FF && letter <= 0xFB50) ||
  194 + (letter >= 0xFDFF && letter <= 0xFEFF);
  195 + }
  196 +
  197 + static bool _isArabicEndLetter(int letter) {
  198 + return _isArabicLetter(letter) &&
  199 + _isInArabicSubstitutionA(letter) &&
  200 + _arabicSubstitionA[letter].length <= 2;
  201 + }
  202 +
  203 + static bool _isArabicAlfLetter(int letter) {
  204 + return _isArabicLetter(letter) && _alfletter.contains(letter);
  205 + }
  206 +
  207 + static bool _arabicLetterHasFinalForm(int letter) {
  208 + return _isArabicLetter(letter) &&
  209 + _isInArabicSubstitutionA(letter) &&
  210 + (_arabicSubstitionA[letter].length >= 2);
  211 + }
  212 +
  213 + static bool _arabicLetterHasMedialForm(int letter) {
  214 + return _isArabicLetter(letter) &&
  215 + _isInArabicSubstitutionA(letter) &&
  216 + _arabicSubstitionA[letter].length == 4;
  217 + }
  218 +
  219 + static bool _isArabicDiacritic(int letter) {
  220 + return _arabicDiacritics.containsKey(letter);
  221 + }
  222 +
  223 + static bool _isArabicDiacriticValue(int letter) {
  224 + return _arabicDiacritics.containsValue(letter);
  225 + }
  226 +
  227 + static List<int> _resolveLigatures(List<int> lettersq) {
  228 + final List<int> result = <int>[];
  229 + dynamic tmpLigatures = _ligatures;
  230 + dynamic tmpDiacritic = _diacriticLigatures;
  231 + final List<int> letters = lettersq.reversed.toList();
  232 +
  233 + final List<int> effectedLetters = <int>[];
  234 + final List<int> effectedDiacritics = <int>[];
  235 +
  236 + final List<int> finalDiacritics = <int>[];
  237 +
  238 + for (int i = 0; i < letters.length; i++) {
  239 + if (_isArabicDiacriticValue(letters[i])) {
  240 + effectedDiacritics.insert(0, letters[i]);
  241 + if (tmpDiacritic.containsKey(letters[i])) {
  242 + tmpDiacritic = tmpDiacritic[letters[i]];
  243 +
  244 + if (tmpDiacritic is int) {
  245 + finalDiacritics.insert(0, tmpDiacritic);
  246 + tmpDiacritic = _diacriticLigatures;
  247 + effectedDiacritics.clear();
  248 + }
  249 + } else {
  250 + tmpDiacritic = _diacriticLigatures;
  251 +
  252 + // add all Diacritics if there is no letter Ligatures.
  253 + if (effectedLetters.isEmpty) {
  254 + result.insertAll(0, finalDiacritics);
  255 + result.insertAll(0, effectedDiacritics);
  256 + finalDiacritics.clear();
  257 + effectedDiacritics.clear();
  258 + }
  259 + }
  260 + } else if (tmpLigatures.containsKey(letters[i])) {
  261 + effectedLetters.insert(0, letters[i]);
  262 + tmpLigatures = tmpLigatures[letters[i]];
  263 +
  264 + if (tmpLigatures is int) {
  265 + result.insert(0, tmpLigatures);
  266 + tmpLigatures = _ligatures;
  267 + effectedLetters.clear();
  268 + }
  269 + } else {
  270 + tmpLigatures = _ligatures;
  271 +
  272 + // add effected letters if they aren't ligature.
  273 + if (effectedLetters.isNotEmpty) {
  274 + result.insertAll(0, effectedLetters);
  275 + effectedLetters.clear();
  276 + }
  277 +
  278 + // add Diacritics after or before letter ligature.
  279 + if (effectedLetters.isEmpty && effectedDiacritics.isNotEmpty) {
  280 + result.insertAll(0, effectedDiacritics);
  281 + effectedDiacritics.clear();
  282 + }
  283 +
  284 + result.insert(0, letters[i]);
  285 + }
  286 +
  287 + // add Diacritic ligatures.
  288 + if (effectedLetters.isEmpty && finalDiacritics.isNotEmpty) {
  289 + result.insertAll(0, finalDiacritics);
  290 + finalDiacritics.clear();
  291 + }
  292 + }
  293 +
  294 + return result;
  295 + }
  296 +
  297 + static int _getCorrectForm(int currentChar, int beforeChar, int nextChar) {
  298 + if (_isInArabicSubstitutionA(currentChar) == false) {
  299 + return _noChangeInForm;
  300 + }
  301 + if (!_arabicLetterHasFinalForm(currentChar) ||
  302 + (!_isArabicLetter(beforeChar) && !_isArabicLetter(nextChar)) ||
  303 + (!_isArabicLetter(nextChar) && _isArabicEndLetter(beforeChar)) ||
  304 + (_isArabicEndLetter(currentChar) && !_isArabicLetter(beforeChar)) ||
  305 + (_isArabicEndLetter(currentChar) && _isArabicAlfLetter(beforeChar)) ||
  306 + (_isArabicEndLetter(currentChar) && _isArabicEndLetter(beforeChar))) {
  307 + return _isolatedForm;
  308 + }
  309 +
  310 + if (_arabicLetterHasMedialForm(currentChar) &&
  311 + _isArabicLetter(beforeChar) &&
  312 + !_isArabicEndLetter(beforeChar) &&
  313 + _isArabicLetter(nextChar) &&
  314 + _arabicLetterHasFinalForm(nextChar)) {
  315 + return _medialForm;
  316 + }
  317 +
  318 + if (_isArabicEndLetter(currentChar) || (!_isArabicLetter(nextChar))) {
  319 + return _finalForm;
  320 + }
  321 + return _initialForm;
  322 + }
  323 +
  324 + static Iterable<String> _parse(String text) sync* {
  325 + final List<String> words = text.split(' ');
  326 +
  327 + bool first = true;
  328 + for (String word in words) {
  329 + final List<int> newWord = <int>[];
  330 + bool isArabic = false;
  331 +
  332 + int prevLetter = 0;
  333 +
  334 + for (int j = 0; j < word.length; j += 1) {
  335 + final int currentLetter = word.codeUnitAt(j);
  336 +
  337 + if (_isArabicDiacritic(currentLetter)) {
  338 + newWord.insert(0, _arabicDiacritics[currentLetter]);
  339 + continue;
  340 + }
  341 + final int nextLetter = word
  342 + .split('')
  343 + .skip(j + 1)
  344 + .map((String e) => e.codeUnitAt(0))
  345 + .firstWhere(
  346 + (int element) => !_isArabicDiacritic(element),
  347 + orElse: () => 0,
  348 + );
  349 +
  350 + if (_isArabicLetter(currentLetter)) {
  351 + isArabic = true;
  352 +
  353 + final int position =
  354 + _getCorrectForm(currentLetter, prevLetter, nextLetter);
  355 + if (position != -1) {
  356 + newWord.insert(0, _arabicSubstitionA[currentLetter][position]);
  357 + } else {
  358 + newWord.add(currentLetter);
  359 + }
  360 + } else {
  361 + if (isArabic && currentLetter > 32) {
  362 + newWord.insert(0, currentLetter);
  363 + } else {
  364 + newWord.add(currentLetter);
  365 + }
  366 + }
  367 + prevLetter = currentLetter;
  368 + }
  369 +
  370 + if (!first) {
  371 + yield ' ';
  372 + }
  373 + first = false;
  374 +
  375 + yield String.fromCharCodes(_resolveLigatures(newWord));
  376 + }
  377 + }
  378 +
  379 + static String convert(String input) {
  380 + return List<String>.from(_parse(input)).join('');
  381 + }
  382 +}
@@ -59,6 +59,11 @@ class PdfTtfFont extends PdfFont { @@ -59,6 +59,11 @@ class PdfTtfFont extends PdfFont {
59 return PdfFontMetrics.zero; 59 return PdfFontMetrics.zero;
60 } 60 }
61 61
  62 + if (PdfArabic._isArabicDiacriticValue(charCode)) {
  63 + final PdfFontMetrics metric = font.glyphInfoMap[g] ?? PdfFontMetrics.zero;
  64 + return metric.copyWith(advanceWidth: 0);
  65 + }
  66 +
62 return font.glyphInfoMap[g] ?? PdfFontMetrics.zero; 67 return font.glyphInfoMap[g] ?? PdfFontMetrics.zero;
63 } 68 }
64 69
@@ -20,6 +20,8 @@ part of widget; @@ -20,6 +20,8 @@ part of widget;
20 20
21 enum TextAlign { left, right, center, justify } 21 enum TextAlign { left, right, center, justify }
22 22
  23 +enum TextDirection { ltr, rtl }
  24 +
23 abstract class _Span { 25 abstract class _Span {
24 _Span(this.style); 26 _Span(this.style);
25 27
@@ -443,12 +445,14 @@ class RichText extends Widget { @@ -443,12 +445,14 @@ class RichText extends Widget {
443 RichText( 445 RichText(
444 {@required this.text, 446 {@required this.text,
445 TextAlign textAlign, 447 TextAlign textAlign,
  448 + TextDirection textDirection,
446 bool softWrap, 449 bool softWrap,
447 this.tightBounds = false, 450 this.tightBounds = false,
448 this.textScaleFactor = 1.0, 451 this.textScaleFactor = 1.0,
449 int maxLines}) 452 int maxLines})
450 : assert(text != null), 453 : assert(text != null),
451 _textAlign = textAlign, 454 _textAlign = textAlign,
  455 + textDirection = textDirection ?? TextDirection.ltr,
452 _softWrap = softWrap, 456 _softWrap = softWrap,
453 _maxLines = maxLines; 457 _maxLines = maxLines;
454 458
@@ -459,6 +463,8 @@ class RichText extends Widget { @@ -459,6 +463,8 @@ class RichText extends Widget {
459 TextAlign get textAlign => _textAlign; 463 TextAlign get textAlign => _textAlign;
460 TextAlign _textAlign; 464 TextAlign _textAlign;
461 465
  466 + final TextDirection textDirection;
  467 +
462 final double textScaleFactor; 468 final double textScaleFactor;
463 469
464 bool get softWrap => _softWrap; 470 bool get softWrap => _softWrap;
@@ -505,6 +511,17 @@ class RichText extends Widget { @@ -505,6 +511,17 @@ class RichText extends Widget {
505 return totalWidth; 511 return totalWidth;
506 } 512 }
507 513
  514 + if (textDirection == TextDirection.rtl) {
  515 + for (_Span span in spans) {
  516 + span.offset = PdfPoint(
  517 + totalWidth - (span.offset.x + span.width) - delta,
  518 + span.offset.y - baseline,
  519 + );
  520 + }
  521 +
  522 + return totalWidth;
  523 + }
  524 +
508 for (_Span span in spans) { 525 for (_Span span in spans) {
509 span.offset = span.offset.translate(delta, -baseline); 526 span.offset = span.offset.translate(delta, -baseline);
510 } 527 }
@@ -566,7 +583,11 @@ class RichText extends Widget { @@ -566,7 +583,11 @@ class RichText extends Widget {
566 final PdfFontMetrics space = 583 final PdfFontMetrics space =
567 font.stringMetrics(' ') * (style.fontSize * textScaleFactor); 584 font.stringMetrics(' ') * (style.fontSize * textScaleFactor);
568 585
569 - final List<String> spanLines = span.text.split('\n'); 586 + final List<String> spanLines = (textDirection == TextDirection.rtl
  587 + ? PdfArabic.convert(span.text)
  588 + : span.text)
  589 + .split('\n');
  590 +
570 for (int line = 0; line < spanLines.length; line++) { 591 for (int line = 0; line < spanLines.length; line++) {
571 for (String word in spanLines[line].split(RegExp(r'\s'))) { 592 for (String word in spanLines[line].split(RegExp(r'\s'))) {
572 if (word.isEmpty) { 593 if (word.isEmpty) {
@@ -846,6 +867,7 @@ class Text extends RichText { @@ -846,6 +867,7 @@ class Text extends RichText {
846 String text, { 867 String text, {
847 TextStyle style, 868 TextStyle style,
848 TextAlign textAlign, 869 TextAlign textAlign,
  870 + TextDirection textDirection,
849 bool softWrap, 871 bool softWrap,
850 bool tightBounds = false, 872 bool tightBounds = false,
851 double textScaleFactor = 1.0, 873 double textScaleFactor = 1.0,
@@ -856,6 +878,7 @@ class Text extends RichText { @@ -856,6 +878,7 @@ class Text extends RichText {
856 textAlign: textAlign, 878 textAlign: textAlign,
857 softWrap: softWrap, 879 softWrap: softWrap,
858 tightBounds: tightBounds, 880 tightBounds: tightBounds,
  881 + textDirection: textDirection,
859 textScaleFactor: textScaleFactor, 882 textScaleFactor: textScaleFactor,
860 maxLines: maxLines); 883 maxLines: maxLines);
861 } 884 }
@@ -18,6 +18,7 @@ library pdf.all_tests; @@ -18,6 +18,7 @@ library pdf.all_tests;
18 18
19 import '../example/main.dart' as example; 19 import '../example/main.dart' as example;
20 import 'annotations_test.dart' as annotations; 20 import 'annotations_test.dart' as annotations;
  21 +import 'arabic_test.dart' as arabic;
21 import 'colors_test.dart' as colors; 22 import 'colors_test.dart' as colors;
22 import 'complex_test.dart' as complex; 23 import 'complex_test.dart' as complex;
23 import 'data_types_test.dart' as data_types; 24 import 'data_types_test.dart' as data_types;
@@ -48,6 +49,7 @@ import 'widget_wrap_test.dart' as widget_wrap; @@ -48,6 +49,7 @@ import 'widget_wrap_test.dart' as widget_wrap;
48 49
49 void main() { 50 void main() {
50 annotations.main(); 51 annotations.main();
  52 + arabic.main();
51 colors.main(); 53 colors.main();
52 complex.main(); 54 complex.main();
53 data_types.main(); 55 data_types.main();
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +// ignore_for_file: omit_local_variable_types
  18 +
  19 +import 'dart:io';
  20 +
  21 +import 'package:pdf/pdf.dart';
  22 +import 'package:pdf/widgets.dart';
  23 +import 'package:test/test.dart';
  24 +
  25 +import 'utils.dart';
  26 +
  27 +class ArabicText {
  28 + ArabicText(this.original, this.reshaped);
  29 +
  30 + final String original;
  31 + final List<int> reshaped;
  32 +
  33 + String get originalRev => original.split('').reversed.join('');
  34 +}
  35 +
  36 +Document pdf;
  37 +Font arabicFont;
  38 +TextStyle style;
  39 +
  40 +void main() {
  41 + setUpAll(() {
  42 + Document.debug = false;
  43 + RichText.debug = true;
  44 + pdf = Document();
  45 +
  46 + arabicFont = loadFont('hacen-tunisia.ttf');
  47 + style = TextStyle(font: arabicFont, fontSize: 30);
  48 + });
  49 +
  50 + test('Arabic Diacritics', () {
  51 + final ArabicText a =
  52 + ArabicText('السلام', <int>[65249, 65276, 65204, 65247, 65165]);
  53 + final ArabicText b = ArabicText('السَلَاْمٌ',
  54 + <int>[1612, 65249, 1618, 1614, 65276, 1614, 65204, 65247, 65165]);
  55 +
  56 + expect(
  57 + PdfArabic.convert(a.original).codeUnits,
  58 + equals(a.reshaped),
  59 + );
  60 + expect(
  61 + PdfArabic.convert(b.original).codeUnits,
  62 + equals(b.reshaped),
  63 + );
  64 + });
  65 +
  66 + test('Arabic Default Reshaping', () {
  67 + final List<ArabicText> cases = <ArabicText>[
  68 + ArabicText('السَلاْمُ عَلَيْكُمْ', <int>[
  69 + 1615,
  70 + 65249,
  71 + 1618,
  72 + 65276,
  73 + 1614,
  74 + 65204,
  75 + 65247,
  76 + 65165,
  77 + 32,
  78 + 1618,
  79 + 65250,
  80 + 1615,
  81 + 65244,
  82 + 1618,
  83 + 65268,
  84 + 1614,
  85 + 65248,
  86 + 1614,
  87 + 65227
  88 + ]),
  89 + ArabicText('اللغة العربيَّة هي أكثرُ اللغاتِ', <int>[
  90 + 65172,
  91 + 65232,
  92 + 65248,
  93 + 65247,
  94 + 65165,
  95 + 32,
  96 + 65172,
  97 + 64608,
  98 + 65268,
  99 + 65169,
  100 + 65198,
  101 + 65228,
  102 + 65247,
  103 + 65165,
  104 + 32,
  105 + 65266,
  106 + 65259,
  107 + 32,
  108 + 1615,
  109 + 65198,
  110 + 65180,
  111 + 65243,
  112 + 65155,
  113 + 32,
  114 + 1616,
  115 + 65173,
  116 + 65166,
  117 + 65232,
  118 + 65248,
  119 + 65247,
  120 + 65165
  121 + ]),
  122 + ArabicText('تحدُّثاً ونُطقاً ضِمْنَ مَجمُوعَة', <int>[
  123 + 1611,
  124 + 65166,
  125 + 65179,
  126 + 64609,
  127 + 65194,
  128 + 65188,
  129 + 65175,
  130 + 32,
  131 + 1611,
  132 + 65166,
  133 + 65240,
  134 + 65220,
  135 + 1615,
  136 + 65255,
  137 + 65261,
  138 + 32,
  139 + 1614,
  140 + 65254,
  141 + 1618,
  142 + 65252,
  143 + 1616,
  144 + 65215,
  145 + 32,
  146 + 65172,
  147 + 1614,
  148 + 65227,
  149 + 65262,
  150 + 1615,
  151 + 65252,
  152 + 65184,
  153 + 1614,
  154 + 65251
  155 + ]),
  156 + ArabicText('اللغات السامية', <int>[
  157 + 65173,
  158 + 65166,
  159 + 65232,
  160 + 65248,
  161 + 65247,
  162 + 65165,
  163 + 32,
  164 + 65172,
  165 + 65268,
  166 + 65251,
  167 + 65166,
  168 + 65204,
  169 + 65247,
  170 + 65165
  171 + ]),
  172 + ArabicText('العربية لغةٌ رسميةٌ في', <int>[
  173 + 65172,
  174 + 65268,
  175 + 65169,
  176 + 65198,
  177 + 65228,
  178 + 65247,
  179 + 65165,
  180 + 32,
  181 + 1612,
  182 + 65172,
  183 + 65232,
  184 + 65247,
  185 + 32,
  186 + 1612,
  187 + 65172,
  188 + 65268,
  189 + 65252,
  190 + 65203,
  191 + 65197,
  192 + 32,
  193 + 65266,
  194 + 65235
  195 + ]),
  196 + ArabicText('كلِّ دولِ الوطنِ العربيِّ', <int>[
  197 + 64610,
  198 + 65246,
  199 + 65243,
  200 + 32,
  201 + 1616,
  202 + 65245,
  203 + 65261,
  204 + 65193,
  205 + 32,
  206 + 1616,
  207 + 65254,
  208 + 65219,
  209 + 65262,
  210 + 65247,
  211 + 65165,
  212 + 32,
  213 + 64610,
  214 + 65266,
  215 + 65169,
  216 + 65198,
  217 + 65228,
  218 + 65247,
  219 + 65165
  220 + ]),
  221 + ArabicText('إضافة إلى كونها لغة', <int>[
  222 + 65172,
  223 + 65235,
  224 + 65166,
  225 + 65215,
  226 + 65159,
  227 + 32,
  228 + 65264,
  229 + 65247,
  230 + 65159,
  231 + 32,
  232 + 65166,
  233 + 65260,
  234 + 65255,
  235 + 65262,
  236 + 65243,
  237 + 32,
  238 + 65172,
  239 + 65232,
  240 + 65247
  241 + ]),
  242 + ArabicText('رسمية في تشاد وإريتريا', <int>[
  243 + 65172,
  244 + 65268,
  245 + 65252,
  246 + 65203,
  247 + 65197,
  248 + 32,
  249 + 65266,
  250 + 65235,
  251 + 32,
  252 + 65193,
  253 + 65166,
  254 + 65208,
  255 + 65175,
  256 + 32,
  257 + 65166,
  258 + 65267,
  259 + 65198,
  260 + 65176,
  261 + 65267,
  262 + 65197,
  263 + 65159,
  264 + 65261
  265 + ]),
  266 + ArabicText('وإسرائيل. وهي إحدى اللغات', <int>[
  267 + 46,
  268 + 65246,
  269 + 65268,
  270 + 65163,
  271 + 65165,
  272 + 65198,
  273 + 65203,
  274 + 65159,
  275 + 65261,
  276 + 32,
  277 + 65266,
  278 + 65259,
  279 + 65261,
  280 + 32,
  281 + 65263,
  282 + 65194,
  283 + 65187,
  284 + 65159,
  285 + 32,
  286 + 65173,
  287 + 65166,
  288 + 65232,
  289 + 65248,
  290 + 65247,
  291 + 65165
  292 + ]),
  293 + ArabicText('الرسمية الست في منظمة', <int>[
  294 + 65172,
  295 + 65268,
  296 + 65252,
  297 + 65203,
  298 + 65198,
  299 + 65247,
  300 + 65165,
  301 + 32,
  302 + 65174,
  303 + 65204,
  304 + 65247,
  305 + 65165,
  306 + 32,
  307 + 65266,
  308 + 65235,
  309 + 32,
  310 + 65172,
  311 + 65252,
  312 + 65224,
  313 + 65256,
  314 + 65251
  315 + ]),
  316 + ArabicText('الأمم المتحدة، ويُحتفل', <int>[
  317 + 65250,
  318 + 65251,
  319 + 65271,
  320 + 65165,
  321 + 32,
  322 + 65171,
  323 + 65194,
  324 + 65188,
  325 + 65176,
  326 + 65252,
  327 + 65247,
  328 + 65165,
  329 + 1548,
  330 + 32,
  331 + 65246,
  332 + 65236,
  333 + 65176,
  334 + 65188,
  335 + 1615,
  336 + 65267,
  337 + 65261
  338 + ]),
  339 + ArabicText('باليوم العالمي للغة العربية', <int>[
  340 + 65249,
  341 + 65262,
  342 + 65268,
  343 + 65247,
  344 + 65166,
  345 + 65169,
  346 + 32,
  347 + 65266,
  348 + 65252,
  349 + 65247,
  350 + 65166,
  351 + 65228,
  352 + 65247,
  353 + 65165,
  354 + 32,
  355 + 65172,
  356 + 65232,
  357 + 65248,
  358 + 65247,
  359 + 32,
  360 + 65172,
  361 + 65268,
  362 + 65169,
  363 + 65198,
  364 + 65228,
  365 + 65247,
  366 + 65165
  367 + ]),
  368 + ArabicText('في 18 ديسمبر كذكرى اعتماد', <int>[
  369 + 65266,
  370 + 65235,
  371 + 32,
  372 + 49,
  373 + 56,
  374 + 32,
  375 + 65198,
  376 + 65170,
  377 + 65252,
  378 + 65204,
  379 + 65267,
  380 + 65193,
  381 + 32,
  382 + 65263,
  383 + 65198,
  384 + 65243,
  385 + 65196,
  386 + 65243,
  387 + 32,
  388 + 65193,
  389 + 65166,
  390 + 65252,
  391 + 65176,
  392 + 65227,
  393 + 65165
  394 + ]),
  395 + ArabicText('العربية بين لغات العمل في', <int>[
  396 + 65172,
  397 + 65268,
  398 + 65169,
  399 + 65198,
  400 + 65228,
  401 + 65247,
  402 + 65165,
  403 + 32,
  404 + 65254,
  405 + 65268,
  406 + 65169,
  407 + 32,
  408 + 65173,
  409 + 65166,
  410 + 65232,
  411 + 65247,
  412 + 32,
  413 + 65246,
  414 + 65252,
  415 + 65228,
  416 + 65247,
  417 + 65165,
  418 + 32,
  419 + 65266,
  420 + 65235
  421 + ]),
  422 + ArabicText('الأمم المتحدة.', <int>[
  423 + 65250,
  424 + 65251,
  425 + 65271,
  426 + 65165,
  427 + 32,
  428 + 46,
  429 + 65171,
  430 + 65194,
  431 + 65188,
  432 + 65176,
  433 + 65252,
  434 + 65247,
  435 + 65165
  436 + ]),
  437 + ];
  438 +
  439 + pdf.addPage(
  440 + MultiPage(
  441 + crossAxisAlignment: CrossAxisAlignment.end,
  442 + build: (Context context) => <Widget>[
  443 + for (ArabicText item in cases)
  444 + Padding(
  445 + padding: const EdgeInsets.only(bottom: 15),
  446 + child: Text(
  447 + item.original,
  448 + textDirection: TextDirection.rtl,
  449 + style: style,
  450 + ),
  451 + ),
  452 + ],
  453 + ),
  454 + );
  455 +
  456 + for (ArabicText item in cases) {
  457 + expect(
  458 + PdfArabic.convert(item.original).codeUnits,
  459 + equals(item.reshaped),
  460 + );
  461 + }
  462 + });
  463 +
  464 + test('Text Widgets Arabic', () {
  465 + pdf.addPage(Page(
  466 + build: (Context context) => RichText(
  467 + textDirection: TextDirection.rtl,
  468 + text: TextSpan(
  469 + text: 'قهوة\n',
  470 + style: TextStyle(
  471 + font: arabicFont,
  472 + fontSize: 30,
  473 + ),
  474 + children: const <TextSpan>[
  475 + TextSpan(
  476 + text:
  477 + 'القهوة مشروب يعد من بذور الب المحمصة، وينمو في أكثر من 70 لداً. خصوصاً في المناطق الاستوائية في أمريكا الشمالية والجنوبية وجنوب شرق آسيا وشبه القارة الهندية وأفريقيا. ويقال أن البن الأخضر هو ثاني أكثر السلع تداولاً في العالم بعد النفط الخام.',
  478 + style: TextStyle(
  479 + fontSize: 20,
  480 + ),
  481 + ),
  482 + ],
  483 + ),
  484 + ),
  485 + ));
  486 + });
  487 +
  488 + tearDownAll(() {
  489 + final File file = File('arabic.pdf');
  490 + file.writeAsBytesSync(pdf.save());
  491 + });
  492 +}
No preview for this file type