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

Index by grapheme #2458

Closed
wants to merge 15 commits into from
61 changes: 52 additions & 9 deletions src/data/bucket/symbol_bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ import type {SizeData} from '../../symbol/symbol_size';
import type {FeatureStates} from '../../source/source_state';
import type {ImagePosition} from '../../render/image_atlas';
import type {VectorTileLayer} from '@mapbox/vector-tile';
import CanvasComparer from '../../util/canvas_comparer';
import { markedStringToParts } from '../../util/util';

export type SingleCollisionBox = {
x1: number;
Expand Down Expand Up @@ -362,6 +364,8 @@ class SymbolBucket implements Bucket {
allowVerticalPlacement: boolean;
hasRTLText: boolean;

textCache: {[key: string]: string[]};

constructor(options: BucketParameters<SymbolStyleLayer>) {
this.collisionBoxArray = options.collisionBoxArray;
this.zoom = options.zoom;
Expand Down Expand Up @@ -404,6 +408,8 @@ class SymbolBucket implements Bucket {
this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id);

this.sourceID = options.sourceID;

this.textCache = {};
}

createArrays() {
Expand All @@ -415,15 +421,52 @@ class SymbolBucket implements Bucket {
this.symbolInstances = new SymbolInstanceArray();
}

calculateGlyphDependencies(text: string, stack: {[_: number]: boolean}, textAlongLine: boolean, allowVerticalPlacement: boolean, doesAllowVerticalWritingMode: boolean) {
for (let i = 0; i < text.length; i++) {
stack[text.charCodeAt(i)] = true;
if ((textAlongLine || allowVerticalPlacement) && doesAllowVerticalWritingMode) {
const verticalChar = verticalizedCharacterMap[text.charAt(i)];
if (verticalChar) {
stack[verticalChar.charCodeAt(0)] = true;
}
}
calculateGlyphDependencies(text: string, stack: {[_: string]: boolean}, textAlongLine: boolean, allowVerticalPlacement: boolean, doesAllowVerticalWritingMode: boolean) {
// let graphemes = [];

// if (text in this.textCache) {
// // console.log('symbol_bucket cache hit', text);
// graphemes = this.textCache[text];

// } else {
// // console.log('symbol bucket cache miss', text);

// // const segmenter = new Intl.Segmenter(
// // 'en', {granularity: 'grapheme'}
// // );
// // graphemes = Array.from(segmenter.segment(text), s => s.segment);

// const graphemes = [...text];

// // console.log('shaping', line.text);

// // const canvasComparer = new CanvasComparer();

// // if (!canvasComparer.isLatin(text) && !canvasComparer.compareCanvases(text, graphemes)) {
// // canvasComparer.mergeStrings(graphemes);
// // }

// this.textCache[text] = [...graphemes];
// }


// const graphemes = [...text];

const graphemes = markedStringToParts(text);

for (let i = 0; i < graphemes.length; i++) {
// OLIVER
// stack[text.charCodeAt(i).toString()] = true;
stack[graphemes[i]] = true;
// if ((textAlongLine || allowVerticalPlacement) && doesAllowVerticalWritingMode) {
// // OLIVER
// const verticalChar = verticalizedCharacterMap[text.charAt(i)];
// if (verticalChar) {
// // OLIVER
// // stack[verticalChar.charCodeAt(0).toString()] = true;
// stack[verticalChar[i]] = true;
// }
// }
}
}

Expand Down
7 changes: 5 additions & 2 deletions src/render/glyph_atlas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ export default class GlyphAtlas {
const positions = {};
const bins = [];

// console.log('glyph_atlas', stacks);
for (const stack in stacks) {
const glyphs = stacks[stack];
const stackPositions = positions[stack] = {};

for (const id in glyphs) {
const src = glyphs[+id];
//const src = glyphs[+id];
const src = glyphs[id];
if (!src || src.bitmap.width === 0 || src.bitmap.height === 0) continue;

const bin = {
Expand All @@ -62,7 +64,8 @@ export default class GlyphAtlas {
const glyphs = stacks[stack];

for (const id in glyphs) {
const src = glyphs[+id];
// const src = glyphs[+id];
const src = glyphs[id];
if (!src || src.bitmap.width === 0 || src.bitmap.height === 0) continue;
const bin = positions[stack][id].rect;
AlphaImage.copy(src.bitmap, image, {x: 0, y: 0}, {x: bin.x + padding, y: bin.y + padding}, src.bitmap);
Expand Down
26 changes: 25 additions & 1 deletion src/render/glyph_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ export default class GlyphManager {
}

glyph = this._tinySDF(entry, stack, id);

// console.log('glyph_manager', glyph);

if (glyph) {
entry.glyphs[id] = glyph;
callback(null, {stack, id, glyph});
Expand Down Expand Up @@ -161,6 +164,7 @@ export default class GlyphManager {
}

_doesCharSupportLocalGlyph(id: number): boolean {
return true;
/* eslint-disable new-cap */
return !!this.localIdeographFontFamily &&
(isChar['CJK Unified Ideographs'](id) ||
Expand Down Expand Up @@ -200,7 +204,27 @@ export default class GlyphManager {
});
}

const char = tinySDF.draw(String.fromCharCode(id));
console.log('tinysdf', id, typeof id);
// OLIVER
// const char = tinySDF.draw(String.fromCharCode(id));

// let char;

// const cachedChar = localStorage.getItem(id as any);

// if (cachedChar) {
// console.log('cached');
// char = JSON.parse(cachedChar);
// char.data = Object.values(char.data);
// } else {
// console.log('not cached');
// char = tinySDF.draw(id as any);
// localStorage.setItem(id as any, JSON.stringify(char));
// }

const char = tinySDF.draw(id as any);

// console.log('char', char, JSON.stringify(char));

/**
* TinySDF's "top" is the distance from the alphabetic baseline to the top of the glyph.
Expand Down
2 changes: 1 addition & 1 deletion src/source/worker_tile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class WorkerTile {
let iconMap: {[_: string]: StyleImage};
let patternMap: {[_: string]: StyleImage};

const stacks = mapObject(options.glyphDependencies, (glyphs) => Object.keys(glyphs).map(Number));
const stacks = mapObject(options.glyphDependencies, (glyphs) => Object.keys(glyphs).map(String));
if (Object.keys(stacks).length) {
actor.send('getGlyphs', {uid: this.uid, stacks, source: this.source, tileID: this.tileID, type: 'glyphs'}, (err, result) => {
if (!error) {
Expand Down
86 changes: 68 additions & 18 deletions src/symbol/shaping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import {
import verticalizePunctuation from '../util/verticalize_punctuation';
import {plugin as rtlTextPlugin} from '../source/rtl_text_plugin';
import ONE_EM from './one_em';
import {warnOnce} from '../util/util';
import {markedStringToParts, warnOnce} from '../util/util';

import type {StyleGlyph, GlyphMetrics} from '../style/style_glyph';
import {GLYPH_PBF_BORDER} from '../style/parse_glyph_pbf';
import type {ImagePosition} from '../render/image_atlas';
import {IMAGE_PADDING} from '../render/image_atlas';
import type {Rect, GlyphPosition} from '../render/glyph_atlas';
import {Formatted, FormattedSection} from '@maplibre/maplibre-gl-style-spec';
import CanvasComparer from '../util/canvas_comparer';

enum WritingMode {
none = 0,
Expand Down Expand Up @@ -140,8 +141,10 @@ class TaggedString {
return this.sectionIndex[index];
}

getCharCode(index: number): number {
return this.text.charCodeAt(index);
getCharCode(index: number): string {
// OLIVER
// return this.text.charCodeAt(index);
return this.text[index];
}

verticalizePunctuation() {
Expand All @@ -151,12 +154,14 @@ class TaggedString {
trim() {
let beginningWhitespace = 0;
for (let i = 0;
// OLIVER
i < this.text.length && whitespace[this.text.charCodeAt(i)];
i++) {
beginningWhitespace++;
}
let trailingWhitespace = this.text.length;
for (let i = this.text.length - 1;
// OLIVER
i >= 0 && i >= beginningWhitespace && whitespace[this.text.charCodeAt(i)];
i--) {
trailingWhitespace--;
Expand Down Expand Up @@ -203,6 +208,7 @@ class TaggedString {
return;
}

// OLIVER
this.text += String.fromCharCode(nextImageSectionCharCode);
this.sections.push(SectionOptions.forImage(imageName));
this.sectionIndex.push(this.sections.length - 1);
Expand Down Expand Up @@ -316,6 +322,7 @@ function shapeText(
verticalizable: false
};

// console.log('shaping', 'glyphMap', glyphMap);
shapeLines(shaping, glyphMap, glyphPositions, imagePositions, lines, lineHeight, textAnchor, textJustify, writingMode, spacing, allowVerticalPlacement, layoutTextSizeThisZoom);
if (isEmpty(positionedLines)) return false;

Expand All @@ -337,8 +344,9 @@ const whitespace: {
};

const breakable: {
[_: number]: boolean;
[_: number | string]: boolean;
} = {
['\n']: true,
[0x0a]: true, // newline
[0x20]: true, // space
[0x26]: true, // ampersand
Expand Down Expand Up @@ -382,6 +390,7 @@ function getGlyphAdvance(
}
}

// safe to ignore this fucntion
function determineAverageLineWidth(logicalInput: TaggedString,
spacing: number,
maxWidth: number,
Expand All @@ -396,7 +405,7 @@ function determineAverageLineWidth(logicalInput: TaggedString,

for (let index = 0; index < logicalInput.length(); index++) {
const section = logicalInput.getSection(index);
totalWidth += getGlyphAdvance(logicalInput.getCharCode(index), section, glyphMap, imagePositions, spacing, layoutTextSize);
totalWidth += getGlyphAdvance(logicalInput.getCharCode(index) as any, section, glyphMap, imagePositions, spacing, layoutTextSize);
}

const lineCount = Math.max(1, Math.ceil(totalWidth / maxWidth));
Expand Down Expand Up @@ -505,6 +514,7 @@ function determineLineBreaks(
symbolPlacement: string,
layoutTextSize: number
): Array<number> {

if (symbolPlacement !== 'point')
return [];

Expand All @@ -521,12 +531,12 @@ function determineLineBreaks(
for (let i = 0; i < logicalInput.length(); i++) {
const section = logicalInput.getSection(i);
const codePoint = logicalInput.getCharCode(i);
if (!whitespace[codePoint]) currentX += getGlyphAdvance(codePoint, section, glyphMap, imagePositions, spacing, layoutTextSize);
if (!whitespace[codePoint]) currentX += getGlyphAdvance(codePoint as any, section, glyphMap, imagePositions, spacing, layoutTextSize);

// Ideographic characters, spaces, and word-breaking punctuation that often appear without
// surrounding spaces.
if ((i < logicalInput.length() - 1)) {
const ideographicBreak = charAllowsIdeographicBreaking(codePoint);
const ideographicBreak = charAllowsIdeographicBreaking(codePoint as any);
if (breakable[codePoint] || ideographicBreak || section.imageName) {

potentialLineBreaks.push(
Expand All @@ -535,7 +545,7 @@ function determineLineBreaks(
currentX,
targetWidth,
potentialLineBreaks,
calculatePenalty(codePoint, logicalInput.getCharCode(i + 1), ideographicBreak && hasServerSuggestedBreakpoints),
calculatePenalty(codePoint as any, logicalInput.getCharCode(i + 1) as any, ideographicBreak && hasServerSuggestedBreakpoints),
false));
}
}
Expand Down Expand Up @@ -583,6 +593,8 @@ function getAnchorAlignment(anchor: SymbolAnchor) {
return {horizontalAlign, verticalAlign};
}

const textCache: {[key: string]: string[]} = {};

function shapeLines(shaping: Shaping,
glyphMap: {
[_: string]: {
Expand Down Expand Up @@ -615,6 +627,9 @@ function shapeLines(shaping: Shaping,
textJustify === 'left' ? 0 : 0.5;

let lineIndex = 0;

//const canvasComparer = new CanvasComparer();

for (const line of lines) {
line.trim();

Expand All @@ -631,21 +646,54 @@ function shapeLines(shaping: Shaping,
continue;
}

for (let i = 0; i < line.length(); i++) {
const section = line.getSection(i);
const sectionIndex = line.getSectionIndex(i);
const codePoint = line.getCharCode(i);
// let graphemes = [];

// if (line.text in textCache) {
// // console.log('cache hit', line.text);
// graphemes = textCache[line.text];

// } else {
// // console.log('cache miss', line.text);

// // const segmenter = new Intl.Segmenter(
// // 'en', {granularity: 'grapheme'}
// // );
// // graphemes = Array.from(segmenter.segment(line.text), s => s.segment);

// const graphemes = [...line.text];

// // console.log('shaping', line.text);

// // if (!canvasComparer.isLatin(line.text) && !canvasComparer.compareCanvases(line.text, graphemes)) {
// // canvasComparer.mergeStrings(graphemes);
// // }

// textCache[line.text] = [...graphemes];
// }

// const graphemes = [...line.text];

const graphemes = markedStringToParts(line.text);

for (let i = 0; i < graphemes.length; i++) {
// const section = line.getSection(i);
const section = line.getSection(0);
// const sectionIndex = line.getSectionIndex(i);
const sectionIndex = line.getSectionIndex(0);
// const codePoint = line.getCharCode(i) as any;
const codePoint = graphemes[i] as any;
let baselineOffset = 0.0;
let metrics = null;
let rect = null;
let imageName = null;
let verticalAdvance = ONE_EM;
const vertical = !(writingMode === WritingMode.horizontal ||
// Don't verticalize glyphs that have no upright orientation if vertical placement is disabled.
(!allowVerticalPlacement && !charHasUprightVerticalOrientation(codePoint)) ||
// If vertical placement is enabled, don't verticalize glyphs that
// are from complex text layout script, or whitespaces.
(allowVerticalPlacement && (whitespace[codePoint] || charInComplexShapingScript(codePoint))));
// const vertical = !(writingMode === WritingMode.horizontal ||
// // Don't verticalize glyphs that have no upright orientation if vertical placement is disabled.
// (!allowVerticalPlacement && !charHasUprightVerticalOrientation(codePoint)) ||
// // If vertical placement is enabled, don't verticalize glyphs that
// // are from complex text layout script, or whitespaces.
// (allowVerticalPlacement && (whitespace[codePoint] || charInComplexShapingScript(codePoint))));
const vertical = false;

if (!section.imageName) {
const positions = glyphPositions[section.fontStack];
Expand Down Expand Up @@ -714,6 +762,8 @@ function shapeLines(shaping: Shaping,
justifyLine(positionedGlyphs, 0, positionedGlyphs.length - 1, justify, lineOffset);
}

// console.log('shaping', positionedGlyphs);

x = 0;
const currentLineHeight = lineHeight * lineMaxScale + lineOffset;
positionedLine.lineOffset = Math.max(lineOffset, maxLineOffset);
Expand Down
Loading