Skip to content
Snippets Groups Projects
Select Git revision
  • f15f2074cc7bb82c8e1eead38883f35546faa199
  • master default protected
  • rednatco-v2
  • base-pairs-ladder
  • rednatco
  • test
  • ntc-tube-uniform-color
  • ntc-tube-missing-atoms
  • restore-vertex-array-per-program
  • watlas2
  • dnatco_new
  • cleanup-old-nodejs
  • webmmb
  • fix_auth_seq_id
  • update_deps
  • ext_dev
  • ntc_balls
  • nci-2
  • plugin
  • bugfix-0.4.5
  • nci
  • v0.5.0-dev.1
  • v0.4.5
  • v0.4.4
  • v0.4.3
  • v0.4.2
  • v0.4.1
  • v0.4.0
  • v0.3.12
  • v0.3.11
  • v0.3.10
  • v0.3.9
  • v0.3.8
  • v0.3.7
  • v0.3.6
  • v0.3.5
  • v0.3.4
  • v0.3.3
  • v0.3.2
  • v0.3.1
  • v0.3.0
41 results

text-builder.ts

Blame
  • user avatar
    Alexander Rose authored
    f15f2074
    History
    text-builder.ts 7.10 KiB
    /**
     * Copyright (c) 2019 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 { ValueCell } from 'mol-util/value-cell'
    import { ChunkedArray } from 'mol-data/util';
    import { Text } from './text';
    import { getFontAtlas } from './font-atlas';
    
    const quadIndices = new Uint16Array([
        0, 1, 2,
        1, 3, 2
    ])
    
    export interface TextBuilder {
        add(str: string, x: number, y: number, z: number, depth: number, group: number): void
        getText(): Text
    }
    
    export namespace TextBuilder {
        export function create(props: Partial<PD.Values<Text.Params>> = {}, initialCount = 2048, chunkSize = 1024, text?: Text): TextBuilder {
            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 } = p
    
            const fontAtlas = getFontAtlas(p)
            const margin = (1 / 2.5) * backgroundMargin
            const outline = fontAtlas.buffer / fontAtlas.lineHeight
    
            return {
                add: (str: string, x: number, y: number, z: number, depth: number, group: number) => {
                    let xadvance = 0
                    const nChar = str.length
    
                    // calculate width
                    for (let iChar = 0; iChar < nChar; ++iChar) {
                        const c = fontAtlas.get(str[iChar])
                        xadvance += c.nw - 2 * outline
                    }
    
                    // attachment
                    let yShift: number, xShift: number
                    if (attachment.startsWith('top')) {
                        yShift = 1 / 1.25
                    } else if (attachment.startsWith('middle')) {
                        yShift = 1 / 2.5
                    } else {
                        yShift = 0  // "bottom"
                    }
                    if (attachment.endsWith('right')) {
                        xShift = xadvance
                    } else if (attachment.endsWith('center')) {
                        xShift = xadvance / 2
                    } else {
                        xShift = 0  // "left"
                    }
    
                    // background
                    if (background) {
                        ChunkedArray.add2(mappings, -xadvance + xShift - margin - 0.1, yShift + margin) // top left
                        ChunkedArray.add2(mappings, -xadvance + xShift - margin - 0.1, -yShift - margin) // bottom left
                        ChunkedArray.add2(mappings, xadvance - xShift + margin + 0.1, yShift + margin) // top right
                        ChunkedArray.add2(mappings, xadvance - xShift + margin + 0.1, -yShift - margin) // bottom right
    
                        const offset = centers.elementCount
                        for (let i = 0; i < 4; ++i) {
                            ChunkedArray.add2(tcoords, 10, 10)
                            ChunkedArray.add3(centers, x, y, z);
                            ChunkedArray.add(depths, depth);
                            ChunkedArray.add(groups, group);
                        }
                        ChunkedArray.add3(indices, offset + quadIndices[0], offset + quadIndices[1], offset + quadIndices[2])
                        ChunkedArray.add3(indices, offset + quadIndices[3], offset + quadIndices[4], offset + quadIndices[5])
                    }
    
                    xShift += outline
                    yShift += outline
                    xadvance = 0
    
                    for (let iChar = 0; iChar < nChar; ++iChar) {
                        const c = fontAtlas.get(str[iChar])
    
                        ChunkedArray.add2(mappings, xadvance - xShift, c.nh - yShift) // top left
                        ChunkedArray.add2(mappings, xadvance - xShift, -yShift) // bottom left
                        ChunkedArray.add2(mappings, xadvance + c.nw - xShift, c.nh - yShift) // top right
                        ChunkedArray.add2(mappings, xadvance + c.nw - xShift, -yShift) // bottom right
    
                        const texWidth = fontAtlas.texture.width
                        const texHeight = fontAtlas.texture.height
    
                        ChunkedArray.add2(tcoords, c.x / texWidth, c.y / texHeight) // top left
                        ChunkedArray.add2(tcoords, c.x / texWidth, (c.y + c.h) / texHeight) // bottom left
                        ChunkedArray.add2(tcoords, (c.x + c.w) / texWidth, c.y / texHeight) // top right
                        ChunkedArray.add2(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) {
                            ChunkedArray.add3(centers, x, y, z);
                            ChunkedArray.add(depths, depth);
                            ChunkedArray.add(groups, group);
                        }
                        ChunkedArray.add3(indices, offset + quadIndices[0], offset + quadIndices[1], offset + quadIndices[2])
                        ChunkedArray.add3(indices, offset + quadIndices[3], offset + quadIndices[4], offset + quadIndices[5])
                    }
                },
                getText: () => {
                    const ft = fontAtlas.texture
                    const cb = ChunkedArray.compact(centers, true) as Float32Array
                    const mb = ChunkedArray.compact(mappings, true) as Float32Array
                    const db = ChunkedArray.compact(depths, true) as Float32Array
                    const ib = ChunkedArray.compact(indices, true) as Uint32Array
                    const gb = ChunkedArray.compact(groups, true) as Float32Array
                    const tb = ChunkedArray.compact(tcoords, true) as Float32Array
                    return {
                        kind: 'text',
                        charCount: indices.elementCount / 2,
                        fontTexture: text ? ValueCell.update(text.fontTexture, ft) : ValueCell.create(ft),
                        centerBuffer: text ? ValueCell.update(text.centerBuffer, cb) : ValueCell.create(cb),
                        mappingBuffer: text ? ValueCell.update(text.mappingBuffer, mb) : ValueCell.create(mb),
                        depthBuffer: text ? ValueCell.update(text.depthBuffer, db) : ValueCell.create(db),
                        indexBuffer: text ? ValueCell.update(text.indexBuffer, ib) : ValueCell.create(ib),
                        groupBuffer: text ? ValueCell.update(text.groupBuffer, gb) : ValueCell.create(gb),
                        tcoordBuffer: text ? ValueCell.update(text.tcoordBuffer, tb) : ValueCell.create(tb),
                    }
                }
            }
        }
    }