| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 | /* * CBitmapFont.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */#include "StdInc.h"#include "CBitmapFont.h"#include "SDL_Extensions.h"#include "../CGameInfo.h"#include "../gui/CGuiHandler.h"#include "../render/Colors.h"#include "../render/IScreenHandler.h"#include "../../lib/Rect.h"#include "../../lib/filesystem/Filesystem.h"#include "../../lib/modding/CModHandler.h"#include "../../lib/texts/Languages.h"#include "../../lib/texts/TextOperations.h"#include "../../lib/vcmi_endian.h"#include "../../lib/VCMI_Lib.h"#include <SDL_surface.h>struct AtlasLayout{	Point dimensions;	std::map<int, Rect> images;};/// Attempts to pack provided list of images into 2d box of specified size/// Returns resulting layout on success and empty optional on failurestatic std::optional<AtlasLayout> tryAtlasPacking(Point dimensions, const std::map<int, Point> & images){	// Simple atlas packing algorithm. Can be extended if needed, however optimal solution is NP-complete problem, so 'perfect' solution is too costly	AtlasLayout result;	result.dimensions = dimensions;	// a little interval to prevent potential 'bleeding' into adjacent symbols	// should be unnecessary for base game, but may be needed for upscaled filters	constexpr int interval = 1;	int currentHeight = 0;	int nextHeight = 0;	int currentWidth = 0;	for (auto const & image : images)	{		int nextWidth = currentWidth + image.second.x + interval;		if (nextWidth > dimensions.x)		{			currentHeight = nextHeight;			currentWidth = 0;			nextWidth = currentWidth + image.second.x + interval;		}		nextHeight = std::max(nextHeight, currentHeight + image.second.y + interval);		if (nextHeight > dimensions.y)			return std::nullopt; // failure - ran out of space		result.images[image.first] = Rect(Point(currentWidth, currentHeight), image.second);		currentWidth = nextWidth;	}	return result;}/// Arranges images to fit into texture atlas with automatic selection of image size/// Returns images arranged into 2d boxstatic AtlasLayout doAtlasPacking(const std::map<int, Point> & images){	// initial size of an atlas. Smaller size won't even fit tiniest H3 font	Point dimensions(128, 128);	for (;;)	{		auto result = tryAtlasPacking(dimensions, images);		if (result)			return *result;		// else - packing failed. Increase atlas size and try again		// increase width and height in alternating form: (64,64) -> (128,64) -> (128,128) ...		if (dimensions.x > dimensions.y)			dimensions.y *= 2;		else			dimensions.x *= 2;	}}void CBitmapFont::loadModFont(const std::string & modName, const ResourcePath & resource, std::unordered_map<CodePoint, EntryFNT> & loadedChars){	if (!CResourceHandler::get(modName)->existsResource(resource))	{		logGlobal->error("Failed to load font %s from mod %s", resource.getName(), modName);		return;	}	auto data = CResourceHandler::get(modName)->load(resource)->readAll();	std::string modLanguage = CGI->modh->getModLanguage(modName);	std::string modEncoding = Languages::getLanguageOptions(modLanguage).encoding;	uint32_t dataHeight = data.first[5];	maxHeight = std::max(maxHeight, dataHeight);	constexpr size_t symbolsInFile = 0x100;	constexpr size_t baseIndex = 32;	constexpr size_t offsetIndex = baseIndex + symbolsInFile*12;	constexpr size_t dataIndex = offsetIndex + symbolsInFile*4;	for (uint32_t charIndex = 0; charIndex < symbolsInFile; ++charIndex)	{		CodePoint codepoint = TextOperations::getUnicodeCodepoint(static_cast<char>(charIndex), modEncoding);		EntryFNT symbol;		symbol.leftOffset =  read_le_u32(data.first.get() + baseIndex + charIndex * 12 + 0);		symbol.width =       read_le_u32(data.first.get() + baseIndex + charIndex * 12 + 4);		symbol.rightOffset = read_le_u32(data.first.get() + baseIndex + charIndex * 12 + 8);		symbol.height = dataHeight;		uint32_t pixelDataOffset = read_le_u32(data.first.get() + offsetIndex + charIndex * 4);		uint32_t pixelsCount = dataHeight * symbol.width;		symbol.pixels.resize(pixelsCount);		uint8_t * pixelData = data.first.get() + dataIndex + pixelDataOffset;		std::copy_n(pixelData, pixelsCount, symbol.pixels.data() );		loadedChars[codepoint] = symbol;	}}CBitmapFont::CBitmapFont(const std::string & filename):	maxHeight(0){	ResourcePath resource("data/" + filename, EResType::BMP_FONT);	std::unordered_map<CodePoint, EntryFNT> loadedChars;	loadModFont("core", resource, loadedChars);	for(const auto & modName : VLC->modh->getActiveMods())	{		if (CResourceHandler::get(modName)->existsResource(resource))			loadModFont(modName, resource, loadedChars);	}	std::map<int, Point> atlasSymbol;	for (auto const & symbol : loadedChars)		atlasSymbol[symbol.first] = Point(symbol.second.width, symbol.second.height);	auto atlas = doAtlasPacking(atlasSymbol);	atlasImage = SDL_CreateRGBSurface(0, atlas.dimensions.x, atlas.dimensions.y, 8, 0, 0, 0, 0);	assert(atlasImage->format->palette != nullptr);	assert(atlasImage->format->palette->ncolors == 256);	atlasImage->format->palette->colors[0] = { 0, 255, 255, SDL_ALPHA_OPAQUE }; // transparency	atlasImage->format->palette->colors[1] = { 0, 0, 0, SDL_ALPHA_OPAQUE }; // black shadow	CSDL_Ext::fillSurface(atlasImage, CSDL_Ext::toSDL(Colors::CYAN));	CSDL_Ext::setColorKey(atlasImage, CSDL_Ext::toSDL(Colors::CYAN));	for (size_t i = 2; i < atlasImage->format->palette->ncolors; ++i)		atlasImage->format->palette->colors[i] = { 255, 255, 255, SDL_ALPHA_OPAQUE };	for (auto const	& symbol : loadedChars)	{		BitmapChar storedEntry;		storedEntry.leftOffset = symbol.second.leftOffset;		storedEntry.rightOffset = symbol.second.rightOffset;		storedEntry.positionInAtlas = atlas.images.at(symbol.first);		// Copy pixel data to atlas		uint8_t *dstPixels = static_cast<uint8_t*>(atlasImage->pixels);		uint8_t *dstLine   = dstPixels + storedEntry.positionInAtlas.y * atlasImage->pitch;		uint8_t *dst = dstLine + storedEntry.positionInAtlas.x;		for (size_t i = 0; i < storedEntry.positionInAtlas.h; ++i)		{			const uint8_t *srcPtr = symbol.second.pixels.data() + i * storedEntry.positionInAtlas.w;			uint8_t * dstPtr = dst + i * atlasImage->pitch;			std::copy_n(srcPtr, storedEntry.positionInAtlas.w, dstPtr);		}		chars[symbol.first] = storedEntry;	}	if (GH.screenHandler().getScalingFactor() != 1)	{		auto scaledSurface = CSDL_Ext::scaleSurfaceIntegerFactor(atlasImage, GH.screenHandler().getScalingFactor());		SDL_FreeSurface(atlasImage);		atlasImage = scaledSurface;	}}CBitmapFont::~CBitmapFont(){	SDL_FreeSurface(atlasImage);}size_t CBitmapFont::getLineHeight() const{	return maxHeight;}size_t CBitmapFont::getGlyphWidth(const char * data) const{	CodePoint localChar = TextOperations::getUnicodeCodepoint(data, 4);	auto iter = chars.find(localChar);	if (iter == chars.end())		return 0;	return iter->second.leftOffset + iter->second.positionInAtlas.w + iter->second.rightOffset;}bool CBitmapFont::canRepresentCharacter(const char *data) const{	CodePoint localChar = TextOperations::getUnicodeCodepoint(data, 4);	auto iter = chars.find(localChar);	return iter != chars.end();}bool CBitmapFont::canRepresentString(const std::string & data) const{	for(size_t i=0; i<data.size(); i += TextOperations::getUnicodeCharacterSize(data[i]))		if (!canRepresentCharacter(data.data() + i))			return false;	return true;}void CBitmapFont::renderCharacter(SDL_Surface * surface, const BitmapChar & character, const ColorRGBA & color, int &posX, int &posY) const{	int scalingFactor = GH.screenHandler().getScalingFactor();	posX += character.leftOffset * scalingFactor;	auto sdlColor = CSDL_Ext::toSDL(color);	if (atlasImage->format->palette)		SDL_SetPaletteColors(atlasImage->format->palette, &sdlColor, 255, 1);	else		SDL_SetSurfaceColorMod(atlasImage, color.r, color.g, color.b);	CSDL_Ext::blitSurface(atlasImage, character.positionInAtlas * scalingFactor, surface, Point(posX, posY));	posX += character.positionInAtlas.w * scalingFactor;	posX += character.rightOffset * scalingFactor;}void CBitmapFont::renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const{	if (data.empty())		return;	assert(surface);	int posX = pos.x;	int posY = pos.y;	for(size_t i=0; i<data.size(); i += TextOperations::getUnicodeCharacterSize(data[i]))	{		CodePoint codepoint = TextOperations::getUnicodeCodepoint(data.data() + i, data.size() - i);		auto iter = chars.find(codepoint);		if (iter != chars.end())			renderCharacter(surface, iter->second, color, posX, posY);	}}
 |