Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use mapbox-gl-rtl-text to do Arabic text shaping and bidirectional layout (#3708) #3758

Merged
merged 1 commit into from
Jan 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.0/mapbox-gl-rtl-text.js');
* @see [Add support for right-to-left scripts](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