From 053b353aa159e9a69e091cf2a81b2f2634836820 Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Fri, 19 May 2017 12:30:14 -0700 Subject: [PATCH] Disable letter-spacing for Arabic text (issue #4208) --- src/data/bucket/symbol_bucket.js | 5 +- src/util/is_char_in_unicode_block.js | 14 ++--- src/util/script_detection.js | 17 +++++ .../text-arabic/letter-spacing/expected.png | Bin 0 -> 4819 bytes .../text-arabic/letter-spacing/style.json | 59 ++++++++++++++++++ 5 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 test/integration/render-tests/text-arabic/letter-spacing/expected.png create mode 100644 test/integration/render-tests/text-arabic/letter-spacing/style.json diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index f6ccdf8dd1b..56f52ff1475 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -360,10 +360,11 @@ class SymbolBucket { if (feature.text) { const allowsVerticalWritingMode = scriptDetection.allowsVerticalWritingMode(feature.text); const textOffset = this.layers[0].getLayoutValue('text-offset', {zoom: this.zoom}, feature.properties).map((t)=> t * oneEm); + const spacingIfAllowed = scriptDetection.allowsLetterSpacing(feature.text) ? spacing : 0; shapedTextOrientations = { - [WritingMode.horizontal]: shapeText(feature.text, stacks[fontstack], maxWidth, lineHeight, horizontalAlign, verticalAlign, justify, spacing, textOffset, oneEm, WritingMode.horizontal), - [WritingMode.vertical]: allowsVerticalWritingMode && textAlongLine && shapeText(feature.text, stacks[fontstack], maxWidth, lineHeight, horizontalAlign, verticalAlign, justify, spacing, textOffset, oneEm, WritingMode.vertical) + [WritingMode.horizontal]: shapeText(feature.text, stacks[fontstack], maxWidth, lineHeight, horizontalAlign, verticalAlign, justify, spacingIfAllowed, textOffset, oneEm, WritingMode.horizontal), + [WritingMode.vertical]: allowsVerticalWritingMode && textAlongLine && shapeText(feature.text, stacks[fontstack], maxWidth, lineHeight, horizontalAlign, verticalAlign, justify, spacingIfAllowed, textOffset, oneEm, WritingMode.vertical) }; } else { shapedTextOrientations = {}; diff --git a/src/util/is_char_in_unicode_block.js b/src/util/is_char_in_unicode_block.js index 5eb83f8174d..1c1c70da29e 100644 --- a/src/util/is_char_in_unicode_block.js +++ b/src/util/is_char_in_unicode_block.js @@ -18,15 +18,15 @@ const unicodeBlockLookup: UnicodeBlockLookup = { // 'Cyrillic': (char) => char >= 0x0400 && char <= 0x04FF, // 'Cyrillic Supplement': (char) => char >= 0x0500 && char <= 0x052F, // 'Armenian': (char) => char >= 0x0530 && char <= 0x058F, - // 'Hebrew': (char) => char >= 0x0590 && char <= 0x05FF, - // 'Arabic': (char) => char >= 0x0600 && char <= 0x06FF, - // 'Syriac': (char) => char >= 0x0700 && char <= 0x074F, - // 'Arabic Supplement': (char) => char >= 0x0750 && char <= 0x077F, + //'Hebrew': (char) => char >= 0x0590 && char <= 0x05FF, + 'Arabic': (char) => char >= 0x0600 && char <= 0x06FF, + //'Syriac': (char) => char >= 0x0700 && char <= 0x074F, + 'Arabic Supplement': (char) => char >= 0x0750 && char <= 0x077F, // 'Thaana': (char) => char >= 0x0780 && char <= 0x07BF, // 'NKo': (char) => char >= 0x07C0 && char <= 0x07FF, // 'Samaritan': (char) => char >= 0x0800 && char <= 0x083F, // 'Mandaic': (char) => char >= 0x0840 && char <= 0x085F, - // 'Arabic Extended-A': (char) => char >= 0x08A0 && char <= 0x08FF, + 'Arabic Extended-A': (char) => char >= 0x08A0 && char <= 0x08FF, // 'Devanagari': (char) => char >= 0x0900 && char <= 0x097F, // 'Bengali': (char) => char >= 0x0980 && char <= 0x09FF, // 'Gurmukhi': (char) => char >= 0x0A00 && char <= 0x0A7F, @@ -159,13 +159,13 @@ const unicodeBlockLookup: UnicodeBlockLookup = { 'Private Use Area': (char) => char >= 0xE000 && char <= 0xF8FF, 'CJK Compatibility Ideographs': (char) => char >= 0xF900 && char <= 0xFAFF, // 'Alphabetic Presentation Forms': (char) => char >= 0xFB00 && char <= 0xFB4F, - // 'Arabic Presentation Forms-A': (char) => char >= 0xFB50 && char <= 0xFDFF, + 'Arabic Presentation Forms-A': (char) => char >= 0xFB50 && char <= 0xFDFF, // 'Variation Selectors': (char) => char >= 0xFE00 && char <= 0xFE0F, 'Vertical Forms': (char) => char >= 0xFE10 && char <= 0xFE1F, // 'Combining Half Marks': (char) => char >= 0xFE20 && char <= 0xFE2F, 'CJK Compatibility Forms': (char) => char >= 0xFE30 && char <= 0xFE4F, 'Small Form Variants': (char) => char >= 0xFE50 && char <= 0xFE6F, - // 'Arabic Presentation Forms-B': (char) => char >= 0xFE70 && char <= 0xFEFF, + 'Arabic Presentation Forms-B': (char) => char >= 0xFE70 && char <= 0xFEFF, 'Halfwidth and Fullwidth Forms': (char) => char >= 0xFF00 && char <= 0xFFEF // 'Specials': (char) => char >= 0xFFF0 && char <= 0xFFFF, // 'Linear B Syllabary': (char) => char >= 0x10000 && char <= 0x1007F, diff --git a/src/util/script_detection.js b/src/util/script_detection.js index ad8fb597cfe..89b9564a804 100644 --- a/src/util/script_detection.js +++ b/src/util/script_detection.js @@ -17,6 +17,23 @@ module.exports.allowsVerticalWritingMode = function(chars) { return false; }; +module.exports.allowsLetterSpacing = function(chars) { + for (const char of chars) { + if (!exports.charAllowsLetterSpacing(char.charCodeAt(0))) return false; + } + return true; +}; + +module.exports.charAllowsLetterSpacing = function(char) { + if (isChar['Arabic'](char)) return false; + if (isChar['Arabic Supplement'](char)) return false; + if (isChar['Arabic Extended-A'](char)) return false; + if (isChar['Arabic Presentation Forms-A'](char)) return false; + if (isChar['Arabic Presentation Forms-B'](char)) return false; + + return true; +}; + module.exports.charAllowsIdeographicBreaking = function(char) { // Return early for characters outside all ideographic ranges. if (char < 0x2E80) return false; diff --git a/test/integration/render-tests/text-arabic/letter-spacing/expected.png b/test/integration/render-tests/text-arabic/letter-spacing/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..212659e51bf1306f19c8fd98d4c85a477e196e68 GIT binary patch literal 4819 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