Skip to content

Commit

Permalink
Improve CJK text rendering [#2990] (#3006)
Browse files Browse the repository at this point in the history
* Improve CJK text rendering [#2990]

* Double TinySDF-rendered glyphs with new texScale glyph property
* Change topAdjustment/leftAdjustment to make 2x glyphs match 1x positions

* rename texScale to textureScale [#2990]

* set textureScale to 1 for image SDFs [#2990]

* Simplify implementation to an optional boolean [#2990]

* resolve CHANGELOG.md conflict [#3006]

* move comment [#3006]

* set allowed diff to 0 for localIdeographs [#3006]

* fix duplicate entry in CHANGELOG

* Make local ideographs test font sizes much bigger [#3006]

* add expected images for ubuntu/windows [#3006]
  • Loading branch information
bdon authored Oct 11, 2023
1 parent 8edef2b commit c162f49
Show file tree
Hide file tree
Showing 9 changed files with 41 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### ✨ Features and improvements

- Locally rendered glyphs are double resolution (48px), greatly improving sharpness of CJK text. ([#2990](https://github.com/maplibre/maplibre-gl-js/issues/2990), [#3006](https://github.com/maplibre/maplibre-gl-js/pull/3006))
- _...Add new stuff here..._

### 🐞 Bug fixes
Expand Down
19 changes: 10 additions & 9 deletions src/render/glyph_manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,13 @@ describe('GlyphManager', () => {
manager.getGlyphs({'Arial Unicode MS': [0x3005]}, (err, glyphs) => {
expect(err).toBeFalsy();
expect(glyphs['Arial Unicode MS'][0x3005]).not.toBeNull();
//Request char from Katakana range (te)
//Request char from Katakana range (te)
manager.getGlyphs({'Arial Unicode MS': [0x30C6]}, (err, glyphs) => {
expect(err).toBeFalsy();
const glyph = glyphs['Arial Unicode MS'][0x30c6];
//Ensure that te is locally generated.
expect(glyph.bitmap.height).toBe(6);
expect(glyph.bitmap.width).toBe(6);
expect(glyph.bitmap.height).toBe(12);
expect(glyph.bitmap.width).toBe(12);
done();
});
});
Expand All @@ -110,31 +110,32 @@ describe('GlyphManager', () => {
test('GlyphManager generates CJK PBF locally', done => {
const manager = createGlyphManager('sans-serif');

// character 平
manager.getGlyphs({'Arial Unicode MS': [0x5e73]}, (err, glyphs) => {
expect(err).toBeFalsy();
expect(glyphs['Arial Unicode MS'][0x5e73].metrics.advance).toBe(1);
expect(glyphs['Arial Unicode MS'][0x5e73].metrics.advance).toBe(0.5);
done();
});
});

test('GlyphManager generates Katakana PBF locally', done => {
const manager = createGlyphManager('sans-serif');

// Katakana letter te
// Katakana letter te
manager.getGlyphs({'Arial Unicode MS': [0x30c6]}, (err, glyphs) => {
expect(err).toBeFalsy();
expect(glyphs['Arial Unicode MS'][0x30c6].metrics.advance).toBe(1);
expect(glyphs['Arial Unicode MS'][0x30c6].metrics.advance).toBe(0.5);
done();
});
});

test('GlyphManager generates Hiragana PBF locally', done => {
const manager = createGlyphManager('sans-serif');

//Hiragana letter te
//Hiragana letter te
manager.getGlyphs({'Arial Unicode MS': [0x3066]}, (err, glyphs) => {
expect(err).toBeFalsy();
expect(glyphs['Arial Unicode MS'][0x3066].metrics.advance).toBe(1);
expect(glyphs['Arial Unicode MS'][0x3066].metrics.advance).toBe(0.5);
done();
});
});
Expand All @@ -143,7 +144,7 @@ describe('GlyphManager', () => {

const manager = createGlyphManager('sans-serif');
const drawSpy = GlyphManager.TinySDF.prototype.draw = jest.fn().mockImplementation(() => {
return {data: new Uint8ClampedArray(900)} as any;
return {data: new Uint8ClampedArray(60 * 60)} as any;
});

// Katakana letter te
Expand Down
27 changes: 17 additions & 10 deletions src/render/glyph_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ export class GlyphManager {
return;
}

// Client-generated glyphs are rendered at 2x texture scale,
// because CJK glyphs are more detailed than others.
const textureScale = 2;

let tinySDF = entry.tinySDF;
if (!tinySDF) {
let fontWeight = '400';
Expand All @@ -191,9 +195,9 @@ export class GlyphManager {
fontWeight = '200';
}
tinySDF = entry.tinySDF = new GlyphManager.TinySDF({
fontSize: 24,
buffer: 3,
radius: 8,
fontSize: 24 * textureScale,
buffer: 3 * textureScale,
radius: 8 * textureScale,
cutoff: 0.25,
fontFamily,
fontWeight
Expand All @@ -215,17 +219,20 @@ export class GlyphManager {
* To approximately align TinySDF glyphs with server-provided glyphs, we use this baseline adjustment
* factor calibrated to be in between DIN Pro and Arial Unicode (but closer to Arial Unicode)
*/
const topAdjustment = 27;
const topAdjustment = 27.5;

const leftAdjustment = 0.5;

return {
id,
bitmap: new AlphaImage({width: char.width || 30, height: char.height || 30}, char.data),
bitmap: new AlphaImage({width: char.width || 30 * textureScale, height: char.height || 30 * textureScale}, char.data),
metrics: {
width: char.glyphWidth || 24,
height: char.glyphHeight || 24,
left: char.glyphLeft || 0,
top: char.glyphTop - topAdjustment || -8,
advance: char.glyphAdvance || 24
width: char.glyphWidth / textureScale || 24,
height: char.glyphHeight / textureScale || 24,
left: (char.glyphLeft / textureScale + leftAdjustment) || 0,
top: char.glyphTop / textureScale - topAdjustment || -8,
advance: char.glyphAdvance / textureScale || 24,
isDoubleResolution: true
}
};
}
Expand Down
7 changes: 4 additions & 3 deletions src/style/style_glyph.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import type {AlphaImage} from '../util/image';

/**
* The glyph's metrices
*/
export type GlyphMetrics = {
width: number;
height: number;
left: number;
top: number;
advance: number;
/**
* isDoubleResolution = true for 48px textures
*/
isDoubleResolution?: boolean;
};

/**
Expand Down
6 changes: 4 additions & 2 deletions src/symbol/quads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,12 @@ export function getGlyphQuads(
builtInOffset = [0, 0];
}

const textureScale = positionedGlyph.metrics.isDoubleResolution ? 2 : 1;

const x1 = (positionedGlyph.metrics.left - rectBuffer) * positionedGlyph.scale - halfAdvance + builtInOffset[0];
const y1 = (-positionedGlyph.metrics.top - rectBuffer) * positionedGlyph.scale + builtInOffset[1];
const x2 = x1 + textureRect.w * positionedGlyph.scale / pixelRatio;
const y2 = y1 + textureRect.h * positionedGlyph.scale / pixelRatio;
const x2 = x1 + textureRect.w / textureScale * positionedGlyph.scale / pixelRatio;
const y2 = y1 + textureRect.h / textureScale * positionedGlyph.scale / pixelRatio;

const tl = new Point(x1, y1);
const tr = new Point(x2, y1);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"text-anchor": "top",
"text-field": "{name_ja}{name_en}",
"text-font": ["Open Sans Semibold", "Arial Unicode MS Bold"],
"text-offset": [-10, 0]
"text-size": 30,
"text-offset": [0, -2]
}
},
{
Expand All @@ -48,6 +49,7 @@
"text-anchor": "top",
"text-field": "{name_ch}{name_kr}",
"text-font": ["Open Sans Semibold", "Arial Unicode MS Bold"],
"text-size": 30,
"text-offset": [0, 0]
}
},
Expand All @@ -59,7 +61,8 @@
"text-anchor": "top",
"text-field": "{name_en}{name_ja}",
"text-font": ["Open Sans Semibold", "Arial Unicode MS Bold"],
"text-offset": [10, 0]
"text-size": 30,
"text-offset": [0, 2]
}
}
]
Expand Down

0 comments on commit c162f49

Please sign in to comment.