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

554 latin rlig #5

Merged
merged 11 commits into from
Nov 30, 2023
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ <h1>Free Software</h1>
hinting: form.hinting.checked,
features: {
liga: form.ligatures.checked,
rlig: form.ligatures.checked
rlig: true
}
};
previewCtx.clearRect(0, 0, 940, 300);
Expand Down
19 changes: 15 additions & 4 deletions src/bidi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}
}
}

Expand Down
33 changes: 2 additions & 31 deletions src/features/arab/arabicRequiredLigatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
38 changes: 38 additions & 0 deletions src/features/commonFeatureUtils.js
Original file line number Diff line number Diff line change
@@ -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 };
36 changes: 8 additions & 28 deletions src/features/latn/latinLigatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
33 changes: 33 additions & 0 deletions test/bidi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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]);
Expand Down
Binary file added test/fonts/Pecita.ttf
Binary file not shown.
Loading