diff --git a/packages/core/src/styles/TextStyle.ts b/packages/core/src/styles/TextStyle.ts index 12e033ad6..6139bb967 100644 --- a/packages/core/src/styles/TextStyle.ts +++ b/packages/core/src/styles/TextStyle.ts @@ -109,6 +109,24 @@ export interface TextStyle { */ text?: string | number | boolean | StyleValueFunction | StyleZoomRange; + /** + * The "textAnchor" attribute is used to align the text relative to the {@link anchor} point. + * + * Possible values: + * - "Left": The text is aligned left to the anchor. + * - "Right": The text is aligned right to the anchor. + * - "Center": The center of the Text is placed at the {@link anchor} point. + * - "Top": The top of the Text is placed closest the {@link anchor} point. + * - "TopLeft": The top left side of the Text is placed closest the {@link anchor} point. + * - "TopRight": The top right side of the Text is placed closest the {@link anchor} point. + * - "Bottom": The bottom of the Text is placed closest to the {@link anchor} point. + * - "BottomLeft": The bottom left side of the Text is placed closest the {@link anchor} point. + * - "BottomRight": The bottom right side of the Text is placed closest the {@link anchor} point. + * + * @defaultValue "Center" + */ + textAnchor?: 'Left' | 'Center' | 'Right' | 'Top' | 'TopLeft' | 'TopRight' | 'Bottom' | 'BottomLeft' | 'BottomRight' + /** * "textRef" Reference to an attribute of an feature that's value should be displayed as text. * If both "text" and "textRef" are set, "text" prevails. @@ -121,7 +139,7 @@ export interface TextStyle { * ``` * @example * ```typescript - * // display the id of the featurre + * // display the id of the feature * ... * textRef: "id" * ``` @@ -221,7 +239,7 @@ export interface TextStyle { * * @defaultValue For Polygon geometry the default is "Center". For Line geometry the default for styles of type "Text" is "Line". */ - anchor?: 'Line' | 'Coordinate' | 'Centroid' + anchor?: 'Line' | 'Coordinate' | 'Centroid' | 'Center' /** * Enable or disable the space check for point styles on line geometries. diff --git a/packages/display/src/displays/webgl/buffer/FeatureFactory.ts b/packages/display/src/displays/webgl/buffer/FeatureFactory.ts index 77359fb07..e87c39d1b 100644 --- a/packages/display/src/displays/webgl/buffer/FeatureFactory.ts +++ b/packages/display/src/displays/webgl/buffer/FeatureFactory.ts @@ -37,11 +37,7 @@ import {PointBuffer} from './templates/PointBuffer'; import {PolygonBuffer} from './templates/PolygonBuffer'; import {ExtrudeBuffer} from './templates/ExtrudeBuffer'; import {toPresentationFormB} from '../arabic'; -import { - Tile, - Feature, - GeoJSONCoordinate -} from '@here/xyz-maps-core'; +import {Tile, Feature, GeoJSONCoordinate, TextStyle} from '@here/xyz-maps-core'; import {TemplateBuffer} from './templates/TemplateBuffer'; import {addVerticalLine} from './addVerticalLine'; import {BoxBuffer} from './templates/BoxBuffer'; @@ -178,7 +174,8 @@ export class FeatureFactory { rotationZ: number = 0, rotationY: number | undefined, text?: string, - defaultLineWrap?: number | boolean + defaultLineWrap?: number | boolean, + textAnchor?: TextStyle['textAnchor'] ) { const isFlat = z === null; const level = this.z; @@ -205,11 +202,8 @@ export class FeatureFactory { texture.addChars(text); const fontInfo = texture.getAtlas(); - let lineWrap = getValue('lineWrap', style, feature, level); + let lineWrap = getValue('lineWrap', style, feature, level) ?? defaultLineWrap; - if (lineWrap == UNDEF) { - lineWrap = defaultLineWrap; - } const lines = wrapText(text, lineWrap); positionBuffer = flexAttributes.a_position; @@ -226,7 +220,8 @@ export class FeatureFactory { flexAttributes.a_texcoord.data, fontInfo, rotationZ, - rotationY + rotationY, + textAnchor ); } else { if (type == 'Model') { @@ -291,13 +286,13 @@ export class FeatureFactory { x, y, z, - img, - width, - height, - flexAttributes.a_size.data, - positionBuffer.data, - flexAttributes.a_texcoord.data, - rotationZ + img, + width, + height, + flexAttributes.a_size.data, + positionBuffer.data, + flexAttributes.a_texcoord.data, + rotationZ ); groupBuffer.addUniform('u_texture', this.icons.getTexture()); // group.texture = this.icons.getTexture(); @@ -400,7 +395,7 @@ export class FeatureFactory { let alignment; let sizeUnit; let offsetUnit; - let collisionGroups: {[key:string]: CollisionGroup} = {}; + let collisionGroups: { [key: string]: CollisionGroup } = {}; let collisionData; this.lineFactory.initFeature(level, tileSize, collisionGroup?.id); @@ -421,10 +416,9 @@ export class FeatureFactory { if (opacity === 0) continue; - let collide = - geomType == 'Polygon' - ? true // no collision detection support for polygons - : getValue('collide', style, feature, level); + let collide = geomType == 'Polygon' + ? true // no collision detection support for polygons + : getValue('collide', style, feature, level); if (priority == UNDEF && ((type == 'Text' && !collide) || collide === false)) { let collisionGroupId = getValue('collisionGroup', style, feature, level) || DEFAULT_COLLISION_GRP; @@ -463,7 +457,7 @@ export class FeatureFactory { if ( opacity == UNDEF || - opacity >= 0.98 // no alpha visible -> no need to use more expensive alpha pass + opacity >= 0.98 // no alpha visible -> no need to use more expensive alpha pass ) { opacity = 1; } @@ -544,13 +538,13 @@ export class FeatureFactory { [offsetX, offsetUnit] = parseSizeValue(offset); groupId = - (altitude ? 'AL' : 'L') + - sizeUnit + - offsetX + - offsetUnit + - strokeLinecap + - strokeLinejoin + - (strokeDasharray || NONE); + (altitude ? 'AL' : 'L') + + sizeUnit + + offsetX + + offsetUnit + + strokeLinecap + + strokeLinejoin + + (strokeDasharray || NONE); } else { fill = getValue('fill', style, feature, level); @@ -801,34 +795,36 @@ export class FeatureFactory { collisionGroup = null; } - this.createPoint(type, group, x, y, z, style, feature, collisionData, rotation, UNDEF, text); + this.createPoint(type, group, x, y, z, style, feature, collisionData, rotation, UNDEF, text, UNDEF, + type == 'Text' && getValue('textAnchor', style, feature, level) + ); } } else if (geomType == 'LineString') { if (type == 'Line') { let vertexLength = this.lineFactory.createLine( - coordinates, - group, - tile, - tileSize, - removeTileBounds, - strokeDasharray, - strokeLinecap, - strokeLinejoin, - strokeWidth, - altitude, - offsetX, - getValue('from', style, feature, level), - getValue('to', style, feature, level) + coordinates, + group, + tile, + tileSize, + removeTileBounds, + strokeDasharray, + strokeLinecap, + strokeLinejoin, + strokeWidth, + altitude, + offsetX, + getValue('from', style, feature, level), + getValue('to', style, feature, level) ); group.buffer.setIdOffset(feature.id); } else { + let isText = type == 'Text'; let anchor = getValue('anchor', style, feature, level); - if (anchor == UNDEF) { - anchor = type == 'Text' ? 'Line' : 'Coordinate'; - } + anchor ??= isText ? 'Line' : 'Coordinate'; - const checkCollisions = type == 'Text' ? !collide : collide === false; + const textAnchor = isText && getValue('textAnchor', style, feature, level); + const checkCollisions = isText ? !collide : collide === false; let w; let h; @@ -876,36 +872,37 @@ export class FeatureFactory { } this.lineFactory.placeAtSegments( - coordinates, - altitude, - tile, - tileSize, - checkCollisions && this.collisions, - priority, - getValue('repeat', style, feature, level), - offsetX, - offsetY, - w, - h, - applyRotation, - checkLineSpace, - from, to, - (x, y, z, rotationZ, rotationY, collisionData) => { - this.createPoint( - type, - group, - x, - y, - z, - style, - feature, - collisionData, - rotationZ + rotation, - rotationY, - text, - false - ); - } + coordinates, + altitude, + tile, + tileSize, + checkCollisions && this.collisions, + priority, + getValue('repeat', style, feature, level), + offsetX, + offsetY, + w, + h, + applyRotation, + checkLineSpace, + from, to, + (x, y, z, rotationZ, rotationY, collisionData) => { + this.createPoint( + type, + group, + x, + y, + z, + style, + feature, + collisionData, + rotationZ + rotation, + rotationY, + text, + false, + textAnchor + ); + } ); } else { if (collisionGroup) { @@ -917,21 +914,21 @@ export class FeatureFactory { } this.lineFactory.placeAtPoints( - coordinates, - altitude, - tile, - tileSize, - checkCollisions && this.collisions, - priority, - w, - h, - offsetX, - offsetY, - from, - to, - (x, y, z, rotZ, rotY, collisionData) => { - this.createPoint(type, group, x, y, z, style, feature, collisionData, rotZ + rotation, UNDEF, text); - } + coordinates, + altitude, + tile, + tileSize, + checkCollisions && this.collisions, + priority, + w, + h, + offsetX, + offsetY, + from, + to, + (x, y, z, rotZ, rotY, collisionData) => { + this.createPoint(type, group, x, y, z, style, feature, collisionData, rotZ + rotation, UNDEF, text, UNDEF, textAnchor); + } ); } } @@ -960,12 +957,12 @@ export class FeatureFactory { aPosition, (group.buffer as ExtrudeBuffer).flexAttributes.a_normal.data, vIndex, - coordinates, - tile, - tileSize, - extrude, - extrudeBase, - strokeIndex + coordinates, + tile, + tileSize, + extrude, + extrudeBase, + strokeIndex ); } else if (type == 'Polygon') { flatPoly = addPolygon(aPosition, coordinates, tile, tileSize); @@ -1025,8 +1022,10 @@ export class FeatureFactory { collisionGrp.height = halfHeight; this.pendingCollisions.push(collisionGrp); - }; + } + ; } + destroy() { this.modelFactory.destroy(); } diff --git a/packages/display/src/displays/webgl/buffer/addText.ts b/packages/display/src/displays/webgl/buffer/addText.ts index 781936c5a..d220d9d60 100644 --- a/packages/display/src/displays/webgl/buffer/addText.ts +++ b/packages/display/src/displays/webgl/buffer/addText.ts @@ -20,9 +20,22 @@ import {createTextData, OFFSET_SCALE} from './createText'; import {GlyphAtlas} from '../GlyphAtlas'; import {FlexArray} from './templates/FlexArray'; +import {TextStyle} from '@here/xyz-maps-core'; const EXTENT_SCALE = 64; +const ANCHOR_OFFSET: Record = { + Center: {x: .5, y: 0}, + Left: {x: 0, y: 0}, + Right: {x: 1, y: 0}, + Top: {x: .5, y: -1}, + TopLeft: {x: 0, y: -1}, + TopRight: {x: 1, y: -1}, + Bottom: {x: .5, y: 1}, + BottomLeft: {x: 0, y: 1}, + BottomRight: {x: 1, y: 1} +}; + const addText = ( cx: number, cy: number, @@ -33,12 +46,13 @@ const addText = ( textureCoordinates: FlexArray, glyphAtlas: GlyphAtlas, rotationZ = 0, - rotationY: number | undefined + rotationY: number | undefined, + textAnchor: TextStyle['textAnchor'] | string = 'Center' ) => { - const lineCnt = lines.length; + const lineOffset = lines.length - 1; const lineHeight = glyphAtlas.lineHeight; - - let ty = (glyphAtlas.baselineOffset + (lineCnt - 1) * lineHeight * .5) * OFFSET_SCALE; + const anchorOffset = ANCHOR_OFFSET[textAnchor] || ANCHOR_OFFSET.Center; + let ty = (glyphAtlas.baselineOffset + lineHeight * (lineOffset * .5 + anchorOffset.y * (lineOffset || 1))) * OFFSET_SCALE; // LSB defines visibility, visible by default cx = cx * EXTENT_SCALE << 1 | 1; @@ -56,17 +70,16 @@ const addText = ( // } if (hasHeight) { - // normalize float meters to uint16 (0m ... +9000m) + // normalize float meters to uint16 (0m ... +9000m) z = Math.round(z / 9000 * 0xffff); dim = 3; } - let i = vertex.length / dim; for (let text of lines) { const textData = createTextData(text, glyphAtlas, offsets, textureCoordinates, rotationZ, rotationY); - const tx = textData.width * glyphAtlas.scale / 2 * OFFSET_SCALE; + const tx = textData.width * anchorOffset.x * glyphAtlas.scale * OFFSET_SCALE; const vertexCnt = textData.count * dim; vertex.reserve(vertexCnt);