Signed-off-by: Balázs Szücs <bszucs1209@gmail.com>
This commit is contained in:
Balázs Szücs 2025-08-22 20:18:55 +02:00
parent a849b9166c
commit df31630aed

View File

@ -1,5 +1,6 @@
package stirling.software.SPDF.utils.text; package stirling.software.SPDF.utils.text;
import java.nio.charset.StandardCharsets;
import java.text.Normalizer; import java.text.Normalizer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -96,7 +97,6 @@ public class WidthCalculator {
for (int codePoint : codePoints) { for (int codePoint : codePoints) {
String character = new String(Character.toChars(codePoint)); String character = new String(Character.toChars(codePoint));
Float charWidth = calculateSingleCharacterWidth(font, character, fontSize); Float charWidth = calculateSingleCharacterWidth(font, character, fontSize);
if (charWidth == null) return null;
totalWidth += charWidth; totalWidth += charWidth;
if (previousCodePoint != -1) { if (previousCodePoint != -1) {
@ -133,7 +133,7 @@ public class WidthCalculator {
if (encoded == null && font instanceof PDType0Font) { if (encoded == null && font instanceof PDType0Font) {
try { try {
encoded = character.getBytes("UTF-8"); encoded = character.getBytes(StandardCharsets.UTF_8);
} catch (Exception e) { } catch (Exception e) {
log.debug("UTF-8 encoding failed for '{}': {}", character, e.getMessage()); log.debug("UTF-8 encoding failed for '{}': {}", character, e.getMessage());
} }
@ -271,7 +271,6 @@ public class WidthCalculator {
} }
} }
// Try multi-byte interpretation for Unicode fonts
if (encoded.length >= 2 && font instanceof PDType0Font) { if (encoded.length >= 2 && font instanceof PDType0Font) {
try { try {
int glyphCode = ((encoded[0] & 0xFF) << 8) | (encoded[1] & 0xFF); int glyphCode = ((encoded[0] & 0xFF) << 8) | (encoded[1] & 0xFF);
@ -348,7 +347,6 @@ public class WidthCalculator {
return enhancedAverage; return enhancedAverage;
} catch (Exception e) { } catch (Exception e) {
// Ultimate fallback
float conservativeWidth = text.length() * CONSERVATIVE_CHAR_WIDTH_RATIO * fontSize; float conservativeWidth = text.length() * CONSERVATIVE_CHAR_WIDTH_RATIO * fontSize;
log.debug("Conservative fallback width: {}", conservativeWidth); log.debug("Conservative fallback width: {}", conservativeWidth);
return conservativeWidth; return conservativeWidth;
@ -394,7 +392,6 @@ public class WidthCalculator {
i += Character.charCount(codePoint); i += Character.charCount(codePoint);
} }
// Log composition analysis for debugging
log.debug( log.debug(
"Text composition analysis - Spaces: {}, Upper: {}, Lower: {}, Digits: {}, Punct: {}", "Text composition analysis - Spaces: {}, Upper: {}, Lower: {}, Digits: {}, Punct: {}",
spaceCount, spaceCount,
@ -410,7 +407,6 @@ public class WidthCalculator {
try { try {
float baseAverage = font.getAverageFontWidth(); float baseAverage = font.getAverageFontWidth();
// Try to get more specific metrics
float capHeight = 0; float capHeight = 0;
float xHeight = 0; float xHeight = 0;
@ -419,7 +415,6 @@ public class WidthCalculator {
xHeight = font.getFontDescriptor().getXHeight(); xHeight = font.getFontDescriptor().getXHeight();
} }
// Use metrics to adjust the average width estimation
float adjustmentFactor = 1.0f; float adjustmentFactor = 1.0f;
if (capHeight > 0 && xHeight > 0) { if (capHeight > 0 && xHeight > 0) {
adjustmentFactor = Math.max(0.8f, Math.min(1.2f, xHeight / capHeight)); adjustmentFactor = Math.max(0.8f, Math.min(1.2f, xHeight / capHeight));
@ -439,7 +434,6 @@ public class WidthCalculator {
return false; return false;
} }
// Check cache first
String cacheKey = createReliabilityCacheKey(font); String cacheKey = createReliabilityCacheKey(font);
Boolean cachedResult = reliabilityCache.get(cacheKey); Boolean cachedResult = reliabilityCache.get(cacheKey);
if (cachedResult != null) { if (cachedResult != null) {
@ -452,26 +446,22 @@ public class WidthCalculator {
boolean result = performReliabilityCheck(font); boolean result = performReliabilityCheck(font);
// Cache the result
reliabilityCache.put(cacheKey, result); reliabilityCache.put(cacheKey, result);
return result; return result;
} }
private boolean performReliabilityCheck(PDFont font) { private boolean performReliabilityCheck(PDFont font) {
try { try {
// Check if font is damaged
if (font.isDamaged()) { if (font.isDamaged()) {
log.debug("Font {} is damaged", font.getName()); log.debug("Font {} is damaged", font.getName());
return false; return false;
} }
// Check basic width calculation capability
if (!TextEncodingHelper.canCalculateBasicWidths(font)) { if (!TextEncodingHelper.canCalculateBasicWidths(font)) {
log.debug("Font {} cannot perform basic width calculations", font.getName()); log.debug("Font {} cannot perform basic width calculations", font.getName());
return false; return false;
} }
// Test with a simple character
try { try {
font.getStringWidth("A"); font.getStringWidth("A");
return true; return true;
@ -495,95 +485,4 @@ public class WidthCalculator {
return false; return false;
} }
} }
public float calculateCharacterWidth(PDFont font, String character, float fontSize) {
if (font == null || character == null || character.isEmpty() || fontSize <= 0) return 0;
String cacheKey = createCacheKey(font, character, fontSize);
Float cachedWidth = widthCache.get(cacheKey);
if (cachedWidth != null) return cachedWidth;
Float width = calculateSingleCharacterWidth(font, character, fontSize);
if (width == null) width = calculateAverageCharacterWidth(font, fontSize);
widthCache.put(cacheKey, width);
return width;
}
public String createWidthMatchingPlaceholder(
String originalText,
float targetWidth,
PDFont font,
float fontSize,
String placeholderChar) {
if (originalText == null || originalText.isEmpty() || targetWidth <= 0) return "";
if (placeholderChar == null || placeholderChar.isEmpty()) placeholderChar = " ";
try {
float placeholderCharWidth = calculateCharacterWidth(font, placeholderChar, fontSize);
if (placeholderCharWidth <= 0) {
return " ".repeat(Math.max(1, originalText.length()));
}
int placeholderCount = Math.max(1, Math.round(targetWidth / placeholderCharWidth));
int originalLength = originalText.length();
int maxReasonableLength = Math.max(originalLength * 3, Math.max(placeholderCount, 10));
placeholderCount = Math.min(placeholderCount, maxReasonableLength);
placeholderCount = Math.max(1, placeholderCount);
return placeholderChar.repeat(placeholderCount);
} catch (Exception e) {
return " ".repeat(Math.max(1, originalText.length()));
}
}
public boolean canCalculateTextWidth(PDFont font, String text) {
if (font == null || text == null || text.isEmpty()) return false;
if (!isWidthCalculationReliable(font)) return false;
List<Integer> codePoints = getCodePoints(text);
int testSampleSize = Math.min(5, codePoints.size());
for (int i = 0; i < testSampleSize; i++) {
int codePoint = codePoints.get(i);
String character = new String(Character.toChars(codePoint));
try {
if (!TextEncodingHelper.canEncodeCharacters(font, character)) {
log.debug(
"Cannot encode character U+{} in text '{}'",
Integer.toHexString(codePoint),
text);
return false;
}
float width = calculateCharacterWidth(font, character, 12.0f);
if (width <= 0) {
log.debug(
"Character U+{} has invalid width: {}",
Integer.toHexString(codePoint),
width);
return false;
}
} catch (Exception e) {
log.debug(
"Error testing character U+{}: {}",
Integer.toHexString(codePoint),
e.getMessage());
return false;
}
}
return true;
}
public void clearWidthCache() {
widthCache.clear();
}
public void clearReliabilityCache() {
reliabilityCache.clear();
}
} }