diff --git a/src/symbol/quads.js b/src/symbol/quads.js index 62e244bd56c..c1120354d9c 100644 --- a/src/symbol/quads.js +++ b/src/symbol/quads.js @@ -153,8 +153,10 @@ function getIconQuads(anchor, shapedIcon, boxScale, line, layer, alongLine, shap */ function getGlyphQuads(anchor, shaping, boxScale, line, layer, alongLine, globalProperties, featureProperties) { + const oneEm = 24; const textRotate = layer.getLayoutValue('text-rotate', globalProperties, featureProperties) * Math.PI / 180; const keepUpright = layer.layout['text-keep-upright']; + const textOffset = layer.getLayoutValue('text-offset', globalProperties, featureProperties).map((t)=> t * oneEm); const positionedGlyphs = shaping.positionedGlyphs; const quads = []; @@ -189,32 +191,23 @@ function getGlyphQuads(anchor, shaping, boxScale, line, layer, alongLine, global }]; } - const x1 = positionedGlyph.x + glyph.left; - const y1 = positionedGlyph.y - glyph.top; - const x2 = x1 + rect.w; - const y2 = y1 + rect.h; - - const center = new Point(positionedGlyph.x, glyph.advance / 2); - - const otl = new Point(x1, y1); - const otr = new Point(x2, y1); - const obl = new Point(x1, y2); - const obr = new Point(x2, y2); - - if (positionedGlyph.angle !== 0) { - otl._sub(center)._rotate(positionedGlyph.angle)._add(center); - otr._sub(center)._rotate(positionedGlyph.angle)._add(center); - obl._sub(center)._rotate(positionedGlyph.angle)._add(center); - obr._sub(center)._rotate(positionedGlyph.angle)._add(center); - } + const baseQuad = { + upright: calculateBaseQuad(positionedGlyph, glyph, rect, textOffset), + // The quad coordinates represent an offset from the anchor. Since + // we use the same anchor for both the 'upright' and 'upside-down' + // copies of each glyph, invert the y dimension of text-offset for the + // upside-down case. + upsideDown: calculateBaseQuad(positionedGlyph, glyph, rect, [textOffset[0], -textOffset[1]]) + }; for (let i = 0; i < glyphInstances.length; i++) { const instance = glyphInstances[i]; - let tl = otl, - tr = otr, - bl = obl, - br = obr; + const base = baseQuad[instance.upsideDown ? 'upsideDown' : 'upright']; + let tl = base.tl, + tr = base.tr, + bl = base.bl, + br = base.br; if (textRotate) { const sin = Math.sin(textRotate), @@ -240,6 +233,30 @@ function getGlyphQuads(anchor, shaping, boxScale, line, layer, alongLine, global return quads; } +function calculateBaseQuad(positionedGlyph, glyph, rect, offset) { + const x1 = positionedGlyph.x + glyph.left + offset[0]; + const y1 = positionedGlyph.y - glyph.top + offset[1]; + const x2 = x1 + rect.w; + const y2 = y1 + rect.h; + + const center = new Point(positionedGlyph.x, glyph.advance / 2); + + const tl = new Point(x1, y1); + const tr = new Point(x2, y1); + const bl = new Point(x1, y2); + const br = new Point(x2, y2); + + if (positionedGlyph.angle !== 0) { + tl._sub(center)._rotate(positionedGlyph.angle)._add(center); + tr._sub(center)._rotate(positionedGlyph.angle)._add(center); + bl._sub(center)._rotate(positionedGlyph.angle)._add(center); + br._sub(center)._rotate(positionedGlyph.angle)._add(center); + } + + return { tl, tr, bl, br }; +} + + /** * We can only render glyph quads that slide along a straight line. To draw * curved lines we need an instance of a glyph for each segment it appears on. diff --git a/src/symbol/shaping.js b/src/symbol/shaping.js index 20076ea44bc..74410422ff5 100644 --- a/src/symbol/shaping.js +++ b/src/symbol/shaping.js @@ -275,7 +275,7 @@ function shapeLines(shaping, glyphs, lines, lineHeight, horizontalAlign, vertica y += lineHeight; } - align(positionedGlyphs, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, lines.length, translate); + align(positionedGlyphs, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, lines.length); // Calculate the bounding box const height = lines.length * lineHeight; @@ -299,9 +299,9 @@ function justifyLine(positionedGlyphs, glyphs, start, end, justify) { } } -function align(positionedGlyphs, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, lineCount, translate) { - const shiftX = (justify - horizontalAlign) * maxLineLength + translate[0]; - const shiftY = (-verticalAlign * lineCount + 0.5) * lineHeight + translate[1]; +function align(positionedGlyphs, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, lineCount) { + const shiftX = (justify - horizontalAlign) * maxLineLength; + const shiftY = (-verticalAlign * lineCount + 0.5) * lineHeight; for (let j = 0; j < positionedGlyphs.length; j++) { positionedGlyphs[j].x += shiftX; diff --git a/test/integration/render-tests/text-keep-upright/line-placement-true-offset/expected.png b/test/integration/render-tests/text-keep-upright/line-placement-true-offset/expected.png new file mode 100644 index 00000000000..3c6dfef6d7d Binary files /dev/null and b/test/integration/render-tests/text-keep-upright/line-placement-true-offset/expected.png differ diff --git a/test/integration/render-tests/text-keep-upright/line-placement-true-offset/style.json b/test/integration/render-tests/text-keep-upright/line-placement-true-offset/style.json new file mode 100644 index 00000000000..b587263ccf1 --- /dev/null +++ b/test/integration/render-tests/text-keep-upright/line-placement-true-offset/style.json @@ -0,0 +1,58 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 64, + "operations": [ + [ "setBearing", 20 ], + [ "wait" ] + ] + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [-20, -20], + [20, -20], + [20, 20], + [-20, 20] + ] + } + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "guid", + "type": "line", + "source": "geojson" + }, + { + "id": "text", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-placement": "line", + "symbol-spacing": 3, + "text-allow-overlap": true, + "text-ignore-placement": true, + "text-field": "A", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-keep-upright": true, + "text-offset": [0, -1] + } + } + ] +} diff --git a/test/integration/render-tests/text-keep-upright/line-placement-true-text-anchor/expected.png b/test/integration/render-tests/text-keep-upright/line-placement-true-text-anchor/expected.png new file mode 100644 index 00000000000..11358a8fb5c Binary files /dev/null and b/test/integration/render-tests/text-keep-upright/line-placement-true-text-anchor/expected.png differ diff --git a/test/integration/render-tests/text-keep-upright/line-placement-true-text-anchor/style.json b/test/integration/render-tests/text-keep-upright/line-placement-true-text-anchor/style.json new file mode 100644 index 00000000000..7db2bbf2867 --- /dev/null +++ b/test/integration/render-tests/text-keep-upright/line-placement-true-text-anchor/style.json @@ -0,0 +1,128 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 64, + "operations": [ + [ "setBearing", 20 ], + [ "wait" ] + ] + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [-10, -10], + [10, -10], + [10, 10], + [-10, 10] + ] + } + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "guid", + "type": "line", + "source": "geojson" + }, + { + "id": "left", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-placement": "line", + "symbol-spacing": 50, + "text-size": 10, + "text-allow-overlap": true, + "text-ignore-placement": true, + "text-field": "A", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-keep-upright": true, + "text-anchor": "left" + }, + "paint": { + "text-color": "red" + } + }, + { + "id": "right", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-placement": "line", + "symbol-spacing": 50, + "text-size": 10, + "text-allow-overlap": true, + "text-ignore-placement": true, + "text-field": "A", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-keep-upright": true, + "text-anchor": "right" + }, + "paint": { + "text-color": "blue" + } + }, + { + "id": "top", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-placement": "line", + "symbol-spacing": 50, + "text-size": 10, + "text-allow-overlap": true, + "text-ignore-placement": true, + "text-field": "A", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-keep-upright": true, + "text-anchor": "top" + }, + "paint": { + "text-color": "green" + } + }, + { + "id": "bottom", + "type": "symbol", + "source": "geojson", + "layout": { + "symbol-placement": "line", + "symbol-spacing": 50, + "text-size": 10, + "text-allow-overlap": true, + "text-ignore-placement": true, + "text-field": "A", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-keep-upright": true, + "text-anchor": "bottom" + }, + "paint": { + "text-color": "black" + } + } + ] +}