Skip to content

Commit 24b3e04

Browse files
zmiaoSnailBones
authored andcommitted
Introduce descender and ascender for font metrics + fix several text rendering issues (mapbox#8781)
* Introduce descender+ascender, fix fonts dis-alignment * Fix failing tests * Add glyph baseine checker * Update render test * Update font baseline, make vertical mode applying with font baseline * Fix failing tests * Update render test case source position * Move ascender/descender to font level attributes, remove non-necessary pbf files * Add new glyph pbfs Remove duplicates render tests Fix confilicts * rename the inner callback parameter naming * gl-native parity: Extend Ideographs rasterization range to include CJK symbols and punctuations update comments * Adjust text shaping fix flow and lint errors code clean * Fix shaping.test contents parsing Fix error * Update render tests expectation update expectations Update test ignore list * Fix vertical text rendering with 'text-offset' property * Fix text shaping for characters that have very big part below baselines, such as p, g, y, etc. Fix vertical text shaping with ZWSP and punctuations. additional fix * Update test expectations * Fix text rendering when both 'text-rotate' and 'text-offset' is set * Update render tests * Fix text rendering for line labels * Enable ignored render tests regarding text rendering Update render tests * Make ascender and descender optional * Update render tests * Update shaping.js logic * Update render test expectations * Update shaping.test Fix unit tests * Fix review comments * fix typo
1 parent efb7884 commit 24b3e04

82 files changed

Lines changed: 573 additions & 383 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/render/glyph_atlas.js

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {AlphaImage} from '../util/image.js';
55
import {register} from '../util/web_worker_transfer.js';
66
import potpack from 'potpack';
77

8-
import type {GlyphMetrics, StyleGlyph} from '../style/style_glyph.js';
8+
import type {StyleGlyph} from '../style/style_glyph.js';
99

1010
const glyphPadding = 1;
1111
/*
@@ -17,34 +17,31 @@ const glyphPadding = 1;
1717
*/
1818
const localGlyphPadding = glyphPadding * SDF_SCALE;
1919

20-
export type Rect = {
20+
export type GlyphRect = {
2121
x: number,
2222
y: number,
2323
w: number,
2424
h: number
2525
};
26+
// {glyphID: glyphRect}
27+
export type GlyphPositionMap = { [_: number]: GlyphRect };
2628

27-
export type GlyphPosition = {
28-
rect: Rect,
29-
metrics: GlyphMetrics
30-
};
31-
32-
export type GlyphPositions = {[_: string]: {[_: number]: GlyphPosition } }
29+
// {fontStack: glyphPoistionMap}
30+
export type GlyphPositions = { [_: string]: GlyphPositionMap };
3331

3432
export default class GlyphAtlas {
3533
image: AlphaImage;
3634
positions: GlyphPositions;
37-
38-
constructor(stacks: {[_: string]: {[_: number]: ?StyleGlyph } }) {
35+
constructor(stacks: {[_: string]: {glyphs: {[_: number]: ?StyleGlyph }, ascender?: number, descender?: number }}) {
3936
const positions = {};
4037
const bins = [];
4138

4239
for (const stack in stacks) {
43-
const glyphs = stacks[stack];
44-
const stackPositions = positions[stack] = {};
40+
const glyphData = stacks[stack];
41+
const glyphPositionMap = positions[stack] = {};
4542

46-
for (const id in glyphs) {
47-
const src = glyphs[+id];
43+
for (const id in glyphData.glyphs) {
44+
const src = glyphData.glyphs[+id];
4845
if (!src || src.bitmap.width === 0 || src.bitmap.height === 0) continue;
4946

5047
const padding = src.metrics.localGlyph ? localGlyphPadding : glyphPadding;
@@ -55,20 +52,20 @@ export default class GlyphAtlas {
5552
h: src.bitmap.height + 2 * padding
5653
};
5754
bins.push(bin);
58-
stackPositions[id] = {rect: bin, metrics: src.metrics};
55+
glyphPositionMap[id] = bin;
5956
}
6057
}
6158

6259
const {w, h} = potpack(bins);
6360
const image = new AlphaImage({width: w || 1, height: h || 1});
6461

6562
for (const stack in stacks) {
66-
const glyphs = stacks[stack];
63+
const glyphData = stacks[stack];
6764

68-
for (const id in glyphs) {
69-
const src = glyphs[+id];
65+
for (const id in glyphData.glyphs) {
66+
const src = glyphData.glyphs[+id];
7067
if (!src || src.bitmap.width === 0 || src.bitmap.height === 0) continue;
71-
const bin = positions[stack][id].rect;
68+
const bin = positions[stack][id];
7269
const padding = src.metrics.localGlyph ? localGlyphPadding : glyphPadding;
7370
AlphaImage.copy(src.bitmap, image, {x: 0, y: 0}, {x: bin.x + padding, y: bin.y + padding}, src.bitmap);
7471
}

src/render/glyph_manager.js

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@ export const SDF_SCALE = 2;
3737
type Entry = {
3838
// null means we've requested the range, but the glyph wasn't included in the result.
3939
glyphs: {[id: number]: StyleGlyph | null},
40-
requests: {[range: number]: Array<Callback<{[_: number]: StyleGlyph | null}>>},
40+
requests: {[range: number]: Array<Callback<{glyphs: {[number]: StyleGlyph | null}, ascender?: number, descender?: number}>>},
4141
ranges: {[range: number]: boolean | null},
42-
tinySDF?: TinySDF
42+
tinySDF?: TinySDF,
43+
ascender?: number,
44+
descender?: number
4345
};
4446

4547
export const LocalGlyphMode = {
@@ -55,7 +57,7 @@ class GlyphManager {
5557
entries: {[_: string]: Entry};
5658
// Multiple fontstacks may share the same local glyphs, so keep an index
5759
// into the glyphs based soley on font weight
58-
localGlyphs: {[_: string]: {[id: number]: StyleGlyph | null}};
60+
localGlyphs: {[_: string]: {glyphs: {[id: number]: StyleGlyph | null}, ascender: ?number, descender: ?number}};
5961
url: ?string;
6062

6163
// exposed as statics to enable stubbing in unit tests
@@ -80,7 +82,7 @@ class GlyphManager {
8082
this.url = url;
8183
}
8284

83-
getGlyphs(glyphs: {[stack: string]: Array<number>}, callback: Callback<{[stack: string]: {[id: number]: ?StyleGlyph}}>) {
85+
getGlyphs(glyphs: {[stack: string]: Array<number>}, callback: Callback<{[stack: string]: {glyphs: {[_: number]: ?StyleGlyph}, ascender?: number, descender?: number}}>) {
8486
const all = [];
8587

8688
for (const stack in glyphs) {
@@ -89,49 +91,53 @@ class GlyphManager {
8991
}
9092
}
9193

92-
asyncAll(all, ({stack, id}, callback: Callback<{stack: string, id: number, glyph: ?StyleGlyph}>) => {
94+
asyncAll(all, ({stack, id}, fnCallback: Callback<{stack: string, id: number, glyph: ?StyleGlyph}>) => {
9395
let entry = this.entries[stack];
9496
if (!entry) {
9597
entry = this.entries[stack] = {
9698
glyphs: {},
9799
requests: {},
98-
ranges: {}
100+
ranges: {},
101+
ascender: undefined,
102+
descender: undefined
99103
};
100104
}
101105

102106
let glyph = entry.glyphs[id];
103107
if (glyph !== undefined) {
104-
callback(null, {stack, id, glyph});
108+
fnCallback(null, {stack, id, glyph});
105109
return;
106110
}
107111

108112
glyph = this._tinySDF(entry, stack, id);
109113
if (glyph) {
110114
entry.glyphs[id] = glyph;
111-
callback(null, {stack, id, glyph});
115+
fnCallback(null, {stack, id, glyph});
112116
return;
113117
}
114118

115119
const range = Math.floor(id / 256);
116120
if (range * 256 > 65535) {
117-
callback(new Error('glyphs > 65535 not supported'));
121+
fnCallback(new Error('glyphs > 65535 not supported'));
118122
return;
119123
}
120124

121125
if (entry.ranges[range]) {
122-
callback(null, {stack, id, glyph});
126+
fnCallback(null, {stack, id, glyph});
123127
return;
124128
}
125129

126130
let requests = entry.requests[range];
127131
if (!requests) {
128132
requests = entry.requests[range] = [];
129133
GlyphManager.loadGlyphRange(stack, range, (this.url: any), this.requestManager,
130-
(err, response: ?{[_: number]: StyleGlyph | null}) => {
134+
(err, response: ?{glyphs: {[_: number]: StyleGlyph | null}, ascender?: number, descender?: number}) => {
131135
if (response) {
132-
for (const id in response) {
136+
entry.ascender = response.ascender;
137+
entry.descender = response.descender;
138+
for (const id in response.glyphs) {
133139
if (!this._doesCharSupportLocalGlyph(+id)) {
134-
entry.glyphs[+id] = response[+id];
140+
entry.glyphs[+id] = response.glyphs[+id];
135141
}
136142
}
137143
entry.ranges[range] = true;
@@ -143,11 +149,11 @@ class GlyphManager {
143149
});
144150
}
145151

146-
requests.push((err, result: ?{[_: number]: StyleGlyph | null}) => {
152+
requests.push((err, result: ?{glyphs: {[_: number]: StyleGlyph | null}, ascender?: number, descender?: number}) => {
147153
if (err) {
148-
callback(err);
154+
fnCallback(err);
149155
} else if (result) {
150-
callback(null, {stack, id, glyph: result[id] || null});
156+
fnCallback(null, {stack, id, glyph: result.glyphs[id] || null});
151157
}
152158
});
153159
}, (err, glyphs: ?Array<{stack: string, id: number, glyph: ?StyleGlyph}>) => {
@@ -158,11 +164,15 @@ class GlyphManager {
158164

159165
for (const {stack, id, glyph} of glyphs) {
160166
// Clone the glyph so that our own copy of its ArrayBuffer doesn't get transferred.
161-
(result[stack] || (result[stack] = {}))[id] = glyph && {
167+
if (result[stack] === undefined) result[stack] = {};
168+
if (result[stack].glyphs === undefined) result[stack].glyphs = {};
169+
result[stack].glyphs[id] = glyph && {
162170
id: glyph.id,
163171
bitmap: glyph.bitmap.clone(),
164172
metrics: glyph.metrics
165173
};
174+
result[stack].ascender = this.entries[stack].ascender;
175+
result[stack].descender = this.entries[stack].descender;
166176
}
167177

168178
callback(null, result);
@@ -181,7 +191,9 @@ class GlyphManager {
181191
(isChar['CJK Unified Ideographs'](id) ||
182192
isChar['Hangul Syllables'](id) ||
183193
isChar['Hiragana'](id) ||
184-
isChar['Katakana'](id));
194+
isChar['Katakana'](id)) ||
195+
// gl-native parity: Extend Ideographs rasterization range to include CJK symbols and punctuations
196+
isChar['CJK Symbols and Punctuation'](id);
185197
/* eslint-enable new-cap */
186198
}
187199
}

src/source/worker_source.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export type WorkerTileResult = {
5959
rawTileData?: ArrayBuffer,
6060
resourceTiming?: Array<PerformanceResourceTiming>,
6161
// Only used for benchmarking:
62-
glyphMap?: {[_: string]: {[_: number]: ?StyleGlyph}} | null,
62+
glyphMap?: {[_: string]: {glyphs: {[_: number]: ?StyleGlyph}, ascender?: number, descender?: number}} | null,
6363
iconMap?: {[_: string]: StyleImage} | null,
6464
glyphPositions?: GlyphPositions | null
6565
};

src/source/worker_tile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ class WorkerTile {
163163
lineAtlas.trim();
164164

165165
let error: ?Error;
166-
let glyphMap: ?{[_: string]: {[_: number]: ?StyleGlyph}};
166+
let glyphMap: ?{[_: string]: {glyphs: {[_: number]: ?StyleGlyph}, ascender?: number, descender?: number}};
167167
let iconMap: ?{[_: string]: StyleImage};
168168
let patternMap: ?{[_: string]: StyleImage};
169169
const taskMetadata = {type: 'maybePrepare', isSymbolTile: this.isSymbolTile, zoom: this.zoom};

src/style/load_glyph_range.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default function (fontstack: string,
1212
range: number,
1313
urlTemplate: string,
1414
requestManager: RequestManager,
15-
callback: Callback<{[_: number]: StyleGlyph | null}>) {
15+
callback: Callback<{glyphs: {[number]: StyleGlyph | null}, ascender?: number, descender?: number}>) {
1616
const begin = range * 256;
1717
const end = begin + 255;
1818

@@ -27,12 +27,11 @@ export default function (fontstack: string,
2727
callback(err);
2828
} else if (data) {
2929
const glyphs = {};
30-
31-
for (const glyph of parseGlyphPBF(data)) {
30+
const glyphData = parseGlyphPBF(data);
31+
for (const glyph of glyphData.glyphs) {
3232
glyphs[glyph.id] = glyph;
3333
}
34-
35-
callback(null, glyphs);
34+
callback(null, {glyphs, ascender: glyphData.ascender, descender: glyphData.descender});
3635
}
3736
});
3837
}

src/style/parse_glyph_pbf.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,28 @@ const border = 3;
77

88
import type {StyleGlyph} from './style_glyph.js';
99

10-
function readFontstacks(tag: number, glyphs: Array<StyleGlyph>, pbf: Protobuf) {
10+
function readFontstacks(tag: number, glyphData: {glyphs: Array<StyleGlyph>, ascender?: number, descender?: number}, pbf: Protobuf) {
11+
glyphData.glyphs = [];
1112
if (tag === 1) {
12-
pbf.readMessage(readFontstack, glyphs);
13+
pbf.readMessage(readFontstack, glyphData);
1314
}
1415
}
1516

16-
function readFontstack(tag: number, glyphs: Array<StyleGlyph>, pbf: Protobuf) {
17+
function readFontstack(tag: number, glyphData: {glyphs: Array<StyleGlyph>, ascender?: number, descender?: number}, pbf: Protobuf) {
1718
if (tag === 3) {
1819
const {id, bitmap, width, height, left, top, advance} = pbf.readMessage(readGlyph, {});
19-
glyphs.push({
20+
glyphData.glyphs.push({
2021
id,
2122
bitmap: new AlphaImage({
2223
width: width + 2 * border,
2324
height: height + 2 * border
2425
}, bitmap),
2526
metrics: {width, height, left, top, advance}
2627
});
28+
} else if (tag === 4) {
29+
glyphData.ascender = pbf.readSVarint();
30+
} else if (tag === 5) {
31+
glyphData.descender = pbf.readSVarint();
2732
}
2833
}
2934

@@ -37,8 +42,8 @@ function readGlyph(tag: number, glyph: Object, pbf: Protobuf) {
3742
else if (tag === 7) glyph.advance = pbf.readVarint();
3843
}
3944

40-
export default function (data: ArrayBuffer | Uint8Array): Array<StyleGlyph> {
41-
return new Protobuf(data).readFields(readFontstacks, []);
45+
export default function (data: ArrayBuffer | Uint8Array): {glyphs: Array<StyleGlyph>, ascender?: number, descender?: number} {
46+
return new Protobuf(data).readFields(readFontstacks, {});
4247
}
4348

4449
export const GLYPH_PBF_BORDER = border;

src/style/style.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1723,7 +1723,7 @@ class Style extends Evented {
17231723
setDependencies(this._symbolSourceCaches[params.source]);
17241724
}
17251725

1726-
getGlyphs(mapId: string, params: {stacks: {[_: string]: Array<number>}}, callback: Callback<{[_: string]: {[_: number]: ?StyleGlyph}}>) {
1726+
getGlyphs(mapId: string, params: {stacks: {[_: string]: Array<number>}}, callback: Callback<{[_: string]: {glyphs: {[_: number]: ?StyleGlyph}, ascender?: number, descender?: number}}>) {
17271727
this.glyphManager.getGlyphs(params.stacks, callback);
17281728
}
17291729

0 commit comments

Comments
 (0)