471 lines
No EOL
25 KiB
C++
471 lines
No EOL
25 KiB
C++
/*
|
|
Crafter®.Graphics
|
|
Copyright (C) 2026 Catcrafts®
|
|
catcrafts.net
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License version 3.0 as published by the Free Software Foundation;
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
module;
|
|
#include "../lib/stb_truetype.h"
|
|
export module Crafter.Graphics:RenderingElement2D;
|
|
import Crafter.Asset;
|
|
import std;
|
|
import :Transform2D;
|
|
import :RenderingElement2DBase;
|
|
import :Font;
|
|
import :Types;
|
|
import :Window;
|
|
|
|
export namespace Crafter {
|
|
template<typename T, bool Scaling, bool Owning, bool Rotating, std::uint8_t Alignment = 0, std::uint8_t Frames = 1> requires ((!Rotating || Scaling) && (!Owning || Scaling))
|
|
struct RenderingElement2D : RenderingElement2DBase<T, Frames>, ScalingBase<T, Scaling, Owning, Alignment>, RotatingBase<Rotating> {
|
|
RenderingElement2D() = default;
|
|
RenderingElement2D(Anchor2D anchor, OpaqueType opaque) : RenderingElement2DBase<T, Frames>(anchor, opaque) {
|
|
|
|
}
|
|
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t rotation) requires(Rotating) : RenderingElement2DBase<T, Frames>(anchor, opaque), RotatingBase<Rotating>(rotation) {
|
|
|
|
}
|
|
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, Vector<T, 4, Alignment>* scalingBuffer) requires(Scaling && !Owning) : RenderingElement2DBase<T, Frames>(anchor, opaque), ScalingBase<T, Scaling, Owning, Alignment>(bufferWidth, bufferHeight, scalingBuffer) {
|
|
|
|
}
|
|
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, Vector<T, 4, Alignment>* scalingBuffer, std::uint32_t rotation) requires(Scaling && !Owning && Rotating) : RenderingElement2DBase<T, Frames>(anchor, opaque), ScalingBase<T, Scaling, Owning, Alignment>(bufferWidth, bufferHeight, scalingBuffer), RotatingBase<Rotating>(rotation) {
|
|
|
|
}
|
|
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight) requires(Owning) : RenderingElement2DBase<T, Frames>(anchor, opaque), ScalingBase<T, Scaling, Owning, Alignment>(bufferWidth, bufferHeight) {
|
|
|
|
}
|
|
RenderingElement2D(Anchor2D anchor, OpaqueType opaque, std::uint32_t bufferWidth, std::uint32_t bufferHeight, std::uint32_t rotation) requires(Owning && Rotating) : RenderingElement2DBase<T, Frames>(anchor, opaque), ScalingBase<T, Scaling, Owning, Alignment>(bufferWidth, bufferHeight) , RotatingBase<Rotating>(rotation) {
|
|
|
|
}
|
|
RenderingElement2D(Anchor2D anchor, TextureAsset<Vector<T, 4, Alignment>>& texture) requires(!Owning && Scaling) : RenderingElement2DBase<T, Frames>(anchor, texture.opaque), ScalingBase<T, Scaling, Owning, Alignment>(texture.pixels.data(), texture.sizeX, texture.sizeY) {
|
|
|
|
}
|
|
RenderingElement2D(Anchor2D anchor, TextureAsset<Vector<T, 4, Alignment>>& texture, std::uint32_t rotation) requires(!Owning && Scaling && Rotating) : RenderingElement2DBase<T, Frames>(anchor, texture.opaque), ScalingBase<T, Scaling, Owning, Alignment>(texture.pixels.data(), texture.sizeX, texture.sizeY), RotatingBase<Rotating>(rotation) {
|
|
|
|
}
|
|
|
|
RenderingElement2D(RenderingElement2D&) = delete;
|
|
RenderingElement2D& operator=(RenderingElement2D&) = delete;
|
|
|
|
void ScaleNearestNeighbor() requires(Scaling) {
|
|
for (std::uint32_t y = 0; y < this->scaled.size.y; y++) {
|
|
std::uint32_t srcY = y * ScalingBase<T, true, Owning, Alignment>::bufferHeight / this->scaled.size.y;
|
|
for (std::uint32_t x = 0; x < this->scaled.size.x; x++) {
|
|
std::uint32_t srcX = x * ScalingBase<T, true, Owning, Alignment>::bufferWidth / this->scaled.size.x;
|
|
this->buffer[y * this->scaled.size.x + x] = ScalingBase<T, true, Owning, Alignment>::scalingBuffer[srcY * ScalingBase<T, true, Owning, Alignment>::bufferWidth + srcX];
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScaleRotating() requires(Scaling) {
|
|
const float dstWidth = this->scaled.size.x;
|
|
const float dstHeight = this->scaled.size.y;
|
|
|
|
const float c2 = std::abs(std::cos(RotatingBase<true>::rotation));
|
|
const float s2 = std::abs(std::sin(RotatingBase<true>::rotation));
|
|
|
|
const float rotatedWidth = dstWidth * c2 + dstHeight * s2;
|
|
const float rotatedHeight = dstWidth * s2 + dstHeight * c2;
|
|
|
|
const float diffX = std::ceil((rotatedWidth - dstWidth) * 0.5);
|
|
const float diffY = std::ceil((rotatedHeight - dstHeight) * 0.5);
|
|
|
|
this->scaled.size.x += diffX + diffX;
|
|
this->scaled.size.y += diffY + diffY;
|
|
|
|
this->scaled.position.x -= diffX;
|
|
this->scaled.position.y -= diffY;
|
|
|
|
this->buffer.clear();
|
|
this->buffer.resize(this->scaled.size.x * this->scaled.size.y);
|
|
|
|
// Destination center
|
|
const float dstCx = (this->scaled.size.x - 1.0) * 0.5;
|
|
const float dstCy = (this->scaled.size.y - 1.0) * 0.5;
|
|
|
|
// Source center
|
|
const float srcCx = (ScalingBase<T, true, Owning, Alignment>::bufferWidth - 1.0) * 0.5;
|
|
const float srcCy = (ScalingBase<T, true, Owning, Alignment>::bufferHeight - 1.0) * 0.5;
|
|
|
|
const float c = std::cos(RotatingBase<true>::rotation);
|
|
const float s = std::sin(RotatingBase<true>::rotation);
|
|
|
|
// Scale factors (destination → source)
|
|
const float scaleX = static_cast<float>(ScalingBase<T, true, Owning, Alignment>::bufferWidth) / dstWidth;
|
|
const float scaleY = static_cast<float>(ScalingBase<T, true, Owning, Alignment>::bufferHeight) / dstHeight;
|
|
|
|
for (std::uint32_t yB = 0; yB < this->scaled.size.y; ++yB) {
|
|
for (std::uint32_t xB = 0; xB < this->scaled.size.x; ++xB) {
|
|
|
|
// ---- Destination pixel relative to center ----
|
|
const float dx = (static_cast<float>(xB) - dstCx) * scaleX;
|
|
const float dy = (static_cast<float>(yB) - dstCy) * scaleY;
|
|
|
|
// ---- Inverse rotation ----
|
|
const float sx = (c * dx - s * dy) + srcCx;
|
|
const float sy = (s * dx + c * dy) + srcCy;
|
|
|
|
// ---- Nearest neighbour sampling ----
|
|
const std::int32_t srcX = static_cast<std::int32_t>(std::round(sx));
|
|
const std::int32_t srcY = static_cast<std::int32_t>(std::round(sy));
|
|
|
|
if (srcX >= 0 && srcX < ScalingBase<T, true, Owning, Alignment>::bufferWidth && srcY >= 0 && srcY < ScalingBase<T, true, Owning, Alignment>::bufferHeight) {
|
|
this->buffer[yB * this->scaled.size.x + xB] = ScalingBase<T, true, Owning, Alignment>::scalingBuffer[srcY * ScalingBase<T, true, Owning, Alignment>::bufferWidth + srcX];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void UpdatePosition(RendertargetBase<Frames>& window, Transform2D& parent) override {
|
|
ScaleData2D oldScale = this->scaled;
|
|
this->ScaleElement(parent);
|
|
if constexpr(Scaling && !Rotating) {
|
|
if(oldScale.size.x != this->scaled.size.x || oldScale.size.y != this->scaled.size.y) {
|
|
this->buffer.resize(this->scaled.size.x * this->scaled.size.y);
|
|
ScaleNearestNeighbor();
|
|
}
|
|
} else if constexpr(Rotating) {
|
|
if(oldScale.size.x != this->scaled.size.x || oldScale.size.y != this->scaled.size.y) {
|
|
this->buffer.resize(this->scaled.size.x * this->scaled.size.y);
|
|
ScaleRotating();
|
|
}
|
|
} else {
|
|
if(oldScale.size.x != this->scaled.size.x || oldScale.size.y != this->scaled.size.y) {
|
|
this->buffer.resize(this->scaled.size.x * this->scaled.size.y);
|
|
}
|
|
}
|
|
for(Transform2D* child : this->children) {
|
|
child->UpdatePosition(window, *this);
|
|
}
|
|
}
|
|
|
|
std::vector<std::string_view> ResizeText(RendertargetBase<Frames>& window, Transform2D& parent, const std::string_view text, float& size, Font& font, TextOverflowMode overflowMode = TextOverflowMode::Clip, TextScaleMode scaleMode = TextScaleMode::None) {
|
|
float scale = stbtt_ScaleForPixelHeight(&font.font, size);
|
|
int baseline = (int)(font.ascent * scale);
|
|
|
|
std::vector<std::string_view> lines;
|
|
std::string_view remaining = text;
|
|
|
|
std::uint32_t lineHeight = (font.ascent - font.descent) * scale;
|
|
|
|
if(overflowMode == TextOverflowMode::Clip) {
|
|
while (!remaining.empty()) {
|
|
// Find next newline or end of string
|
|
auto newlinePos = remaining.find('\n');
|
|
if (newlinePos != std::string_view::npos) {
|
|
lines.emplace_back(remaining.substr(0, newlinePos));
|
|
remaining = remaining.substr(newlinePos + 1);
|
|
} else {
|
|
lines.emplace_back(remaining);
|
|
break;
|
|
}
|
|
}
|
|
std::uint32_t maxWidth = 0;
|
|
|
|
for(const std::string_view line: lines) {
|
|
std::uint32_t lineWidth = 0;
|
|
for (const char c : line) {
|
|
int advance, lsb;
|
|
stbtt_GetCodepointHMetrics(&font.font, c, &advance, &lsb);
|
|
lineWidth += (int)(advance * scale);
|
|
}
|
|
if(lineWidth > maxWidth) {
|
|
maxWidth = lineWidth;
|
|
}
|
|
}
|
|
|
|
if(scaleMode == TextScaleMode::Element) {
|
|
std::int32_t logicalPerPixelY = this->anchor.height / this->scaled.size.y;
|
|
std::int32_t oldHeight = this->anchor.height;
|
|
std::int32_t logicalPerPixelX = this->anchor.width / this->scaled.size.x;
|
|
std::int32_t oldwidth = this->anchor.width;
|
|
this->anchor.height = lineHeight * logicalPerPixelY;
|
|
this->anchor.width = maxWidth * logicalPerPixelX;
|
|
if(oldHeight != this->anchor.height || oldwidth != this->anchor.width) {
|
|
UpdatePosition(window, parent);
|
|
}
|
|
} else if(scaleMode == TextScaleMode::Font) {
|
|
float lineHeightPerFont = lineHeight / size;
|
|
float lineWidthPerFont = maxWidth / size;
|
|
|
|
float maxFontHeight = this->scaled.size.y / lineHeightPerFont;
|
|
float maxFontWidth = this->scaled.size.x / lineWidthPerFont;
|
|
|
|
size = std::min(maxFontHeight, maxFontWidth);
|
|
} else {
|
|
if constexpr(Scaling) {
|
|
lines.resize(ScalingBase<T, true, Owning, Alignment>::bufferHeight / lines.size());
|
|
} else {
|
|
lines.resize(this->scaled.size.y / lines.size());
|
|
}
|
|
}
|
|
} else {
|
|
while (!remaining.empty()) {
|
|
std::string_view line;
|
|
auto newlinePos = remaining.find('\n');
|
|
if (newlinePos != std::string_view::npos) {
|
|
line = remaining.substr(0, newlinePos);
|
|
remaining = remaining.substr(newlinePos + 1);
|
|
} else {
|
|
line = remaining;
|
|
remaining = "";
|
|
}
|
|
|
|
std::uint32_t lineWidth = 0;
|
|
std::size_t lastWrapPos = 0; // position of last space that can be used to wrap
|
|
std::size_t startPos = 0;
|
|
|
|
for (std::size_t i = 0; i < line.size(); ++i) {
|
|
char c = line[i];
|
|
|
|
// get width of this character
|
|
int advance, lsb;
|
|
stbtt_GetCodepointHMetrics(&font.font, c, &advance, &lsb);
|
|
lineWidth += (std::uint32_t)(advance * scale);
|
|
|
|
// remember last space for wrapping
|
|
if (c == ' ') {
|
|
lastWrapPos = i;
|
|
}
|
|
|
|
// if line exceeds width, wrap
|
|
if (lineWidth > this->scaled.size.x) {
|
|
std::size_t wrapPos;
|
|
if (lastWrapPos > startPos) {
|
|
wrapPos = lastWrapPos; // wrap at last space
|
|
} else {
|
|
wrapPos = i; // no space, hard wrap
|
|
}
|
|
|
|
// push the line up to wrapPos
|
|
lines.push_back(line.substr(startPos, wrapPos - startPos));
|
|
|
|
// skip any spaces at the beginning of next line
|
|
startPos = wrapPos;
|
|
while (startPos < line.size() && line[startPos] == ' ') {
|
|
++startPos;
|
|
}
|
|
|
|
// reset width and i
|
|
lineWidth = 0;
|
|
i = startPos - 1; // -1 because loop will increment i
|
|
}
|
|
}
|
|
|
|
// add the remaining part of the line
|
|
if (startPos < line.size()) {
|
|
lines.push_back(line.substr(startPos));
|
|
}
|
|
}
|
|
|
|
if(scaleMode == TextScaleMode::Element) {
|
|
float logicalPerPixelY = this->anchor.height / this->scaled.size.y;
|
|
float oldHeight = this->anchor.height;
|
|
this->anchor.height = lineHeight * logicalPerPixelY;
|
|
if(oldHeight != this->anchor.height) {
|
|
UpdatePosition(window, parent);
|
|
}
|
|
} else if(scaleMode == TextScaleMode::Font) {
|
|
float lineHeightPerFont = lineHeight / size;
|
|
size = this->scaled.size.y / lineHeightPerFont;
|
|
} else {
|
|
if constexpr(Scaling) {
|
|
lines.resize(ScalingBase<T, true, Owning, Alignment>::bufferHeight / lines.size());
|
|
} else {
|
|
lines.resize(this->scaled.size.y / lines.size());
|
|
}
|
|
}
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
|
|
int utf8_decode(const char* s, int* bytes_consumed) {
|
|
unsigned char c = s[0];
|
|
if (c < 0x80) {
|
|
*bytes_consumed = 1;
|
|
return c;
|
|
} else if ((c & 0xE0) == 0xC0) {
|
|
*bytes_consumed = 2;
|
|
return ((c & 0x1F) << 6) | (s[1] & 0x3F);
|
|
} else if ((c & 0xF0) == 0xE0) {
|
|
*bytes_consumed = 3;
|
|
return ((c & 0x0F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F);
|
|
} else if ((c & 0xF8) == 0xF0) {
|
|
*bytes_consumed = 4;
|
|
return ((c & 0x07) << 18) | ((s[1] & 0x3F) << 12) | ((s[2] & 0x3F) << 6) | (s[3] & 0x3F);
|
|
}
|
|
*bytes_consumed = 1;
|
|
return 0xFFFD; // replacement char
|
|
}
|
|
|
|
void RenderText(std::span<const std::string_view> lines, float size, Vector<T, 4> color, Font& font, TextAlignment alignment = TextAlignment::Left, std::uint32_t offsetX = 0, std::uint32_t offsetY = 0, OpaqueType opaque = OpaqueType::FullyOpaque) {
|
|
float scale = stbtt_ScaleForPixelHeight(&font.font, size);
|
|
int baseline = (int)(font.ascent * scale);
|
|
std::uint32_t lineHeight = (font.ascent - font.descent) * scale;
|
|
std::uint32_t currentY = baseline;
|
|
for(std::string_view line : lines) {
|
|
|
|
std::uint32_t lineWidth = 0;
|
|
for (const char c : line) {
|
|
int advance, lsb;
|
|
stbtt_GetCodepointHMetrics(&font.font, c, &advance, &lsb);
|
|
lineWidth += (int)(advance * scale);
|
|
}
|
|
|
|
std::uint32_t x = 0;
|
|
switch (alignment) {
|
|
case TextAlignment::Left:
|
|
x = 0;
|
|
break;
|
|
case TextAlignment::Center:
|
|
x = (this->scaled.size.x - lineWidth) / 2;
|
|
break;
|
|
case TextAlignment::Right:
|
|
x = this->scaled.size.x - lineWidth;
|
|
break;
|
|
}
|
|
|
|
const char* p = line.data();
|
|
const char* end = p + line.size();
|
|
|
|
while (p < end) {
|
|
int bytes;
|
|
int codepoint = utf8_decode(p, &bytes);
|
|
p += bytes;
|
|
|
|
int ax;
|
|
int lsb;
|
|
stbtt_GetCodepointHMetrics(&font.font, codepoint, &ax, &lsb);
|
|
|
|
int c_x1, c_y1, c_x2, c_y2;
|
|
stbtt_GetCodepointBitmapBox(&font.font, codepoint, scale, scale, &c_x1, &c_y1, &c_x2, &c_y2);
|
|
|
|
int w = c_x2 - c_x1;
|
|
int h = c_y2 - c_y1;
|
|
|
|
std::vector<unsigned char> bitmap(w * h);
|
|
stbtt_MakeCodepointBitmap(&font.font, bitmap.data(), w, h, w, scale, scale, codepoint);
|
|
|
|
// Only render characters that fit within the scaled bounds
|
|
switch(opaque) {
|
|
case OpaqueType::FullyOpaque: {
|
|
for (int j = 0; j < h; j++) {
|
|
for (int i = 0; i < w; i++) {
|
|
int bufferX = x + i + c_x1 + offsetX;
|
|
int bufferY = currentY + j + c_y1 + offsetY;
|
|
|
|
// Only draw pixels that are within our scaled buffer bounds
|
|
if constexpr(Scaling) {
|
|
if (bufferX >= 0 && bufferX < ScalingBase<T, true, Owning, Alignment>::bufferWidth && bufferY >= 0 && bufferY < ScalingBase<T, true, Owning, Alignment>::bufferHeight) {
|
|
ScalingBase<T, true, Owning, Alignment>::scalingBuffer[bufferY * ScalingBase<T, true, Owning, Alignment>::bufferWidth + bufferX] = {color.r, color.g, color.b, static_cast<T>(bitmap[j * w + i])};
|
|
}
|
|
} else {
|
|
if (bufferX >= 0 && bufferX < (int)this->scaled.size.x && bufferY >= 0 && bufferY < (int)this->scaled.size.y) {
|
|
this->buffer[bufferY * this->scaled.size.x + bufferX] = {color.r, color.g, color.b, static_cast<T>(bitmap[j * w + i])};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case OpaqueType::SemiOpaque: {
|
|
for (int j = 0; j < h; j++) {
|
|
for (int i = 0; i < w; i++) {
|
|
int bufferX = x + i + c_x1 + offsetX;
|
|
int bufferY = currentY + j + c_y1 + offsetY;
|
|
|
|
// Only draw pixels that are within our scaled buffer bounds
|
|
if constexpr(Scaling) {
|
|
if (bufferX >= 0 && bufferX < ScalingBase<T, true, Owning, Alignment>::bufferWidth && bufferY >= 0 && bufferY < ScalingBase<T, true, Owning, Alignment>::bufferHeight) {
|
|
ScalingBase<T, true, Owning, Alignment>::scalingBuffer[bufferY * ScalingBase<T, true, Owning, Alignment>::bufferWidth + bufferX] = {color.r, color.g, color.b, static_cast<T>(bitmap[j * w + i])};
|
|
}
|
|
} else {
|
|
if (bufferX >= 0 && bufferX < (int)this->scaled.size.x && bufferY >= 0 && bufferY < (int)this->scaled.size.y) {
|
|
std::uint8_t alpha = bitmap[j * w + i];
|
|
if(alpha != 0) {
|
|
this->buffer[bufferY * this->scaled.size.x + bufferX] = {color.r, color.g, color.b, static_cast<T>(bitmap[j * w + i])};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case OpaqueType::Transparent: {
|
|
for (int j = 0; j < h; j++) {
|
|
for (int i = 0; i < w; i++) {
|
|
int bufferX = x + i + c_x1 + offsetX;
|
|
int bufferY = currentY + j + c_y1 + offsetY;
|
|
|
|
// Only draw pixels that are within our scaled buffer bounds
|
|
if constexpr(Scaling) {
|
|
if (bufferX >= 0 && bufferX < ScalingBase<T, true, Owning, Alignment>::bufferWidth && bufferY >= 0 && bufferY < ScalingBase<T, true, Owning, Alignment>::bufferHeight) {
|
|
ScalingBase<T, true, Owning, Alignment>::scalingBuffer[bufferY * ScalingBase<T, true, Owning, Alignment>::bufferWidth + bufferX] = {color.r, color.g, color.b, static_cast<T>(bitmap[j * w + i])};
|
|
}
|
|
} else {
|
|
if (bufferX >= 0 && bufferX < (int)this->scaled.size.x && bufferY >= 0 && bufferY < (int)this->scaled.size.y) {
|
|
if constexpr(std::same_as<T, std::uint8_t>) {
|
|
std::uint8_t alpha = bitmap[j * w + i];
|
|
|
|
Vector<T, 4, Alignment> dst = this->buffer[bufferY * this->scaled.size.x + bufferX];
|
|
|
|
float srcA = (alpha / 255.0f) * (color.a / 255.0f);
|
|
float dstA = dst.a / 255.0f;
|
|
|
|
float oneMinusSrcA = 1.0f - color.a;
|
|
|
|
float outA = srcA + dstA * (1.0f - srcA);
|
|
this->buffer[bufferY * this->scaled.size.x + bufferX] = Vector<std::uint8_t, 4, Alignment>(
|
|
static_cast<std::uint8_t>((color.r * srcA + dst.r * dstA * (1.0f - srcA)) / outA),
|
|
static_cast<std::uint8_t>((color.g * srcA + dst.g * dstA * (1.0f - srcA)) / outA),
|
|
static_cast<std::uint8_t>((color.b * srcA + dst.b * dstA * (1.0f - srcA)) / outA),
|
|
static_cast<std::uint8_t>(outA * 255)
|
|
);
|
|
} else if constexpr(std::same_as<T, _Float16>) {
|
|
std::uint8_t alpha = bitmap[j * w + i];
|
|
_Float16 srcA = (_Float16(alpha)/_Float16(255.0f))*color.a;
|
|
Vector<_Float16, 4, Alignment> dst = this->buffer[bufferY * this->scaled.size.x + bufferX];
|
|
|
|
_Float16 outA = srcA + dst.a * (1.0f - srcA);
|
|
this->buffer[bufferY * this->scaled.size.x + bufferX] = Vector<_Float16, 4, Alignment>(
|
|
(color.r * srcA + dst.r * dst.a * (1.0f - srcA)),
|
|
(color.g * srcA + dst.g * dst.a * (1.0f - srcA)),
|
|
(color.b * srcA + dst.b * dst.a * (1.0f - srcA)),
|
|
outA
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
x += (int)(ax * scale);
|
|
|
|
if (p + 1 < end) {
|
|
int next;
|
|
x += (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, utf8_decode(p+1, &next));
|
|
}
|
|
}
|
|
currentY += lineHeight;
|
|
}
|
|
}
|
|
};
|
|
} |