diff --git a/color-input.js b/color-input.js new file mode 100644 index 0000000..dff394c --- /dev/null +++ b/color-input.js @@ -0,0 +1,60 @@ +const controlGrid = document.getElementById("control-parent"); + +function parseColor(c) +{ + const groups = c.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/i); + if (groups == null) { + throw new Error("invalid color", c); + } else { + return [parseInt(groups[1], 16), parseInt(groups[2], 16), parseInt(groups[3], 16)]; + } +} + +class ColorInput { + constructor(label, defaultValue) + { + const parent = controlGrid; + + const labelE = document.createElement("label"); + labelE.innerHTML = label; + + const inputE = document.createElement("input"); + inputE.type = "color"; + inputE.className = "span-2"; + + const rowE = document.createElement("row"); + const sepE = document.createElement("row-separator"); + + rowE.appendChild(labelE); + rowE.appendChild(inputE); + + parent.appendChild(rowE); + parent.appendChild(sepE); + + this.label = label; + this.input = inputE; + + this.lastValue = undefined; + + const storageValue = localStorage.getItem(this.label); + if (storageValue !== null) { + this.input.value = storageValue; + } else { + this.input.value = defaultValue; + } + + this.update(); + } + + update() + { + const rawValue = this.input.value; + if (rawValue !== this.lastValue) { + this.lastValue = rawValue; + localStorage.setItem(this.label, rawValue); + } + return parseColor(rawValue); + } +}; + +export { ColorInput }; diff --git a/font.js b/font.js index e4428ad..f6cfb1b 100644 --- a/font.js +++ b/font.js @@ -110,6 +110,18 @@ class FontRenderer { } } + font_layout__draw(s) + { + const length = Math.min(s.length, this.maxStringLength - 1); + for (let i = 0; i < length; i++) { + const c = s.charCodeAt(i) % 256; + this.stringBuffer[i] = c; + } + this.stringBuffer[length] = 0; + + this.exports.font_layout__draw(this.fontLayoutAddress, this.stringBufferAddress); + } + constructor(device, canvasFormat, viewUniformBuffer, wgsl, module, fontBufferSrc) { const label = "font"; @@ -127,6 +139,12 @@ class FontRenderer { this.maxGlyphs = 1024; this.fontLayoutAddress = this.exports.font_layout__create(this.fontBufferAddress, this.maxGlyphs); + // text buffer + this.maxStringLength = 1024; + this.stringBufferAddress = this.exports.mem_alloc(this.maxStringLength); + this.stringBuffer = new Uint8Array(this.module.memory.buffer, this.stringBufferAddress); + this.lastString = undefined; + ////////////////////////////////////////////////////////////////////// // shader module ////////////////////////////////////////////////////////////////////// @@ -257,7 +275,7 @@ class FontRenderer { ////////////////////////////////////////////////////////////////////// this.sliders = makeSliders(); - this.sliderArray = new Float32Array(4 * 4); + this.sliderArray = new Float32Array(4 * 6); ////////////////////////////////////////////////////////////////////// // buffers @@ -281,6 +299,21 @@ class FontRenderer { update(device, frameNumber) { + const configuration = updateSliders(this.sliders); + + ////////////////////////////////////////////////////////////////////// + // string draw + ////////////////////////////////////////////////////////////////////// + + if (this.lastString === undefined || this.lastString !== configuration.text) { + this.lastString = configuration.text; + this.font_layout__draw(configuration.text); + } + + ////////////////////////////////////////////////////////////////////// + // layout buffer + ////////////////////////////////////////////////////////////////////// + this.instanceCount = this.exports.font_layout__layout_buffer_index(this.fontLayoutAddress); const layoutBufferSize = this.instanceCount * 4 * 4; @@ -291,8 +324,6 @@ class FontRenderer { // sliders ////////////////////////////////////////////////////////////////////// - const configuration = updateSliders(this.sliders); - this.sliderArray[0] = configuration.mode; this.sliderArray[1] = configuration.scale; this.sliderArray[2] = configuration.translateX; @@ -313,6 +344,14 @@ class FontRenderer { this.sliderArray[14] = configuration.outlineThreshold; this.sliderArray[15] = configuration.outlineScale; + this.sliderArray[16] = configuration.insideColor[0] / 255.0; + this.sliderArray[17] = configuration.insideColor[1] / 255.0; + this.sliderArray[18] = configuration.insideColor[2] / 255.0; + + this.sliderArray[20] = configuration.outlineColor[0] / 255.0; + this.sliderArray[21] = configuration.outlineColor[1] / 255.0; + this.sliderArray[22] = configuration.outlineColor[2] / 255.0; + device.queue.writeBuffer(this.frames[frameNumber].configurationBuffer, 0, this.sliderArray); @@ -342,8 +381,6 @@ async function loadFont(device, canvasFormat, viewUniformBuffer, module) const renderer = new FontRenderer(device, canvasFormat, viewUniformBuffer, fontWgsl, module, liberationBuffer); - module.instance.exports.font_layout__draw(renderer.fontLayoutAddress); - return renderer; } diff --git a/font.wgsl b/font.wgsl index 4bea821..fe7c603 100644 --- a/font.wgsl +++ b/font.wgsl @@ -26,6 +26,9 @@ struct Config { supersampleOffset: f32, outlineThreshold: f32, outlineScale: f32, + + insideColor: vec4f, + outlineColor: vec4f, }; struct GlyphBuffer { @@ -154,8 +157,8 @@ const offsets = array( fn supersampleColor(texture: vec2f, s: vec2f, p: vec2f) -> vec4f { - let insideColor = vec4f(1, 1, 1, 1); - let outlineColor = vec4f(0, 0, 0, 1); + let insideColor = vec4f(config.insideColor.xyz, 1); + let outlineColor = vec4f(config.outlineColor.xyz, 1); let outsideColor = vec4f(0, 0, 0, 0); let dx = dpdx(texture.x * s.x); @@ -223,7 +226,7 @@ fn fragmentMain(input: VertexOutput) -> FragmentOutput var output: FragmentOutput; - if (config.mode == 0) { + if (config.mode == 1) { output.color = subpixelColor(input.texture, s, p); } else { output.color = supersampleColor(input.texture, s, p); diff --git a/fontSliders.js b/fontSliders.js index b8960c3..3155c74 100644 --- a/fontSliders.js +++ b/fontSliders.js @@ -1,12 +1,14 @@ import { Slider } from "./slider.js" import { Select } from "./select.js" import { RowHeader } from "./row-header.js" +import { TextInput } from "./text-input.js" +import { ColorInput } from "./color-input.js" function makeSliders() { const sliders = { subpixelOptions: new RowHeader("global options"), - mode: new Select("mode", ["subpixel", "supersampling"]), + mode: new Select("mode", ["supersampling", "subpixel"]), scale: new Slider("scale", 0.0, 0.01), translateX: new Slider("translate x", -1, 1), translateY: new Slider("translate y", -1, 1), @@ -15,8 +17,10 @@ function makeSliders() distanceThreshold: new Slider("distance threshold", 0.25, 0.75), distanceScale: new Slider("distance scale", 0.0, 20.0), aliasingEnable: new Slider("aliasing enable", 0, 1, 1), - //rampMin: new Slider("ramp min", 0, 1.0), - //rampMax: new Slider("ramp max", 0, 1.0), + + text: new TextInput("text"), + insideColor: new ColorInput("inside color", "#ffffff"), + outlineColor: new ColorInput("outline color", "#000000"), subpixelOptions: new RowHeader("subpixel mode options"), subpixelEnable: new Slider("subpixel enable", 0, 1, 1), diff --git a/index.html b/index.html index 346cef4..e39f8f9 100644 --- a/index.html +++ b/index.html @@ -61,13 +61,22 @@ border-bottom: 2px solid #888 !important; } - select, option { + select, option, input[type="text"] { font: 0.8rem sans-serif; } - input[type="range"] { + + input[type="range"], input[type="text"] { height: 1rem; + } + input[type="color"] { + height: 1.25rem; + } + input[type="range"] { width: calc(100% - 0.5em); } + input[type="text"] { + width: calc(100% - 1em); + } .span-2 { grid-column: span 2; width: calc(100% - 0.5em); diff --git a/src/font_layout/api.cpp b/src/font_layout/api.cpp index 0c0b240..fc460e9 100644 --- a/src/font_layout/api.cpp +++ b/src/font_layout/api.cpp @@ -49,8 +49,9 @@ extern "C" { return font_layout->layout_buffer_index; } - void font_layout__draw(FontLayout * font_layout) + void font_layout__draw(FontLayout * font_layout, char const * s) { - font_layout->draw_string("hexagon grid"); + font_layout->layout_buffer_index = 0; + font_layout->draw_string(s); } }; diff --git a/text-input.js b/text-input.js new file mode 100644 index 0000000..27cc117 --- /dev/null +++ b/text-input.js @@ -0,0 +1,50 @@ +const controlGrid = document.getElementById("control-parent"); + +class TextInput { + constructor(label) + { + const parent = controlGrid; + + const labelE = document.createElement("label"); + labelE.innerHTML = label; + + const inputE = document.createElement("input"); + inputE.type = "text"; + inputE.className = "span-2"; + + const rowE = document.createElement("row"); + const sepE = document.createElement("row-separator"); + + rowE.appendChild(labelE); + rowE.appendChild(inputE); + + parent.appendChild(rowE); + parent.appendChild(sepE); + + this.label = label; + this.input = inputE; + + this.lastValue = undefined; + + const storageValue = localStorage.getItem(this.label); + if (storageValue !== null) { + this.input.value = storageValue; + } else { + this.input.value = "hexagon grid"; + } + + this.update(); + } + + update() + { + const rawValue = this.input.value; + if (rawValue !== this.lastValue) { + this.lastValue = rawValue; + localStorage.setItem(this.label, rawValue); + } + return rawValue; + } +}; + +export { TextInput };