Skip to content

Commit

Permalink
Merge branch 'master' into feature/thai-context-substitutions-support
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/bidi.js
#	test/bidi.js
  • Loading branch information
rafallyczkowskiadylic committed Mar 6, 2023
2 parents 57bfeec + 1b61010 commit 4d0405b
Show file tree
Hide file tree
Showing 19 changed files with 848 additions and 130 deletions.
16 changes: 15 additions & 1 deletion src/bidi.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import thaiWordCheck from './features/thai/contextCheck/thaiWord.js';
import thaiGlyphComposition from './features/thai/thaiGlyphComposition.js';
import thaiLigatures from './features/thai/thaiLigatures.js';
import thaiRequiredLigatures from './features/thai/thaiRequiredLigatures.js';
import unicodeVariationSequenceCheck from './features/unicode/contextCheck/variationSequenceCheck.js';
import unicodeVariationSequences from './features/unicode/variationSequences.js';

/**
* Create Bidi. features
Expand Down Expand Up @@ -43,7 +45,8 @@ Bidi.prototype.contextChecks = ({
latinWordCheck,
arabicWordCheck,
arabicSentenceCheck,
thaiWordCheck
thaiWordCheck,
unicodeVariationSequenceCheck
});

/**
Expand All @@ -65,6 +68,7 @@ function tokenizeText() {
registerContextChecker.call(this, 'arabicWord');
registerContextChecker.call(this, 'arabicSentence');
registerContextChecker.call(this, 'thaiWord');
registerContextChecker.call(this, 'unicodeVariationSequence');
return this.tokenizer.tokenize(this.text);
}

Expand Down Expand Up @@ -178,6 +182,13 @@ function applyLatinLigatures() {
});
}

function applyUnicodeVariationSequences() {
const ranges = this.tokenizer.getContextRanges('unicodeVariationSequence');
ranges.forEach(range => {
unicodeVariationSequences.call(this, range);
});
}

/**
* Apply available thai features
*/
Expand Down Expand Up @@ -220,6 +231,9 @@ Bidi.prototype.applyFeaturesToContexts = function () {
if (this.checkContextReady('thaiWord')) {
applyThaiFeatures.call(this);
}
if (this.checkContextReady('unicodeVariationSequence')) {
applyUnicodeVariationSequences.call(this);
}
};

/**
Expand Down
3 changes: 3 additions & 0 deletions src/features/arab/arabicRequiredLigatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
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
Expand Down
3 changes: 3 additions & 0 deletions src/features/latn/latinLigatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
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
Expand Down
38 changes: 38 additions & 0 deletions src/features/unicode/contextCheck/variationSequenceCheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Unicode Variation Sequence context checkers
*/

function isVariationSequenceSelector(char) {
if (char === null) return false;
const charCode = char.codePointAt(0);
return (
// Mongolian Variation Selectors
(charCode >= 0x180B && charCode <= 0x180D) ||
// Generic Variation Selectors
(charCode >= 0xFE00 && charCode <= 0xFE0F) ||
// Ideographic Variation Sequences
(charCode >= 0xE0100 && charCode <= 0xE01EF)
);
}

function unicodeVariationSequenceStartCheck(contextParams) {
const char = contextParams.current;
const nextChar = contextParams.get(1);
return (
(nextChar === null && isVariationSequenceSelector(char)) ||
(isVariationSequenceSelector(nextChar))
);
}

function unicodeVariationSequenceEndCheck(contextParams) {
const nextChar = contextParams.get(1);
return (
(nextChar === null) ||
(!isVariationSequenceSelector(nextChar))
);
}

export default {
startCheck: unicodeVariationSequenceStartCheck,
endCheck: unicodeVariationSequenceEndCheck
};
40 changes: 40 additions & 0 deletions src/features/unicode/variationSequences.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Apply unicode variation sequences to a range of tokens
*/

