mapbox-gl
Version:
A WebGL interactive maps library
158 lines (116 loc) • 4.86 kB
JavaScript
'use strict';
module.exports = {
shapeText: shapeText,
shapeIcon: shapeIcon
};
// The position of a glyph relative to the text's anchor point.
function PositionedGlyph(codePoint, x, y, glyph) {
this.codePoint = codePoint;
this.x = x;
this.y = y;
this.glyph = glyph;
}
// A collection of positioned glyphs and some metadata
function Shaping(positionedGlyphs, text, top, bottom, left, right) {
this.positionedGlyphs = positionedGlyphs;
this.text = text;
this.top = top;
this.bottom = bottom;
this.left = left;
this.right = right;
}
function shapeText(text, glyphs, maxWidth, lineHeight, horizontalAlign, verticalAlign, justify, spacing, translate) {
var positionedGlyphs = [];
var shaping = new Shaping(positionedGlyphs, text, translate[1], translate[1], translate[0], translate[0]);
// the y offset *should* be part of the font metadata
var yOffset = -17;
var x = translate[0];
var y = translate[1] + yOffset;
for (var i = 0; i < text.length; i++) {
var codePoint = text.charCodeAt(i);
var glyph = glyphs[codePoint];
if (!glyph) continue;
positionedGlyphs.push(new PositionedGlyph(codePoint, x, y, glyph));
x += glyph.advance + spacing;
}
if (!positionedGlyphs.length) return false;
linewrap(shaping, glyphs, lineHeight, maxWidth, horizontalAlign, verticalAlign, justify);
return shaping;
}
var breakable = { 32: true }; // Currently only breaks at regular spaces
function linewrap(shaping, glyphs, lineHeight, maxWidth, horizontalAlign, verticalAlign, justify) {
var lastSafeBreak = null;
var lengthBeforeCurrentLine = 0;
var lineStartIndex = 0;
var line = 0;
var maxLineLength = 0;
var positionedGlyphs = shaping.positionedGlyphs;
if (maxWidth) {
for (var i = 0; i < positionedGlyphs.length; i++) {
var positionedGlyph = positionedGlyphs[i];
positionedGlyph.x -= lengthBeforeCurrentLine;
positionedGlyph.y += lineHeight * line;
if (positionedGlyph.x > maxWidth && lastSafeBreak !== null) {
var lineLength = positionedGlyphs[lastSafeBreak + 1].x;
maxLineLength = Math.max(lineLength, maxLineLength);
for (var k = lastSafeBreak + 1; k <= i; k++) {
positionedGlyphs[k].y += lineHeight;
positionedGlyphs[k].x -= lineLength;
}
if (justify) {
justifyLine(positionedGlyphs, glyphs, lineStartIndex, lastSafeBreak - 1, justify);
}
lineStartIndex = lastSafeBreak + 1;
lastSafeBreak = null;
lengthBeforeCurrentLine += lineLength;
line++;
}
if (breakable[positionedGlyph.codePoint]) {
lastSafeBreak = i;
}
}
}
var lastPositionedGlyph = positionedGlyphs[positionedGlyphs.length - 1];
var lastLineLength = lastPositionedGlyph.x + glyphs[lastPositionedGlyph.codePoint].advance;
maxLineLength = Math.max(maxLineLength, lastLineLength);
var height = (line + 1) * lineHeight;
justifyLine(positionedGlyphs, glyphs, lineStartIndex, positionedGlyphs.length - 1, justify);
align(positionedGlyphs, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, line);
// Calculate the bounding box
shaping.top += -verticalAlign * height;
shaping.bottom = shaping.top + height;
shaping.left += -horizontalAlign * maxLineLength;
shaping.right = shaping.left + maxLineLength;
}
function justifyLine(positionedGlyphs, glyphs, start, end, justify) {
var lastAdvance = glyphs[positionedGlyphs[end].codePoint].advance;
var lineIndent = (positionedGlyphs[end].x + lastAdvance) * justify;
for (var j = start; j <= end; j++) {
positionedGlyphs[j].x -= lineIndent;
}
}
function align(positionedGlyphs, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, line) {
var shiftX = (justify - horizontalAlign) * maxLineLength;
var shiftY = (-verticalAlign * (line + 1) + 0.5) * lineHeight;
for (var j = 0; j < positionedGlyphs.length; j++) {
positionedGlyphs[j].x += shiftX;
positionedGlyphs[j].y += shiftY;
}
}
function shapeIcon(image, layout) {
if (!image || !image.rect) return null;
var dx = layout['icon-offset'][0];
var dy = layout['icon-offset'][1];
var x1 = dx - image.width / 2;
var x2 = x1 + image.width;
var y1 = dy - image.height / 2;
var y2 = y1 + image.height;
return new PositionedIcon(image, y1, y2, x1, x2);
}
function PositionedIcon(image, top, bottom, left, right) {
this.image = image;
this.top = top;
this.bottom = bottom;
this.left = left;
this.right = right;
}