UNPKG

molstar

Version:

A comprehensive macromolecular library.

278 lines (277 loc) 13.9 kB
/** * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { ChunkedArray } from '../../../mol-data/util'; import { Text } from './text'; import { getFontAtlas } from './font-atlas'; import { assertUnreachable } from '../../../mol-util/type-helpers'; const quadIndices = new Uint16Array([ 0, 1, 2, 1, 3, 2 ]); // avoiding namespace lookup improved performance in Chrome (Aug 2020) const caAdd3 = ChunkedArray.add3; const caAdd2 = ChunkedArray.add2; const caAdd = ChunkedArray.add; export var TextBuilder; (function (TextBuilder) { function create(props = {}, initialCount = 2048, chunkSize = 1024, text) { initialCount *= 2; chunkSize *= 2; const centers = ChunkedArray.create(Float32Array, 3, chunkSize, text ? text.centerBuffer.ref.value : initialCount); const mappings = ChunkedArray.create(Float32Array, 2, chunkSize, text ? text.mappingBuffer.ref.value : initialCount); const depths = ChunkedArray.create(Float32Array, 1, chunkSize, text ? text.depthBuffer.ref.value : initialCount); const indices = ChunkedArray.create(Uint32Array, 3, chunkSize, text ? text.indexBuffer.ref.value : initialCount); const groups = ChunkedArray.create(Float32Array, 1, chunkSize, text ? text.groupBuffer.ref.value : initialCount); const tcoords = ChunkedArray.create(Float32Array, 2, chunkSize, text ? text.tcoordBuffer.ref.value : initialCount); const p = { ...PD.getDefaultValues(Text.Params), ...props }; const { attachment, background, backgroundMargin, tether, tetherLength, tetherBaseWidth } = p; const fontAtlas = getFontAtlas(p); const margin = (1 / 2.5) * backgroundMargin; const outline = fontAtlas.buffer / fontAtlas.lineHeight; const add = (x, y, z, depth, group) => { caAdd3(centers, x, y, z); caAdd(depths, depth); caAdd(groups, group); }; return { add: (str, x, y, z, depth, scale, group) => { let bWidth = 0; const nChar = str.length; // calculate width for (let iChar = 0; iChar < nChar; ++iChar) { const c = fontAtlas.get(str[iChar]); bWidth += c.nw - 2 * outline; } const bHeight = 1 / 1.25; // attachment let yShift, xShift; // vertical if (attachment.startsWith('top')) { yShift = bHeight; } else if (attachment.startsWith('middle')) { yShift = bHeight / 2; } else { yShift = 0; // "bottom" } // horizontal if (attachment.endsWith('right')) { xShift = bWidth; } else if (attachment.endsWith('center')) { xShift = bWidth / 2; } else { xShift = 0; // "left" } if (tether) { switch (attachment) { case 'bottom-left': xShift -= tetherLength / 2 + margin + 0.1; yShift -= tetherLength / 2 + margin; break; case 'bottom-center': yShift -= tetherLength + margin; break; case 'bottom-right': xShift += tetherLength / 2 + margin + 0.1; yShift -= tetherLength / 2 + margin; break; case 'middle-left': xShift -= tetherLength + margin + 0.1; break; case 'middle-center': break; case 'middle-right': xShift += tetherLength + margin + 0.1; break; case 'top-left': xShift -= tetherLength / 2 + margin + 0.1; yShift += tetherLength / 2 + margin; break; case 'top-center': yShift += tetherLength + margin; break; case 'top-right': xShift += tetherLength / 2 + margin + 0.1; yShift += tetherLength / 2 + margin; break; } } const xLeft = (-xShift - margin - 0.1) * scale; const xRight = (bWidth - xShift + margin + 0.1) * scale; const yTop = (bHeight - yShift + margin) * scale; const yBottom = (-yShift - margin) * scale; // background if (background) { caAdd2(mappings, xLeft, yTop); // top left caAdd2(mappings, xLeft, yBottom); // bottom left caAdd2(mappings, xRight, yTop); // top right caAdd2(mappings, xRight, yBottom); // bottom right const offset = centers.elementCount; for (let i = 0; i < 4; ++i) { caAdd2(tcoords, 10, 10); add(x, y, z, depth, group); } caAdd3(indices, offset + quadIndices[0], offset + quadIndices[1], offset + quadIndices[2]); caAdd3(indices, offset + quadIndices[3], offset + quadIndices[4], offset + quadIndices[5]); } if (tether) { let xTip, yTip; let xBaseA, yBaseA; let xBaseB, yBaseB; let xBaseCenter, yBaseCenter; const scaledTetherLength = tetherLength * scale; const scaledTetherBaseWidth = tetherBaseWidth * scale; switch (attachment) { case 'bottom-left': xTip = xLeft - scaledTetherLength / 2; xBaseA = xLeft + scaledTetherBaseWidth / 2; xBaseB = xLeft; xBaseCenter = xLeft; yTip = yBottom - scaledTetherLength / 2; yBaseA = yBottom; yBaseB = yBottom + scaledTetherBaseWidth / 2; yBaseCenter = yBottom; break; case 'bottom-center': xTip = 0; xBaseA = scaledTetherBaseWidth / 2; xBaseB = -scaledTetherBaseWidth / 2; xBaseCenter = 0; yTip = yBottom - scaledTetherLength; yBaseA = yBottom; yBaseB = yBottom; yBaseCenter = yBottom; break; case 'bottom-right': xTip = xRight + scaledTetherLength / 2; xBaseA = xRight; xBaseB = xRight - scaledTetherBaseWidth / 2; xBaseCenter = xRight; yTip = yBottom - scaledTetherLength / 2; yBaseA = yBottom + scaledTetherBaseWidth / 2; yBaseB = yBottom; yBaseCenter = yBottom; break; case 'middle-left': xTip = xLeft - scaledTetherLength; xBaseA = xLeft; xBaseB = xLeft; xBaseCenter = xLeft; yTip = 0; yBaseA = -scaledTetherBaseWidth / 2; yBaseB = scaledTetherBaseWidth / 2; yBaseCenter = 0; break; case 'middle-center': xTip = 0; xBaseA = 0; xBaseB = 0; xBaseCenter = 0; yTip = 0; yBaseA = 0; yBaseB = 0; yBaseCenter = 0; break; case 'middle-right': xTip = xRight + scaledTetherLength; xBaseA = xRight; xBaseB = xRight; xBaseCenter = xRight; yTip = 0; yBaseA = scaledTetherBaseWidth / 2; yBaseB = -scaledTetherBaseWidth / 2; yBaseCenter = 0; break; case 'top-left': xTip = xLeft - scaledTetherLength / 2; xBaseA = xLeft + scaledTetherBaseWidth / 2; xBaseB = xLeft; xBaseCenter = xLeft; yTip = yTop + scaledTetherLength / 2; yBaseA = yTop; yBaseB = yTop - scaledTetherBaseWidth / 2; yBaseCenter = yTop; break; case 'top-center': xTip = 0; xBaseA = scaledTetherBaseWidth / 2; xBaseB = -scaledTetherBaseWidth / 2; xBaseCenter = 0; yTip = yTop + scaledTetherLength; yBaseA = yTop; yBaseB = yTop; yBaseCenter = yTop; break; case 'top-right': xTip = xRight + scaledTetherLength / 2; xBaseA = xRight; xBaseB = xRight - scaledTetherBaseWidth / 2; xBaseCenter = xRight; yTip = yTop + scaledTetherLength / 2; yBaseA = yTop - scaledTetherBaseWidth / 2; yBaseB = yTop; yBaseCenter = yTop; break; default: assertUnreachable(attachment); } caAdd2(mappings, xTip, yTip); // tip caAdd2(mappings, xBaseA, yBaseA); // base A caAdd2(mappings, xBaseB, yBaseB); // base B caAdd2(mappings, xBaseCenter, yBaseCenter); // base center const offset = centers.elementCount; for (let i = 0; i < 4; ++i) { caAdd2(tcoords, 10, 10); add(x, y, z, depth, group); } caAdd3(indices, offset, offset + 1, offset + 3); caAdd3(indices, offset, offset + 3, offset + 2); } xShift += outline; yShift += outline; let xadvance = 0; for (let iChar = 0; iChar < nChar; ++iChar) { const c = fontAtlas.get(str[iChar]); const left = (xadvance - xShift) * scale; const right = (xadvance + c.nw - xShift) * scale; const top = (c.nh - yShift) * scale; const bottom = (-yShift) * scale; caAdd2(mappings, left, top); caAdd2(mappings, left, bottom); caAdd2(mappings, right, top); caAdd2(mappings, right, bottom); const texWidth = fontAtlas.texture.width; const texHeight = fontAtlas.texture.height; caAdd2(tcoords, c.x / texWidth, c.y / texHeight); // top left caAdd2(tcoords, c.x / texWidth, (c.y + c.h) / texHeight); // bottom left caAdd2(tcoords, (c.x + c.w) / texWidth, c.y / texHeight); // top right caAdd2(tcoords, (c.x + c.w) / texWidth, (c.y + c.h) / texHeight); // bottom right xadvance += c.nw - 2 * outline; const offset = centers.elementCount; for (let i = 0; i < 4; ++i) add(x, y, z, depth, group); caAdd3(indices, offset + quadIndices[0], offset + quadIndices[1], offset + quadIndices[2]); caAdd3(indices, offset + quadIndices[3], offset + quadIndices[4], offset + quadIndices[5]); } }, getText: () => { const ft = fontAtlas.texture; const cb = ChunkedArray.compact(centers, true); const mb = ChunkedArray.compact(mappings, true); const db = ChunkedArray.compact(depths, true); const ib = ChunkedArray.compact(indices, true); const gb = ChunkedArray.compact(groups, true); const tb = ChunkedArray.compact(tcoords, true); return Text.create(ft, cb, mb, db, ib, gb, tb, indices.elementCount / 2, text); } }; } TextBuilder.create = create; })(TextBuilder || (TextBuilder = {}));