Skip to content

Commit

Permalink
Use mapbox-rtl-text-plugin to support right-to-left scripts.
Browse files Browse the repository at this point in the history
- Full support for Unicode Bidirectional Algorithm
- Supports shaping Arabic script
  • Loading branch information
ChrisLoer committed Jan 18, 2017
1 parent 39344e6 commit 8158a30
Show file tree
Hide file tree
Showing 20 changed files with 183 additions and 32 deletions.
11 changes: 11 additions & 0 deletions docs/_data/plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,5 +199,16 @@
]
}
}
},
"mapbox-gl-rtl-text": {
"prefix": "mapbox-gl-rtl-text",
"latest": "0.1.0",
"v": {
"0.1.0": {
"files": [
"mapbox-gl-rtl-text.js"
]
}
}
}
}
4 changes: 3 additions & 1 deletion docs/_layouts/pages.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
id: controls-and-overlays
- name: Browser support
id: browser-support
- name: Internationalization support
id: internationalization
---

<link href='{{site.url}}/css/docs.css' rel='stylesheet' />
Expand Down Expand Up @@ -96,4 +98,4 @@ <h3 class='heading'>{{example.name}}</h3>
</div>
</div>

<script src='{{site.baseurl}}/js/site.js'></script>
<script src='{{site.baseurl}}/js/site.js'></script>
21 changes: 21 additions & 0 deletions docs/_posts/examples/3400-01-31-mapbox-gl-rtl-text.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
layout: example
category: example
title: Add support for Right-to-Left scripts
description: 'Use the <a target="_blank" href="https://github.com/mapbox/mapbox-gl-rtl-text">mapbox-gl-rtl-text</a> plugin to support scripts that use right-to-left layout (such as Arabic or Hebrew).'
tags:
- internationalization
---
<div id='map'></div>

<script>
mapboxgl.setRTLTextPlugin('https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v{{site.data.plugins.mapbox-gl-rtl-text.latest}}/mapbox-gl-rtl-text.js');

var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [44.3763, 33.2788],
zoom: 11
});

</script>
14 changes: 14 additions & 0 deletions docs/_posts/plugins/0100-01-01-mapbox-gl-rtl-text.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
layout: default
categories: plugin
title: mapbox-gl-rtl-text
prefix: mapbox-gl-rtl-text
description: Enable rendering of right-to-left scripts (such as Arabic and Hebrew)
tags:
- isc
code: https://github.com/mapbox/mapbox-gl-rtl-text
license: BSD
suffix: rtl-text
css: false
js: false
---
4 changes: 2 additions & 2 deletions docs/plugins/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ <h3>{{plugin.title | markdownify | strip_html }} <a href='{{plugin.code}}' title
<a href='{{site.baseurl}}/example/{% if plugin.example %}{{plugin.example}}{% else %}{{plugin.prefix}}{% endif %}/' class='rcon next'>View example</a>
</div>
</div>
<div class='pin-right pad2'>
{% if plugin.js or plugin.css %}<div class='pin-right pad2'>
<a href='#' title='Copy plugin to clipboard' data-clipboard-target='snippet-{{forloop.index}}' class='button icon clipboard js-clipboard'>Copy</a>
</div>
</div>{% endif %}
</div>
<div class='col12 quiet-scroll fill-light'>
<pre id='snippet-{{forloop.index}}' class='pad2 prettyprint'>{% if plugin.js %}&lt;script src='https://api.mapbox.com/mapbox-gl-js/plugins/{{plugin.prefix}}/v{{site.data.plugins[plugin.prefix].latest}}/{{plugin.prefix}}.js'&gt;&lt;/script&gt;{% endif %}
Expand Down
1 change: 1 addition & 0 deletions documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ toc:
- accessToken
- supported
- version
- setRTLTextPlugin
- name: Geography
description: |
These are Mapbox GL JS's ways of representing locations
Expand Down
7 changes: 6 additions & 1 deletion js/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const CollisionFeature = require('../../symbol/collision_feature');
const findPoleOfInaccessibility = require('../../util/find_pole_of_inaccessibility');
const classifyRings = require('../../util/classify_rings');
const VectorTileFeature = require('vector-tile').VectorTileFeature;
const rtlTextPlugin = require('../../source/rtl_text_plugin');

