UNPKG

mapbox-gl

Version:
180 lines (136 loc) 5.49 kB
'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 = 0; var y = 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, translate); return shaping; } var invisible = { 0x20: true, // space 0x200b: true // zero-width space }; var breakable = { 0x20: true, // space 0x26: true, // ampersand 0x2b: true, // plus sign 0x2d: true, // hyphen-minus 0x2f: true, // solidus 0xad: true, // soft hyphen 0xb7: true, // middle dot 0x200b: true, // zero-width space 0x2010: true, // hyphen 0x2013: true // en dash }; function linewrap(shaping, glyphs, lineHeight, maxWidth, horizontalAlign, verticalAlign, justify, translate) { 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) { // Collapse invisible characters. var lineEnd = lastSafeBreak; if (invisible[positionedGlyphs[lastSafeBreak].codePoint]) { lineEnd--; } justifyLine(positionedGlyphs, glyphs, lineStartIndex, lineEnd, 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, translate); // 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, translate) { var shiftX = (justify - horizontalAlign) * maxLineLength + translate[0]; var shiftY = (-verticalAlign * (line + 1) + 0.5) * lineHeight + translate[1]; 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; }