diff --git a/docs/index.html b/docs/index.html
index ed92e27e..972f1f2e 100755
--- a/docs/index.html
+++ b/docs/index.html
@@ -114,7 +114,7 @@
Free Software
hinting: form.hinting.checked,
features: {
liga: form.ligatures.checked,
- rlig: form.ligatures.checked
+ rlig: true
}
};
previewCtx.clearRect(0, 0, 940, 300);
diff --git a/src/bidi.js b/src/bidi.js
index bbc77907..23df1bfc 100644
--- a/src/bidi.js
+++ b/src/bidi.js
@@ -10,7 +10,7 @@ import arabicSentenceCheck from './features/arab/contextCheck/arabicSentence.js'
import arabicPresentationForms from './features/arab/arabicPresentationForms.js';
import arabicRequiredLigatures from './features/arab/arabicRequiredLigatures.js';
import latinWordCheck from './features/latn/contextCheck/latinWord.js';
-import latinLigature from './features/latn/latinLigatures.js';
+import { latinRequiredLigature, latinLigature } from './features/latn/latinLigatures.js';
import thaiWordCheck from './features/thai/contextCheck/thaiWord.js';
import unicodeVariationSequenceCheck from './features/unicode/contextCheck/variationSequenceCheck.js';
import unicodeVariationSequences from './features/unicode/variationSequences.js';
@@ -281,15 +281,26 @@ function applyArabicRequireLigatures() {
}
/**
- * Apply required arabic ligatures
+ * Apply required and normal latin ligatures
*/
function applyLatinLigatures() {
- if (!this.hasFeatureEnabled('latn', 'liga')) return;
+ const script = 'latn';
+ if (!Object.prototype.hasOwnProperty.call(this.featuresTags, script)) return;
+ const tags = this.featuresTags[script];
+ if ((tags.indexOf('rlig') === -1) && (tags.indexOf('liga') === -1)) return;
checkGlyphIndexStatus.call(this);
const ranges = this.tokenizer.getContextRanges('latinWord');
+
for(let i = 0; i < ranges.length; i++) {
const range = ranges[i];
- latinLigature.call(this, range);
+
+ if (tags.indexOf('rlig') >= 0) {
+ latinRequiredLigature.call(this, range);
+ }
+
+ if (tags.indexOf('liga') >= 0) {
+ latinLigature.call(this, range);
+ }
}
}
diff --git a/src/features/arab/arabicRequiredLigatures.js b/src/features/arab/arabicRequiredLigatures.js
index c2fe38c0..87c72cb8 100644
--- a/src/features/arab/arabicRequiredLigatures.js
+++ b/src/features/arab/arabicRequiredLigatures.js
@@ -2,43 +2,14 @@
* Apply Arabic required ligatures feature to a range of tokens
*/
-import { ContextParams } from '../../tokenizer.js';
-import applySubstitution from '../applySubstitution.js';
-
-// @TODO: use commonFeatureUtils.js for reduction of code duplication
-// once #564 has been merged.
-
-/**
- * Update context params
- * @param {any} tokens a list of tokens
- * @param {number} index current item index
- */
-function getContextParams(tokens, index) {
- const context = tokens.map(token => token.activeState.value);
- return new ContextParams(context, index || 0);
-}
+import { applyFeatureToRange } from '../commonFeatureUtils.js';
/**
* Apply Arabic required ligatures to a context range
* @param {ContextRange} range a range of tokens
*/
function arabicRequiredLigatures(range) {
- const script = 'arab';
- let tokens = this.tokenizer.getRangeTokens(range);
- let contextParams = getContextParams(tokens);
- for (let index = 0; index < contextParams.context.length; index++) {
- contextParams.setCurrentIndex(index);
- let substitutions = this.query.lookupFeature({
- tag: 'rlig', script, contextParams
- });
- if (substitutions.length) {
- for(let i = 0; i < substitutions.length; i++) {
- const action = substitutions[i];
- applySubstitution.call(this, action, tokens, index);
- }
- contextParams = getContextParams(tokens);
- }
- }
+ applyFeatureToRange.apply(this, ['arab', 'rlig', range]);
}
export default arabicRequiredLigatures;
diff --git a/src/features/commonFeatureUtils.js b/src/features/commonFeatureUtils.js
new file mode 100644
index 00000000..f83c470a
--- /dev/null
+++ b/src/features/commonFeatureUtils.js
@@ -0,0 +1,38 @@
+import { ContextParams } from '../tokenizer.js';
+import applySubstitution from './applySubstitution.js';
+
+/**
+ * Update context params
+ * @param {any} tokens a list of tokens
+ * @param {number} index current item index
+ */
+function getContextParams(tokens, index) {
+ const context = tokens.map(token => token.activeState.value);
+ return new ContextParams(context, index || 0);
+}
+
+/**
+ * Apply feature substitutions to a context range
+ * @param {string} script a script tag
+ * @param {string} feature 4-letter feature code
+ * @param {ContextRange} range a range of tokens
+ */
+function applyFeatureToRange(script, feature, range) {
+ let tokens = this.tokenizer.getRangeTokens(range);
+ let contextParams = getContextParams(tokens);
+ for (let index = 0; index < contextParams.context.length; index++) {
+ contextParams.setCurrentIndex(index);
+ let substitutions = this.query.lookupFeature({
+ tag: feature, script, contextParams
+ });
+ if (substitutions.length) {
+ for(let i = 0; i < substitutions.length; i++) {
+ const action = substitutions[i];
+ applySubstitution(action, tokens, index);
+ }
+ contextParams = getContextParams(tokens);
+ }
+ }
+}
+
+export { getContextParams, applyFeatureToRange };
diff --git a/src/features/latn/latinLigatures.js b/src/features/latn/latinLigatures.js
index 5d06da02..f80eab47 100644
--- a/src/features/latn/latinLigatures.js
+++ b/src/features/latn/latinLigatures.js
@@ -2,43 +2,23 @@
* Apply Latin ligature feature to a range of tokens
*/
-import { ContextParams } from '../../tokenizer.js';
-import applySubstitution from '../applySubstitution.js';
-
-// @TODO: use commonFeatureUtils.js for reduction of code duplication
-// once #564 has been merged.
+import { applyFeatureToRange } from '../commonFeatureUtils.js';
/**
- * Update context params
- * @param {any} tokens a list of tokens
- * @param {number} index current item index
+ * Apply Latin required ligatures to a context range
+ * @param {ContextRange} range a range of tokens
*/
-function getContextParams(tokens, index) {
- const context = tokens.map(token => token.activeState.value);
- return new ContextParams(context, index || 0);
+function latinRequiredLigature(range) {
+ applyFeatureToRange.apply(this, ['latn', 'rlig', range]);
}
/**
- * Apply Arabic required ligatures to a context range
+ * Apply Latin ligatures to a context range
* @param {ContextRange} range a range of tokens
*/
function latinLigature(range) {
- const script = 'latn';
- let tokens = this.tokenizer.getRangeTokens(range);
- let contextParams = getContextParams(tokens);
- for(let index = 0; index < contextParams.context.length; index++) {
- contextParams.setCurrentIndex(index);
- let substitutions = this.query.lookupFeature({
- tag: 'liga', script, contextParams
- });
- if (substitutions.length) {
- for(let i = 0; i < substitutions.length; i++) {
- const action = substitutions[i];
- applySubstitution.call(this, action, tokens, index);
- }
- contextParams = getContextParams(tokens);
- }
- }
+ applyFeatureToRange.apply(this, ['latn', 'liga', range]);
}
export default latinLigature;
+export { latinRequiredLigature, latinLigature };
diff --git a/test/bidi.js b/test/bidi.js
index 648fef6c..2d7f57b8 100644
--- a/test/bidi.js
+++ b/test/bidi.js
@@ -7,8 +7,11 @@ const loadSync = (url, opt) => parse(readFileSync(url), opt);
describe('bidi.js', function() {
let latinFont;
let arabicFont;
+ let scriptFont;
let bidiFira;
let bidiScheherazade;
+ let bidiPecita;
+ let bidiPecitaNoRlig;
let arabicTokenizer;
before(function () {
@@ -40,6 +43,28 @@ describe('bidi.js', function() {
tags: ['liga', 'rlig']
}];
bidiFira.applyFeatures(latinFont, latinFeatures);
+ /**
+ * script font for rlig tests
+ */
+ scriptFont = loadSync('./test/fonts/Pecita.ttf');
+ bidiPecita = new Bidi();
+ bidiPecita.registerModifier(
+ 'glyphIndex', null, token => scriptFont.charToGlyphIndex(token.char)
+ );
+ bidiPecitaNoRlig = new Bidi();
+ bidiPecitaNoRlig.registerModifier(
+ 'glyphIndex', null, token => scriptFont.charToGlyphIndex(token.char)
+ );
+ const scriptFeatures = [{
+ script: 'latn',
+ tags: ['liga', 'rlig']
+ }];
+ const scriptFeaturesNoRlig = [{
+ script: 'latn',
+ tags: ['liga']
+ }];
+ bidiPecita.applyFeatures(scriptFont, scriptFeatures);
+ bidiPecitaNoRlig.applyFeatures(scriptFont, scriptFeaturesNoRlig);
});
describe('arabic contexts', function() {
it('should match arabic words in a given text', function() {
@@ -83,6 +108,14 @@ describe('bidi.js', function() {
let glyphIndexes = bidiScheherazade.getTextGlyphs('َّ'); // Arabic word 'َّ' : 'Fatha & Shadda'
assert.deepEqual(glyphIndexes, [1311]);
});
+ it('should apply required latin ligature', function () {
+ let glyphIndexes = bidiPecita.getTextGlyphs('quick');
+ assert.deepEqual(glyphIndexes, [4130, 79, 3676]); // "qu" and "ck" rlig
+ });
+ it('should render differently without required latin ligatures', function () {
+ let glyphIndexes = bidiPecitaNoRlig.getTextGlyphs('quick'); // no rligs
+ assert.deepEqual(glyphIndexes, [87, 91, 79, 73, 81]);
+ });
it('should apply latin ligature', function () {
let glyphIndexes = bidiFira.getTextGlyphs('fi'); // fi => fi
assert.deepEqual(glyphIndexes, [1145]);
diff --git a/test/fonts/Pecita.ttf b/test/fonts/Pecita.ttf
new file mode 100644
index 00000000..4ac891f4
Binary files /dev/null and b/test/fonts/Pecita.ttf differ