Final review
This commit is contained in:
@@ -112,7 +112,7 @@ void FontDecompressor::compactSingleGlyph(const uint8_t* alignedSrc, uint8_t* pa
|
|||||||
if (outBits > 0) packedDst[writeIdx] = outByte << (8 - outBits);
|
if (outBits > 0) packedDst[writeIdx] = outByte << (8 - outBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- getBitmap: page buffer → hot group → decompress ---
|
// --- getBitmap: page buffer → transient malloc + decompress + compact ---
|
||||||
|
|
||||||
const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const EpdGlyph* glyph, uint32_t glyphIndex) {
|
const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const EpdGlyph* glyph, uint32_t glyphIndex) {
|
||||||
const uint32_t tStart = micros();
|
const uint32_t tStart = micros();
|
||||||
@@ -159,6 +159,12 @@ const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const Ep
|
|||||||
stats.cacheMisses++;
|
stats.cacheMisses++;
|
||||||
const EpdFontGroup& group = fontData->groups[groupIndex];
|
const EpdFontGroup& group = fontData->groups[groupIndex];
|
||||||
|
|
||||||
|
if (glyph->dataLength > HOT_GLYPH_BUF_SIZE) {
|
||||||
|
LOG_ERR("FDC", "Glyph dataLength %u exceeds HOT_GLYPH_BUF_SIZE %u", glyph->dataLength, HOT_GLYPH_BUF_SIZE);
|
||||||
|
stats.getBitmapTimeUs += micros() - tStart;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (group.uncompressedSize > stats.peakTempBytes) stats.peakTempBytes = group.uncompressedSize;
|
if (group.uncompressedSize > stats.peakTempBytes) stats.peakTempBytes = group.uncompressedSize;
|
||||||
|
|
||||||
uint8_t* groupBuf = static_cast<uint8_t*>(malloc(group.uncompressedSize));
|
uint8_t* groupBuf = static_cast<uint8_t*>(malloc(group.uncompressedSize));
|
||||||
@@ -174,13 +180,6 @@ const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const Ep
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glyph->dataLength > HOT_GLYPH_BUF_SIZE) {
|
|
||||||
LOG_ERR("FDC", "Glyph dataLength %u exceeds HOT_GLYPH_BUF_SIZE %u", glyph->dataLength, HOT_GLYPH_BUF_SIZE);
|
|
||||||
free(groupBuf);
|
|
||||||
stats.getBitmapTimeUs += micros() - tStart;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t alignedOff = getAlignedOffset(fontData, groupIndex, glyphIndex);
|
uint32_t alignedOff = getAlignedOffset(fontData, groupIndex, glyphIndex);
|
||||||
compactSingleGlyph(&groupBuf[alignedOff], _hotGlyphBuf, glyph->width, glyph->height);
|
compactSingleGlyph(&groupBuf[alignedOff], _hotGlyphBuf, glyph->width, glyph->height);
|
||||||
free(groupBuf);
|
free(groupBuf);
|
||||||
@@ -348,6 +347,8 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8
|
|||||||
if (!groupIdToPos) {
|
if (!groupIdToPos) {
|
||||||
LOG_ERR("FDC", "OOM: cannot allocate %u bytes for groupIdToPos map", fontData->groupCount);
|
LOG_ERR("FDC", "OOM: cannot allocate %u bytes for groupIdToPos map", fontData->groupCount);
|
||||||
// Roll back this slot only (other slots from prior prewarmCache calls stay valid)
|
// Roll back this slot only (other slots from prior prewarmCache calls stay valid)
|
||||||
|
stats.pageBufferBytes -= totalBytes;
|
||||||
|
stats.pageGlyphsBytes -= glyphCount * sizeof(PageGlyphEntry);
|
||||||
free(slot.buffer);
|
free(slot.buffer);
|
||||||
free(slot.glyphs);
|
free(slot.glyphs);
|
||||||
slot = {};
|
slot = {};
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ class FontDecompressor {
|
|||||||
void deinit();
|
void deinit();
|
||||||
|
|
||||||
// Returns pointer to decompressed bitmap data for the given glyph.
|
// Returns pointer to decompressed bitmap data for the given glyph.
|
||||||
// Checks the page buffer (from prewarm) first, then falls back to the hot group slot.
|
// Checks the page buffer (from prewarm) first and otherwise transiently
|
||||||
|
// allocates/decompresses the glyph's group into a temporary buffer and
|
||||||
|
// compacts the requested glyph. The returned pointer is valid only until the
|
||||||
|
// next getBitmap call or cache eviction; callers must copy bitmap data if a
|
||||||
|
// longer lifetime is required.
|
||||||
const uint8_t* getBitmap(const EpdFontData* fontData, const EpdGlyph* glyph, uint32_t glyphIndex);
|
const uint8_t* getBitmap(const EpdFontData* fontData, const EpdGlyph* glyph, uint32_t glyphIndex);
|
||||||
|
|
||||||
// Free all cached data (page buffers).
|
// Free all cached data (page buffers).
|
||||||
|
|||||||
@@ -765,9 +765,14 @@ if compress:
|
|||||||
group_count = 0
|
group_count = 0
|
||||||
group_uncompressed = 0
|
group_uncompressed = 0
|
||||||
|
|
||||||
for i, (props, packed) in enumerate(all_glyphs):
|
for i, (props, _) in enumerate(all_glyphs):
|
||||||
sg = get_script_group(props.code_point)
|
sg = get_script_group(props.code_point)
|
||||||
glyph_aligned_size = ((props.width + 3) // 4) * props.height if props.width > 0 and props.height > 0 else 0
|
glyph_aligned_size = ((props.width + 3) // 4) * props.height if props.width > 0 and props.height > 0 else 0
|
||||||
|
if glyph_aligned_size > GROUP_MAX_UNCOMPRESSED_BYTES:
|
||||||
|
raise ValueError(
|
||||||
|
f"Glyph {i} (code point U+{props.code_point:04X}) single aligned size "
|
||||||
|
f"{glyph_aligned_size} exceeds GROUP_MAX_UNCOMPRESSED_BYTES={GROUP_MAX_UNCOMPRESSED_BYTES}"
|
||||||
|
)
|
||||||
size_overflow = group_uncompressed + glyph_aligned_size > GROUP_MAX_UNCOMPRESSED_BYTES
|
size_overflow = group_uncompressed + glyph_aligned_size > GROUP_MAX_UNCOMPRESSED_BYTES
|
||||||
|
|
||||||
if sg != current_group_id or size_overflow:
|
if sg != current_group_id or size_overflow:
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ constexpr int MAX_COST = std::numeric_limits<int>::max();
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Closing punctuation that should not have extra space inserted before it during justification.
|
// Closing punctuation that should not have extra space inserted before it during justification.
|
||||||
// Includes common closing brackets/quotes and sentence-ending marks.
|
// Includes common closing brackets/quotes and sentence-ending marks. En/em dashes
|
||||||
|
// are also treated as inline separators here to avoid justification stretch
|
||||||
|
// immediately before them.
|
||||||
bool isClosingPunctuation(const uint32_t cp) {
|
bool isClosingPunctuation(const uint32_t cp) {
|
||||||
switch (cp) {
|
switch (cp) {
|
||||||
case '.':
|
case '.':
|
||||||
@@ -804,17 +806,16 @@ ParsedText::LineProcessResult ParsedText::extractLine(
|
|||||||
// Count gaps: each word after the first creates a gap, unless it's a continuation.
|
// Count gaps: each word after the first creates a gap, unless it's a continuation.
|
||||||
// Gaps before closing punctuation (. , ) » etc.) are excluded from justification
|
// Gaps before closing punctuation (. , ) » etc.) are excluded from justification
|
||||||
// distribution so they stay at natural space width.
|
// distribution so they stay at natural space width.
|
||||||
|
const uint32_t firstCp = firstCodepoint(words[lastBreakAt + wordIdx]);
|
||||||
if (wordIdx > 0 && !continuesVec[lastBreakAt + wordIdx]) {
|
if (wordIdx > 0 && !continuesVec[lastBreakAt + wordIdx]) {
|
||||||
const bool beforeClosing = isClosingPunctuation(firstCodepoint(words[lastBreakAt + wordIdx]));
|
const bool beforeClosing = isClosingPunctuation(firstCp);
|
||||||
if (!beforeClosing) actualGapCount++;
|
if (!beforeClosing) actualGapCount++;
|
||||||
totalNaturalGaps +=
|
totalNaturalGaps += renderer.getSpaceAdvance(fontId, lastCodepoint(words[lastBreakAt + wordIdx - 1]), firstCp,
|
||||||
renderer.getSpaceAdvance(fontId, lastCodepoint(words[lastBreakAt + wordIdx - 1]),
|
wordStyles[lastBreakAt + wordIdx - 1]);
|
||||||
firstCodepoint(words[lastBreakAt + wordIdx]), wordStyles[lastBreakAt + wordIdx - 1]);
|
|
||||||
} else if (wordIdx > 0 && continuesVec[lastBreakAt + wordIdx]) {
|
} else if (wordIdx > 0 && continuesVec[lastBreakAt + wordIdx]) {
|
||||||
// Cross-boundary kerning for continuation words (e.g. nonbreaking spaces, attached punctuation)
|
// Cross-boundary kerning for continuation words (e.g. nonbreaking spaces, attached punctuation)
|
||||||
totalNaturalGaps +=
|
totalNaturalGaps += renderer.getKerning(fontId, lastCodepoint(words[lastBreakAt + wordIdx - 1]), firstCp,
|
||||||
renderer.getKerning(fontId, lastCodepoint(words[lastBreakAt + wordIdx - 1]),
|
wordStyles[lastBreakAt + wordIdx - 1]);
|
||||||
firstCodepoint(words[lastBreakAt + wordIdx]), wordStyles[lastBreakAt + wordIdx - 1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -859,12 +860,12 @@ ParsedText::LineProcessResult ParsedText::extractLine(
|
|||||||
} else {
|
} else {
|
||||||
int gap = 0;
|
int gap = 0;
|
||||||
if (wordIdx + 1 < lineWordCount) {
|
if (wordIdx + 1 < lineWordCount) {
|
||||||
gap = renderer.getSpaceAdvance(fontId, lastCodepoint(words[lastBreakAt + wordIdx]),
|
const uint32_t nextFirstCp = firstCodepoint(words[lastBreakAt + wordIdx + 1]);
|
||||||
firstCodepoint(words[lastBreakAt + wordIdx + 1]),
|
gap = renderer.getSpaceAdvance(fontId, lastCodepoint(words[lastBreakAt + wordIdx]), nextFirstCp,
|
||||||
wordStyles[lastBreakAt + wordIdx]);
|
wordStyles[lastBreakAt + wordIdx]);
|
||||||
// Don't stretch the gap before closing punctuation — it looks wrong with
|
// Don't stretch the gap before closing punctuation — it looks wrong with
|
||||||
// extra space before ".", ")", "»" etc.
|
// extra space before ".", ")", "»" etc.
|
||||||
const bool nextIsClosing = isClosingPunctuation(firstCodepoint(words[lastBreakAt + wordIdx + 1]));
|
const bool nextIsClosing = isClosingPunctuation(nextFirstCp);
|
||||||
if (blockStyle.alignment == CssTextAlign::Justify && !isLastLine && !nextIsClosing) {
|
if (blockStyle.alignment == CssTextAlign::Justify && !isLastLine && !nextIsClosing) {
|
||||||
gap += justifyExtra;
|
gap += justifyExtra;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user