/**
* Apply unicode variation squences to a context range
* @param {ContextRange} range a range of tokens
*
* @TODO: We could incorporate the data from
* https://www.unicode.org/Public/UCD/latest/ucd/StandardizedVariants.txt
* https://www.unicode.org/Public/UCD/latest/ucd/emoji/emoji-variation-sequences.txt
* https://www.unicode.org/ivd/data/2022-09-13/IVD_Sequences.txt
* and ignore any sequences that are not standardized, but that would have to be
* kept up-do-date and result in huge data overhead
*/
function unicodeVariationSequence(range) {
const font = this.query.font;
const tokens = this.tokenizer.getRangeTokens(range);
// treat varSelector as if not there (this should have been the default even before supporting UVSes)
// https://unicode.org/faq/vs.html#6
tokens[1].setState('deleted', true);
if(font.tables.cmap && font.tables.cmap.varSelectorList) {
const baseCodePoint = tokens[0].char.codePointAt(0);
const vsCodePoint = tokens[1].char.codePointAt(0);
const selectorLookup = font.tables.cmap.varSelectorList[vsCodePoint];
if (selectorLookup !== undefined) {
if (selectorLookup.nonDefaultUVS) {
const mappings = selectorLookup.nonDefaultUVS.uvsMappings;
if(mappings[baseCodePoint]) {
const replacementGlyphId = mappings[baseCodePoint].glyphID;
if(font.glyphs.glyphs[replacementGlyphId] !== undefined) {
tokens[0].setState('glyphIndex', replacementGlyphId);
}
}
}
}
}
}