const shapeText = Shaping.shapeText;
const shapeIcon = Shaping.shapeIcon;
Expand Down Expand Up @@ -143,7 +144,11 @@ class SymbolBucket {

let text;
if (hasText) {
text = resolveText(feature, layout);
if (rtlTextPlugin.applyArabicShaping) {
text = rtlTextPlugin.applyArabicShaping(resolveText(feature, layout));
} else {
text = resolveText(feature, layout);
}
}

let icon;
Expand Down
16 changes: 16 additions & 0 deletions js/mapbox-gl.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ mapboxgl.util.getArrayBuffer = ajax.getArrayBuffer;
const config = require('./util/config');
mapboxgl.config = config;

const rtlTextPlugin = require('./source/rtl_text_plugin');

mapboxgl.setRTLTextPlugin = rtlTextPlugin.setRTLTextPlugin;

/**
* Sets the map's [RTL text plugin](https://www.mapbox.com/mapbox-gl-js/plugins/#mapbox-gl-rtl-text).
* Necessary for supporting languages like Arabic and Hebrew that are written right-to-left.
*
* @function setRTLTextPlugin
* @param {string} pluginURL URL pointing to the Mapbox RTL text plugin source.
* @param {Function} callback Called with an error argument if there is an error.
* @example
* mapboxgl.setRTLTextPlugin('https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.1/mapbox-gl-rtl-text.js');
* @see [Displaying Arabic text](https://www.mapbox.com/mapbox-gl-js/example/mapbox-gl-rtl-text/)
*/

Object.defineProperty(mapboxgl, 'accessToken', {
get: function() { return config.ACCESS_TOKEN; },
set: function(token) { config.ACCESS_TOKEN = token; }
Expand Down
38 changes: 38 additions & 0 deletions js/source/rtl_text_plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';

const ajax = require('../util/ajax');
const window = require('../util/window');

const pluginAvailableCallbacks = [];
let pluginRequested = false;
let pluginBlobURL = null;

module.exports.registerForPluginAvailability = function(callback) {
if (pluginBlobURL) {
callback(pluginBlobURL);
} else {
pluginAvailableCallbacks.push(callback);
}
};

module.exports.errorCallback = null;

module.exports.setRTLTextPlugin = function(pluginURL, callback) {
if (pluginRequested) {
throw new Error('setRTLTextPlugin cannot be called multiple times.');
}
pluginRequested = true;
module.exports.errorCallback = callback;
ajax.getArrayBuffer(pluginURL, (err, response) => {
if (err) {
callback(err);
} else {
pluginBlobURL =
window.URL.createObjectURL(new window.Blob([response]), {type: "text/javascript"});

for (const pluginAvailableCallback of pluginAvailableCallbacks) {
pluginAvailableCallback(pluginBlobURL);
}
}
});
};
20 changes: 20 additions & 0 deletions js/source/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const VectorTileWorkerSource = require('./vector_tile_worker_source');
const GeoJSONWorkerSource = require('./geojson_worker_source');
const assert = require('assert');

const globalRTLTextPlugin = require('./rtl_text_plugin');

/**
* @private
*/
Expand All @@ -31,6 +33,14 @@ class Worker {
}
this.workerSourceTypes[name] = WorkerSource;
};

this.self.registerRTLTextPlugin = (rtlTextPlugin) => {
if (globalRTLTextPlugin.applyArabicShaping || globalRTLTextPlugin.processBidirectionalText) {
throw new Error('RTL text plugin already registered.');
}
globalRTLTextPlugin['applyArabicShaping'] = rtlTextPlugin.applyArabicShaping;
globalRTLTextPlugin['processBidirectionalText'] = rtlTextPlugin.processBidirectionalText;
};
}

setLayers(mapId, layers) {
Expand Down Expand Up @@ -89,6 +99,16 @@ class Worker {
}
}

loadRTLTextPlugin(map, pluginURL, callback) {
try {
if (!globalRTLTextPlugin.applyArabicShaping && !globalRTLTextPlugin.processBidirectionalText) {
this.self.importScripts(pluginURL);
}
} catch (e) {
callback(e);
}
}

getLayerIndex(mapId) {
let layerIndexes = this.layerIndexes[mapId];
if (!layerIndexes) {
Expand Down
9 changes: 9 additions & 0 deletions js/style/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const MapboxGLFunction = require('mapbox-gl-function');
const getWorkerPool = require('../global_worker_pool');
const deref = require('mapbox-gl-style-spec/lib/deref');
const diff = require('mapbox-gl-style-spec/lib/diff');
const rtlTextPlugin = require('../source/rtl_text_plugin');

const supportedDiffOperations = util.pick(diff.operations, [
'addLayer',
Expand Down Expand Up @@ -76,6 +77,14 @@ class Style extends Evented {
this.setEventedParent(map);
this.fire('dataloading', {dataType: 'style'});

const self = this;
rtlTextPlugin.registerForPluginAvailability((pluginBlobURL) => {
self.dispatcher.broadcast('loadRTLTextPlugin', pluginBlobURL, rtlTextPlugin.errorCallback);
for (const id in self.sourceCaches) {
self.sourceCaches[id].reload(); // Should be a no-op if the plugin loads before any tiles load
}
});

const stylesheetLoaded = (err, stylesheet) => {
if (err) {
this.fire('error', {error: err});
Expand Down
22 changes: 11 additions & 11 deletions js/symbol/shaping.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const scriptDetection = require('../util/script_detection');
const verticalizePunctuation = require('../util/verticalize_punctuation');

const rtlTextPlugin = require('../source/rtl_text_plugin');

const WritingMode = {
horizontal: 1,
Expand Down Expand Up @@ -36,8 +36,6 @@ function Shaping(positionedGlyphs, text, top, bottom, left, right, writingMode)
this.writingMode = writingMode;
}

const newLine = 0x0a;

function breakLines(text, lineBreakPoints) {
const lines = [];
let start = 0;
Expand All @@ -53,15 +51,18 @@ function breakLines(text, lineBreakPoints) {
}

function shapeText(text, glyphs, maxWidth, lineHeight, horizontalAlign, verticalAlign, justify, spacing, translate, verticalHeight, writingMode) {
text = text.trim();
if (writingMode === WritingMode.vertical) text = verticalizePunctuation(text);
let logicalInput = text.trim();
if (writingMode === WritingMode.vertical) logicalInput = verticalizePunctuation(logicalInput);

const positionedGlyphs = [];
const shaping = new Shaping(positionedGlyphs, text, translate[1], translate[1], translate[0], translate[0], writingMode);
const shaping = new Shaping(positionedGlyphs, logicalInput, translate[1], translate[1], translate[0], translate[0], writingMode);

const lines = (writingMode === WritingMode.horizontal && maxWidth) ?
breakLines(text, determineLineBreaks(text, spacing, maxWidth, glyphs)) :
[text];
let lines;
if (rtlTextPlugin.processBidirectionalText) {
lines = rtlTextPlugin.processBidirectionalText(logicalInput, determineLineBreaks(logicalInput, spacing, maxWidth, glyphs));
} else {
lines = breakLines(logicalInput, determineLineBreaks(logicalInput, spacing, maxWidth, glyphs));
}

shapeLines(shaping, glyphs, lines, lineHeight, horizontalAlign, verticalAlign, justify, translate, writingMode, spacing, verticalHeight);

Expand All @@ -81,6 +82,7 @@ const whitespace = {
};

const breakable = {
0x0a: true, // newline
0x20: true, // space
0x26: true, // ampersand
0x28: true, // left parenthesis
Expand All @@ -99,8 +101,6 @@ const breakable = {
// See https://github.com/mapbox/mapbox-gl-js/issues/3658
};

breakable[newLine] = true;

function determineAverageLineWidth(logicalInput, spacing, maxWidth, glyphs) {
let totalWidth = 0;

Expand Down
2 changes: 1 addition & 1 deletion js/ui/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,7 @@ class Map extends Camera {
* @param {Function} SourceType A {@link Source} constructor.
* @param {Function} callback Called when the source type is ready or with an error argument if there is an error.
*/
addSourceType (name, SourceType, callback) {
addSourceType(name, SourceType, callback) {
return this.style.addSourceType(name, SourceType, callback);
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"jsdom": "^9.4.2",
"jsonlint": "^1.6.2",
"lodash.template": "^4.4.0",
"mapbox-gl-rtl-text": "mapbox/mapbox-gl-rtl-text#497a92962075ea35eec22d4344d6310040551b7e",
"minifyify": "^7.0.1",
"npm-run-all": "^3.0.0",
"nyc": "^8.3.0",
Expand Down
20 changes: 20 additions & 0 deletions plugins/src/mapbox-gl-rtl-text/v0.1.0/mapbox-gl-rtl-text.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
"version": 8,
"metadata": {
"test": {
"ignored": {
"js": "https://github.com/mapbox/mapbox-gl-js/issues/3708"
},
"width": 256,
"height": 256
}
Expand Down Expand Up @@ -43,4 +40,4 @@
}
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
"version": 8,
"metadata": {
"test": {
"ignored": {
"js": "https://github.com/mapbox/mapbox-gl-js/issues/3707"
},
"width": 128,
"height": 128
}
Expand Down Expand Up @@ -43,4 +40,4 @@
}
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
"version": 8,
"metadata": {
"test": {
"ignored": {
"js": "https://github.com/mapbox/mapbox-gl-js/issues/3708"
},
"width": 128,
"height": 128
}
Expand Down Expand Up @@ -47,4 +44,4 @@
}
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
"version": 8,
"metadata": {
"test": {
"ignored": {
"js": "https://github.com/mapbox/mapbox-gl-js/issues/3707"
},
"width": 256,
"height": 256
}
Expand Down Expand Up @@ -43,4 +40,4 @@
}
}
]
}
}
5 changes: 5 additions & 0 deletions test/suite_implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ const PNG = require('pngjs').PNG;
const Map = require('../js/ui/map');
const window = require('../js/util/window');
const browser = require('../js/util/browser');
const rtlTextPlugin = require('../js/source/rtl_text_plugin');
const rtlText = require('mapbox-gl-rtl-text');

rtlTextPlugin['applyArabicShaping'] = rtlText.applyArabicShaping;
rtlTextPlugin['processBidirectionalText'] = rtlText.processBidirectionalText;

module.exports = function(style, options, _callback) {
let wasCallbackCalled = false;
Expand Down

0 comments on commit 8158a30

Please sign in to comment.