mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-18 16:27:34 +01:00
176 lines
6.9 KiB
C++
176 lines
6.9 KiB
C++
#include <kandinsky/font.h>
|
|
extern "C" {
|
|
#include <kandinsky/fonts/code_points.h>
|
|
}
|
|
#include <ion.h>
|
|
#include <ion/unicode/utf8_decoder.h>
|
|
#include <assert.h>
|
|
|
|
constexpr static int k_tabCharacterWidth = 4;
|
|
|
|
KDSize KDFont::stringSizeUntil(const char * text, const char * limit) const {
|
|
if (text == nullptr || (limit != nullptr && text >= limit)) {
|
|
return KDSizeZero;
|
|
}
|
|
KDSize stringSize = KDSize(0, m_glyphSize.height());
|
|
|
|
UTF8Decoder decoder(text);
|
|
const char * currentStringPosition = decoder.stringPosition();
|
|
CodePoint codePoint = decoder.nextCodePoint();
|
|
while (codePoint != UCodePointNull && (limit == nullptr || currentStringPosition < limit)) {
|
|
KDSize cSize = KDSize(m_glyphSize.width(), 0);
|
|
if (codePoint == UCodePointLineFeed) {
|
|
cSize = KDSize(0, m_glyphSize.height());
|
|
} else if (codePoint == UCodePointTabulation) {
|
|
cSize = KDSize(k_tabCharacterWidth * m_glyphSize.width(), 0);
|
|
} else if (codePoint.isCombining()) {
|
|
cSize = KDSizeZero;
|
|
}
|
|
stringSize = KDSize(stringSize.width() + cSize.width(), stringSize.height() + cSize.height());
|
|
currentStringPosition = decoder.stringPosition();
|
|
codePoint = decoder.nextCodePoint();
|
|
}
|
|
assert(stringSize.width() >= 0 && stringSize.height() >= 0);
|
|
return stringSize;
|
|
}
|
|
|
|
void KDFont::setGlyphGrayscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const {
|
|
fetchGrayscaleGlyphAtIndex(indexForCodePoint(codePoint), glyphBuffer->grayscaleBuffer());
|
|
}
|
|
|
|
void KDFont::accumulateGlyphGrayscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const {
|
|
uint8_t * grayscaleBuffer = glyphBuffer->grayscaleBuffer();
|
|
uint8_t * accumulationGrayscaleBuffer = glyphBuffer->secondaryGrayscaleBuffer();
|
|
fetchGrayscaleGlyphAtIndex(indexForCodePoint(codePoint), accumulationGrayscaleBuffer);
|
|
for (int i=0; i<m_glyphSize.width()*m_glyphSize.height(); i++) {
|
|
grayscaleBuffer[i] |= accumulationGrayscaleBuffer[i];
|
|
}
|
|
}
|
|
|
|
void KDFont::fetchGrayscaleGlyphAtIndex(KDFont::GlyphIndex index, uint8_t * grayscaleBuffer) const {
|
|
Ion::decompress(
|
|
compressedGlyphData(index),
|
|
grayscaleBuffer,
|
|
compressedGlyphDataSize(index),
|
|
m_glyphSize.width() * m_glyphSize.height() * k_bitsPerPixel/8
|
|
);
|
|
}
|
|
|
|
void KDFont::colorizeGlyphBuffer(const RenderPalette * renderPalette, GlyphBuffer * glyphBuffer) const {
|
|
/* Since a grayscale value is smaller than a color value (see assertion), we
|
|
* can store the temporary grayscale values in the output pixel buffer.
|
|
* What's great is that now, if we fill the pixel buffer right-to-left with
|
|
* colors derived from the temporary grayscale values, we will never overwrite
|
|
* the remaining grayscale values since those are smaller. So we can avoid a
|
|
* separate buffer for the temporary grayscale values. */
|
|
assert(k_bitsPerPixel < 8*sizeof(KDColor));
|
|
|
|
uint8_t * grayscaleBuffer = glyphBuffer->grayscaleBuffer();
|
|
KDColor * colorBuffer = glyphBuffer->colorBuffer();
|
|
|
|
uint8_t mask = (0xFF >> (8-k_bitsPerPixel));
|
|
int pixelIndex = m_glyphSize.width() * m_glyphSize.height() - 1; // Let's start at the final pixel
|
|
int grayscaleByteIndex = pixelIndex * k_bitsPerPixel / 8;
|
|
while (pixelIndex >= 0) {
|
|
assert(grayscaleByteIndex == pixelIndex * k_bitsPerPixel / 8);
|
|
uint8_t grayscaleByte = grayscaleBuffer[grayscaleByteIndex--]; // We consume a grayscale byte...
|
|
for (int j=0; j<8/k_bitsPerPixel; j++) { // .. and we'll output 8/k_bits pixels
|
|
uint8_t grayscale = grayscaleByte & mask;
|
|
grayscaleByte = grayscaleByte >> k_bitsPerPixel;
|
|
assert(pixelIndex >= 0);
|
|
colorBuffer[pixelIndex--] = renderPalette->colorAtIndex(grayscale);
|
|
}
|
|
}
|
|
}
|
|
|
|
KDFont::GlyphIndex KDFont::indexForCodePoint(CodePoint c) const {
|
|
#define USE_BINARY_SEARCH 0
|
|
#if USE_BINARY_SEARCH
|
|
int lowerBound = 0;
|
|
int upperBound = m_tableLength;
|
|
while (true) {
|
|
int currentIndex = (lowerBound+upperBound)/2;
|
|
// printf("Considering %d in [%d,%d]\n", currentIndex, lowerBound, upperBound);
|
|
const CodePointIndexPair * currentPair = m_table + currentIndex;
|
|
const CodePointIndexPair * nextPair = (currentIndex + 1) < m_tableLength ? currentPair + 1 : nullptr;
|
|
// printf("At this point, currentPair->codePoint() = %d and c = %d\n", currentPair->codePoint(), c);
|
|
if (currentPair->codePoint() == c) {
|
|
return currentPair->glyphIndex();
|
|
} else if (currentPair->codePoint() > c) {
|
|
// We need to look below
|
|
if (upperBound == currentIndex) {
|
|
// There's nothing below. Error out.
|
|
return 0;
|
|
}
|
|
upperBound = currentIndex;
|
|
continue;
|
|
} else if (nextPair == nullptr) {
|
|
return 0;
|
|
} else if (nextPair->codePoint() == c) {
|
|
return nextPair->glyphIndex();
|
|
} else if (nextPair->codePoint() < c) {
|
|
// We need to look above
|
|
if (lowerBound == currentIndex) {
|
|
// There's nothing above. Error out.
|
|
return 0;
|
|
}
|
|
lowerBound = currentIndex;
|
|
continue;
|
|
} else {
|
|
// At this point,
|
|
// currentPair->codePoint < c && nextPair != nullptr && nextPair->codePoint > c
|
|
// Yay, it's over!
|
|
// There can be an empty space between the currentPair and the nextPair
|
|
// e.g. currentPair(3,1) and nextPair(9, 4)
|
|
// means value at codePoints 3, 4, 5, 6, 7, 8, 9
|
|
// are glyph identifiers 1, ?, ?, ?, ?, ?, 4
|
|
// solved as 1, 2, 3, 0, 0, 0, 4
|
|
|
|
// Let's hunt down the zeroes
|
|
CodePoint lastCodePointOfCurrentPair = currentPair->codePoint() + (nextPair->glyphIndex() - currentPair->glyphIndex() - 1);
|
|
if (c > lastCodePointOfCurrentPair) {
|
|
return 0;
|
|
}
|
|
return currentPair->glyphIndex() + (c - currentPair->codePoint());
|
|
}
|
|
}
|
|
#else
|
|
const CodePointIndexPair * currentPair = m_table;
|
|
const CodePointIndexPair * endPair = m_table + m_tableLength - 1;
|
|
if (c < currentPair->codePoint()) {
|
|
goto NoMatchingGlyph;
|
|
}
|
|
while (currentPair < endPair) {
|
|
const CodePointIndexPair * nextPair = currentPair + 1;
|
|
if (c < nextPair->codePoint()) {
|
|
CodePoint lastCodePointOfCurrentPair = currentPair->codePoint() + (nextPair->glyphIndex() - currentPair->glyphIndex() - 1);
|
|
if (c > lastCodePointOfCurrentPair) {
|
|
goto NoMatchingGlyph;
|
|
}
|
|
return currentPair->glyphIndex() + (c - currentPair->codePoint());
|
|
}
|
|
currentPair = nextPair;
|
|
}
|
|
if (endPair->codePoint() == c) {
|
|
return endPair->glyphIndex();
|
|
}
|
|
NoMatchingGlyph:
|
|
assert(SimpleCodePoints[IndexForReplacementCharacterCodePoint] == 0xFFFD);
|
|
return IndexForReplacementCharacterCodePoint;
|
|
#endif
|
|
}
|
|
|
|
bool KDFont::CanBeWrittenWithGlyphs(const char * text) {
|
|
UTF8Decoder decoder(text);
|
|
CodePoint cp = decoder.nextCodePoint();
|
|
while(cp != UCodePointNull) {
|
|
if (LargeFont->indexForCodePoint(cp) == KDFont::IndexForReplacementCharacterCodePoint
|
|
|| SmallFont->indexForCodePoint(cp) == KDFont::IndexForReplacementCharacterCodePoint)
|
|
{
|
|
return false;
|
|
}
|
|
cp = decoder.nextCodePoint();
|
|
}
|
|
return true;
|
|
}
|