export default unicodeVariationSequence;
4 changes: 2 additions & 2 deletions src/glyphset.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,12 @@ function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) {
* @param {string} charstring
* @return {opentype.Glyph}
*/
function cffGlyphLoader(font, index, parseCFFCharstring, charstring) {
function cffGlyphLoader(font, index, parseCFFCharstring, charstring, version) {
return function() {
const glyph = new Glyph({index: index, font: font});

glyph.path = function() {
const path = parseCFFCharstring(font, glyph, charstring);
const path = parseCFFCharstring(font, glyph, charstring, version);
path.unitsPerEm = font.unitsPerEm;
return path;
};
Expand Down
9 changes: 8 additions & 1 deletion src/opentype.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ function parseBuffer(buffer, opt={}) {
}

let cffTableEntry;
let cff2TableEntry;
let fvarTableEntry;
let statTableEntry;
let gvarTableEntry;
Expand Down Expand Up @@ -319,6 +320,9 @@ function parseBuffer(buffer, opt={}) {
case 'CFF ':
cffTableEntry = tableEntry;
break;
case 'CFF2':
cff2TableEntry = tableEntry;
break;
case 'kern':
kernTableEntry = tableEntry;
break;
Expand Down Expand Up @@ -350,8 +354,11 @@ function parseBuffer(buffer, opt={}) {
} else if (cffTableEntry) {
const cffTable = uncompressTable(data, cffTableEntry);
cff.parse(cffTable.data, cffTable.offset, font, opt);
} else if (cff2TableEntry) {
const cffTable2 = uncompressTable(data, cff2TableEntry);
cff.parse(cffTable2.data, cffTable2.offset, font, opt);
} else {
throw new Error('Font doesn\'t contain TrueType or CFF outlines.');
throw new Error('Font doesn\'t contain TrueType, CFF or CFF2 outlines.');
}

const hmtxTable = uncompressTable(data, hmtxTableEntry);
Expand Down
97 changes: 97 additions & 0 deletions src/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ function getShort(dataView, offset) {
return dataView.getInt16(offset, false);
}

function getUInt24(dataView, offset) {
return (dataView.getUint16(offset) << 8) + dataView.getUint8(offset + 2);
}

// Retrieve an unsigned 32-bit long from the DataView.
// The value is stored in big endian.
function getULong(dataView, offset) {
Expand Down Expand Up @@ -80,12 +84,18 @@ const typeOffsets = {
byte: 1,
uShort: 2,
short: 2,
uInt24: 3,
uLong: 4,
fixed: 4,
longDateTime: 8,
tag: 4
};

const masks = {
LONG_WORDS: 0x8000,
WORD_DELTA_COUNT_MASK: 0x7FFF
};

// A stateful parser that changes the offset whenever a value is retrieved.
// The data is a DataView.
function Parser(data, offset) {
Expand Down Expand Up @@ -130,6 +140,13 @@ Parser.prototype.parseF2Dot14 = function() {
return v;
};


Parser.prototype.parseUInt24 = function() {
const v = getUInt24(this.data, this.offset + this.relativeOffset);
this.relativeOffset += 3;
return v;
};

Parser.prototype.parseULong = function() {
const v = getULong(this.data, this.offset + this.relativeOffset);
this.relativeOffset += 4;
Expand Down Expand Up @@ -548,9 +565,11 @@ Parser.tag = Parser.prototype.parseTag;
Parser.byte = Parser.prototype.parseByte;
Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort;
Parser.uShortList = Parser.prototype.parseUShortList;
Parser.uInt24 = Parser.prototype.parseUInt24;
Parser.uLong = Parser.offset32 = Parser.prototype.parseULong;
Parser.uLongList = Parser.prototype.parseULongList;
Parser.fixed = Parser.prototype.parseFixed;
Parser.f2Dot14 = Parser.prototype.parseF2Dot14;
Parser.struct = Parser.prototype.parseStruct;
Parser.coverage = Parser.prototype.parseCoverage;
Parser.classDef = Parser.prototype.parseClassDef;
Expand Down Expand Up @@ -615,12 +634,90 @@ Parser.prototype.parseFeatureVariationsList = function() {
}) || [];
};

// VariationStore, ItemVariationStore, VariationRegionList, regionAxes, ItemVariationSubtables ...
// https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats
// https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#item-variation-store-header-and-item-variation-data-subtables

Parser.prototype.parseVariationStore = function() {
const vsOffset = this.relativeOffset;
const length = this.parseUShort();
const variationStore = {
itemVariationStore: this.parseItemVariationStore()
};
this.relativeOffset = vsOffset + length + 2; // + 2 for length field
return variationStore;
};

Parser.prototype.parseItemVariationStore = function() {
const itemStoreOffset = this.relativeOffset;
const iVStore = {
format: this.parseUShort(),
variationRegions: [],
itemVariationSubtables: []
};

const variationRegionListOffset = this.parseOffset32();
const itemVariationDataCount = this.parseUShort();
const itemVariationDataOffsets = this.parseULongList(itemVariationDataCount);

this.relativeOffset = itemStoreOffset + variationRegionListOffset;
iVStore.variationRegions = this.parseVariationRegionList();

for( let i = 0; i < itemVariationDataCount; i++ ) {
const subtableOffset = itemVariationDataOffsets[i];
this.relativeOffset = itemStoreOffset + subtableOffset;
iVStore.itemVariationSubtables.push(this.parseItemVariationSubtable());
}

return iVStore;
};

Parser.prototype.parseVariationRegionList = function() {
const axisCount = this.parseUShort();
const regionCount = this.parseUShort();
return this.parseRecordList(regionCount, {
regionAxes: Parser.recordList(axisCount, {
startCoord: Parser.f2Dot14,
peakCoord: Parser.f2Dot14,
endCoord: Parser.f2Dot14,
})
});
};

Parser.prototype.parseItemVariationSubtable = function() {
const itemCount = this.parseUShort();
const wordDeltaCount = this.parseUShort();

const subtable = {
regionIndexes: this.parseUShortList(),
deltaSets: this.parseDeltaSets(itemCount, wordDeltaCount)
};

return subtable;
};

Parser.prototype.parseDeltaSets = function(itemCount, wordDeltaCount) {
const deltas = [];
const longFlag = wordDeltaCount & masks.LONG_WORDS;
const wordCount = wordDeltaCount & masks.WORD_DELTA_COUNT_MASK;

const wordParser = (longFlag ? this.parseULong : this.parseUShort).bind(this);
const restParser = (longFlag ? this.parseUShort : this.parseByte).bind(this);
for( let i = 0; i < itemCount; i++ ) {
const deltaParser = i < wordCount ? wordParser : restParser;
deltas.push(deltaParser());
}

return deltas;
};

export default {
getByte,
getCard8: getByte,
getUShort,
getCard16: getUShort,
getShort,
getUInt24,
getULong,
getFixed,
getTag,
Expand Down
Loading

0 comments on commit 4d0405b

Please sign in to comment.