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

string-index => master #163

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
14 changes: 2 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,10 @@ Maps a single unicode code point (number) to a Glyph object. Does not perform an

Returns whether there is glyph in the font for the given unicode code point.

#### `font.glyphsForString(string)`

This method returns an array of Glyph objects for the given string. This is only a one-to-one mapping from characters
to glyphs. For most uses, you should use `font.layout` (described below), which provides a much more advanced mapping
supporting AAT and OpenType shaping.

### Glyph metrics and layout

Fontkit includes several methods for accessing glyph metrics and performing layout, including support for kerning and other advanced OpenType positioning adjustments.

#### `font.widthOfGlyph(glyph_id)`

Returns the advance width (described above) for a single glyph id.

#### `font.layout(string, features = [])`

This method returns a `GlyphRun` object, which includes an array of `Glyph`s and `GlyphPosition`s for the given string.
Expand Down Expand Up @@ -154,9 +144,9 @@ have axis names as keys, and numbers as values (should be in the range specified

### Other methods

#### `font.getGlyph(glyph_id, codePoints = [])`
#### `font.getGlyph(glyph_id)`

Returns a glyph object for the given glyph id. You can pass the array of code points this glyph represents for your use later, and it will be stored in the glyph object.
Returns a glyph object for the given glyph id.

#### `font.createSubset()`

Expand Down
66 changes: 7 additions & 59 deletions src/TTFFont.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,58 +279,6 @@ export default class TTFFont {
return this.getGlyph(this._cmapProcessor.lookup(codePoint), [codePoint]);
}

/**
* Returns an array of Glyph objects for the given string.
* This is only a one-to-one mapping from characters to glyphs.
* For most uses, you should use font.layout (described below), which
* provides a much more advanced mapping supporting AAT and OpenType shaping.
*
* @param {string} string
* @return {Glyph[]}
*/
glyphsForString(string) {
let glyphs = [];
let len = string.length;
let idx = 0;
let last = -1;
let state = -1;

while (idx <= len) {
let code = 0;
let nextState = 0;

if (idx < len) {
// Decode the next codepoint from UTF 16
code = string.charCodeAt(idx++);
if (0xd800 <= code && code <= 0xdbff && idx < len) {
let next = string.charCodeAt(idx);
if (0xdc00 <= next && next <= 0xdfff) {
idx++;
code = ((code & 0x3ff) << 10) + (next & 0x3ff) + 0x10000;
}
}

// Compute the next state: 1 if the next codepoint is a variation selector, 0 otherwise.
nextState = ((0xfe00 <= code && code <= 0xfe0f) || (0xe0100 <= code && code <= 0xe01ef)) ? 1 : 0;
} else {
idx++;
}

if (state === 0 && nextState === 1) {
// Variation selector following normal codepoint.
glyphs.push(this.getGlyph(this._cmapProcessor.lookup(last, code), [last, code]));
} else if (state === 0 && nextState === 0) {
// Normal codepoint following normal codepoint.
glyphs.push(this.glyphForCodePoint(last));
}

last = code;
state = nextState;
}

return glyphs;
}

@cache
get _layoutEngine() {
return new LayoutEngine(this);
Expand Down Expand Up @@ -374,13 +322,13 @@ export default class TTFFont {
return this._layoutEngine.getAvailableFeatures(script, language);
}

_getBaseGlyph(glyph, characters = []) {
_getBaseGlyph(glyph) {
if (!this._glyphs[glyph]) {
if (this.directory.tables.glyf) {
this._glyphs[glyph] = new TTFGlyph(glyph, characters, this);
this._glyphs[glyph] = new TTFGlyph(glyph, this);

} else if (this.directory.tables['CFF '] || this.directory.tables.CFF2) {
this._glyphs[glyph] = new CFFGlyph(glyph, characters, this);
this._glyphs[glyph] = new CFFGlyph(glyph, this);
}
}

Expand All @@ -396,16 +344,16 @@ export default class TTFFont {
* @param {number[]} characters
* @return {Glyph}
*/
getGlyph(glyph, characters = []) {
getGlyph(glyph) {
if (!this._glyphs[glyph]) {
if (this.directory.tables.sbix) {
this._glyphs[glyph] = new SBIXGlyph(glyph, characters, this);
this._glyphs[glyph] = new SBIXGlyph(glyph, this);

} else if ((this.directory.tables.COLR) && (this.directory.tables.CPAL)) {
this._glyphs[glyph] = new COLRGlyph(glyph, characters, this);
this._glyphs[glyph] = new COLRGlyph(glyph, this);

} else {
this._getBaseGlyph(glyph, characters);
this._getBaseGlyph(glyph);
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/WOFF2Font.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ export default class WOFF2Font extends TTFFont {

// Override this method to get a glyph and return our
// custom subclass if there is a glyf table.
_getBaseGlyph(glyph, characters = []) {
_getBaseGlyph(glyph) {
if (!this._glyphs[glyph]) {
if (this.directory.tables.glyf && this.directory.tables.glyf.transformed) {
if (!this._transformedGlyphs) { this._transformGlyfTable(); }
return this._glyphs[glyph] = new WOFF2Glyph(glyph, characters, this);
return this._glyphs[glyph] = new WOFF2Glyph(glyph, this);

} else {
return super._getBaseGlyph(glyph, characters);
return super._getBaseGlyph(glyph);
}
}
}
Expand Down
16 changes: 9 additions & 7 deletions src/aat/AATMorxProcessor.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import AATStateMachine from './AATStateMachine';
import AATLookupTable from './AATLookupTable';
import {cache} from '../decorators';
import GlyphInfo from '../opentype/GlyphInfo';

// indic replacement flags
const MARK_FIRST = 0x8000;
Expand Down Expand Up @@ -141,7 +142,7 @@ export default class AATMorxProcessor {
glyph = this.glyphs[this.markedGlyph];
var gid = lookupTable.lookup(glyph.id);
if (gid) {
this.glyphs[this.markedGlyph] = this.font.getGlyph(gid, glyph.codePoints);
glyph.id = gid;
}
}

Expand All @@ -151,7 +152,7 @@ export default class AATMorxProcessor {
glyph = this.glyphs[index];
var gid = lookupTable.lookup(glyph.id);
if (gid) {
this.glyphs[index] = this.font.getGlyph(gid, glyph.codePoints);
glyph.id = gid;
}
}

Expand Down Expand Up @@ -191,12 +192,12 @@ export default class AATMorxProcessor {

if (last || store) {
let ligatureEntry = ligatureList.getItem(ligatureIndex);
this.glyphs[componentGlyph] = this.font.getGlyph(ligatureEntry, codePoints);
this.glyphs[componentGlyph] = new GlyphInfo(this.font, ligatureEntry, codePoints, this.glyphs[componentGlyph].stringIndex);
ligatureGlyphs.push(componentGlyph);
ligatureIndex = 0;
codePoints = [];
} else {
this.glyphs[componentGlyph] = this.font.getGlyph(0xffff);
this.glyphs[componentGlyph] = new GlyphInfo(this.font, 0xffff);
}
}

Expand All @@ -213,17 +214,18 @@ export default class AATMorxProcessor {
if (glyph.id !== 0xffff) {
let gid = lookupTable.lookup(glyph.id);
if (gid) { // 0 means do nothing
glyphs[index] = this.font.getGlyph(gid, glyph.codePoints);
glyph.id = gid;
}
}
}
}

_insertGlyphs(glyphIndex, insertionActionIndex, count, isBefore) {
let stringIndex = this.glyphs[glyphIndex].stringIndex;
let insertions = [];
while (count--) {
let gid = this.subtable.table.insertionActions.getItem(insertionActionIndex++);
insertions.push(this.font.getGlyph(gid));
insertions.push(new GlyphInfo(this.font, gid, undefined, stringIndex));
}

if (!isBefore) {
Expand Down Expand Up @@ -314,7 +316,7 @@ export default class AATMorxProcessor {
});

// Add glyph to input and glyphs to process.
let g = this.font.getGlyph(glyph);
let g = new GlyphInfo(this.font, glyph);
input.push(g);
glyphs.push(input[input.length - 1]);

Expand Down
7 changes: 1 addition & 6 deletions src/glyph/Glyph.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import StandardNames from './StandardNames';
* on the font format, but they all inherit from this class.
*/
export default class Glyph {
constructor(id, codePoints, font) {
constructor(id, font) {
/**
* The glyph id in the font
* @type {number}
Expand All @@ -25,12 +25,7 @@ export default class Glyph {
* that represent multiple visual characters.
* @type {number[]}
*/
this.codePoints = codePoints;
this._font = font;

// TODO: get this info from GDEF if available
this.isMark = this.codePoints.length > 0 && this.codePoints.every(unicode.isMark);
this.isLigature = this.codePoints.length > 1;
}

_getPath() {
Expand Down
6 changes: 6 additions & 0 deletions src/layout/GlyphRun.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ export default class GlyphRun {
*/
this.positions = null;

/**
* An array of indices to indices in the input string for each glyph
* @type {number[]}
*/
this.stringIndices = null;

/**
* The script that was requested for shaping. This was either passed in or detected automatically.
* @type {string}
Expand Down
85 changes: 63 additions & 22 deletions src/layout/LayoutEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as Script from './Script';
import unicode from 'unicode-properties';
import AATLayoutEngine from '../aat/AATLayoutEngine';
import OTLayoutEngine from '../opentype/OTLayoutEngine';
import GlyphInfo from '../opentype/GlyphInfo';

export default class LayoutEngine {
constructor(font) {
Expand All @@ -32,28 +33,12 @@ export default class LayoutEngine {
features = [];
}

// Map string to glyphs if needed
if (typeof string === 'string') {
// Attempt to detect the script from the string if not provided.
if (script == null) {
script = Script.forString(string);
}

var glyphs = this.font.glyphsForString(string);
} else {
// Attempt to detect the script from the glyph code points if not provided.
if (script == null) {
let codePoints = [];
for (let glyph of string) {
codePoints.push(...glyph.codePoints);
}

script = Script.forCodePoints(codePoints);
}

var glyphs = string;
// Attempt to detect the script from the string if not provided.
if (script == null) {
script = Script.forString(string);
}

let glyphs = this.glyphsForString(string);
let glyphRun = new GlyphRun(glyphs, features, script, language, direction);

// Return early if there are no glyphs
Expand All @@ -78,9 +63,65 @@ export default class LayoutEngine {
this.engine.cleanup();
}

// Map glyph infos back to normal Glyph objects
glyphRun.stringIndices = glyphRun.glyphs.map(glyphInfo => glyphInfo.stringIndex);
glyphRun.glyphs = glyphRun.glyphs.map(glyphInfo => this.font.getGlyph(glyphInfo.id));
return glyphRun;
}

/**
* Returns an array of Glyph objects for the given string.
* This is only a one-to-one mapping from characters to glyphs.
* For most uses, you should use font.layout (described below), which
* provides a much more advanced mapping supporting AAT and OpenType shaping.
*
* @param {string} string
* @return {Glyph[]}
*/
glyphsForString(string) {
let glyphs = [];
let len = string.length;
let idx = 0;
let last = -1;
let state = -1;

while (idx <= len) {
let code = 0;
let nextState = 0;
let stringIndex = idx - 1;

if (idx < len) {
// Decode the next codepoint from UTF 16
code = string.charCodeAt(idx++);
if (0xd800 <= code && code <= 0xdbff && idx < len) {
let next = string.charCodeAt(idx);
if (0xdc00 <= next && next <= 0xdfff) {
idx++;
code = ((code & 0x3ff) << 10) + (next & 0x3ff) + 0x10000;
}
}

// Compute the next state: 1 if the next codepoint is a variation selector, 0 otherwise.
nextState = ((0xfe00 <= code && code <= 0xfe0f) || (0xe0100 <= code && code <= 0xe01ef)) ? 1 : 0;
} else {
idx++;
}

if (state === 0 && nextState === 1) {
// Variation selector following normal codepoint.
glyphs.push(new GlyphInfo(this.font, this.font._cmapProcessor.lookup(last, code), [last, code], stringIndex));
} else if (state === 0 && nextState === 0) {
// Normal codepoint following normal codepoint.
glyphs.push(new GlyphInfo(this.font, this.font._cmapProcessor.lookup(last), [last], stringIndex));
}

last = code;
state = nextState;
}

return glyphs;
}

substitute(glyphRun) {
// Call the advanced layout engine to make substitutions
if (this.engine && this.engine.substitute) {
Expand Down Expand Up @@ -119,10 +160,10 @@ export default class LayoutEngine {
}

hideDefaultIgnorables(glyphs, positions) {
let space = this.font.glyphForCodePoint(0x20);
const space = this.font.glyphForCodePoint(0x20);
for (let i = 0; i < glyphs.length; i++) {
if (this.isDefaultIgnorable(glyphs[i].codePoints[0])) {
glyphs[i] = space;
glyphs[i].id = space.id;
positions[i].xAdvance = 0;
positions[i].yAdvance = 0;
}
Expand Down
5 changes: 2 additions & 3 deletions src/opentype/GSUBProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@ export default class GSUBProcessor extends OTProcessor {
this.glyphIterator.cur.id = sequence[0];
this.glyphIterator.cur.ligatureComponent = 0;

let features = this.glyphIterator.cur.features;
let curGlyph = this.glyphIterator.cur;
let replacement = sequence.slice(1).map((gid, i) => {
let glyph = new GlyphInfo(this.font, gid, undefined, features);
let glyph = new GlyphInfo(this.font, gid, undefined, curGlyph.stringIndex, curGlyph.features);
glyph.shaperInfo = curGlyph.shaperInfo;
glyph.isLigated = curGlyph.isLigated;
glyph.ligatureComponent = i + 1;
Expand Down Expand Up @@ -82,7 +81,7 @@ export default class GSUBProcessor extends OTProcessor {
}

// Create the replacement ligature glyph
let ligatureGlyph = new GlyphInfo(this.font, ligature.glyph, characters, curGlyph.features);
let ligatureGlyph = new GlyphInfo(this.font, ligature.glyph, characters, curGlyph.stringIndex, curGlyph.features);
ligatureGlyph.shaperInfo = curGlyph.shaperInfo;
ligatureGlyph.isLigated = true;
ligatureGlyph.substituted = true;
Expand Down
Loading