Files
MagicSetEditor2/src/data/symbol_font.cpp
T
twanvl 78edb91deb fixed splitting in symbol font
git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@138 0fc631ac-6414-0410-93d0-97cfa31319b6
2006-12-21 22:17:23 +00:00

326 lines
11 KiB
C++

//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
// ----------------------------------------------------------------------------- : Includes
#include <data/symbol_font.hpp>
#include <util/dynamic_arg.hpp>
#include <util/io/package_manager.hpp>
#include <util/rotation.hpp>
#include <render/text/element.hpp> // fot CharInfo
#include <script/image.hpp>
#include <util/error.hpp>
DECLARE_TYPEOF_COLLECTION(SymbolFont::DrawableSymbol);
DECLARE_TYPEOF_COLLECTION(SymbolInFontP);
// ----------------------------------------------------------------------------- : SymbolFont
// SymbolFont that is used for SymbolInFonts constructed with the default constructor
DECLARE_DYNAMIC_ARG(SymbolFont*, symbol_font_for_reading);
IMPLEMENT_DYNAMIC_ARG(SymbolFont*, symbol_font_for_reading, nullptr);
SymbolFont::SymbolFont()
: img_size(12), min_size(1)
, spacing(1,1)
, scale_text(false)
, text_margin_left(0), text_margin_right(0)
, text_margin_top(0), text_margin_bottom(0)
, text_alignment(ALIGN_MIDDLE_CENTER)
, merge_numbers(false)
{}
String SymbolFont::typeNameStatic() { return _("symbol-font"); }
String SymbolFont::typeName() const { return _("symbol-font"); }
SymbolFontP SymbolFont::byName(const String& name) {
return packages.open<SymbolFont>(name + _(".mse-symbol-font"));
}
IMPLEMENT_REFLECTION(SymbolFont) {
tag.addAlias(300, _("text align"), _("text alignment"));
REFLECT_N("image font size", img_size);
REFLECT_N("scale down to", min_size);
REFLECT_N("horizontal space", spacing.width);
REFLECT_N("vertical space", spacing.height);
WITH_DYNAMIC_ARG(symbol_font_for_reading, this);
REFLECT(symbols);
REFLECT(text_font);
REFLECT(scale_text);
REFLECT(merge_numbers);
REFLECT(text_margin_left);
REFLECT(text_margin_right);
REFLECT(text_margin_top);
REFLECT(text_margin_bottom);
REFLECT(text_alignment);
}
// ----------------------------------------------------------------------------- : SymbolInFont
/// A symbol in a symbol font
class SymbolInFont {
public:
SymbolInFont();
/// Get a shrunk, zoomed bitmap
Bitmap getBitmap(Context& ctx, Package& pkg, double size);
/// Size of a (zoomed) bitmap
/** This is the size of the resulting image, it does NOT convert back to internal coordinates */
RealSize size(Context& ctx, Package& pkg, double size);
String code; ///< Code for this symbol
private:
ScriptableImage image; ///< The image for this symbol
double img_size; ///< Font size used by the image
wxSize actual_size; ///< Actual image size, only known after loading the image
/// Cached bitmaps for different sizes
map<double, Bitmap> bitmaps;
DECLARE_REFLECTION();
};
SymbolInFont::SymbolInFont()
: actual_size(0,0)
{
assert(symbol_font_for_reading());
img_size = symbol_font_for_reading()->img_size;
if (img_size <= 0) img_size = 1;
}
Bitmap SymbolInFont::getBitmap(Context& ctx, Package& pkg, double size) {
// is this bitmap already loaded/generated?
Bitmap& bmp = bitmaps[size];
if (!bmp.Ok()) {
// generate new bitmap
if (!image) {
throw Error(_("No image specified for symbol with code '") + code + _("' in symbol font."));
}
Image img = image.generate(ctx, pkg)->image;
actual_size = wxSize(img.GetWidth(), img.GetHeight());
// scale to match expected size
Image resampled_image(actual_size.GetWidth() * size / img_size,
actual_size.GetHeight() * size / img_size, false);
if (!resampled_image.Ok()) return Bitmap(1,1);
resample(img, resampled_image);
// convert to bitmap, store for later use
bmp = Bitmap(resampled_image);
}
return bmp;
}
RealSize SymbolInFont::size(Context& ctx, Package& pkg, double size) {
if (actual_size.GetWidth() == 0) {
// we don't know what size the image will be
getBitmap(ctx, pkg, size);
}
return wxSize(actual_size * size / img_size);
}
IMPLEMENT_REFLECTION(SymbolInFont) {
REFLECT(code);
REFLECT(image);
REFLECT_N("image font size", img_size);
}
// ----------------------------------------------------------------------------- : SymbolFont : splitting
class SymbolFont::DrawableSymbol {
public:
DrawableSymbol(const String& text, SymbolInFont* symbol)
: text(text), symbol(symbol)
{}
String text; ///< Original text
SymbolInFont* symbol; ///< Symbol to draw, if nullptr, use the default symbol and draw the text
};
void SymbolFont::split(const String& text, SplitSymbols& out) const {
// read a single symbol until we are done with the text
for (size_t pos = 0 ; pos < text.size() ; ) {
// 1. check merged numbers
if (merge_numbers && pos + 1 < text.size()) {
size_t num_count = text.find_first_not_of(_("0123456789"), pos) - pos;
if (num_count >= 2) {
// draw single symbol for the whole number
out.push_back(DrawableSymbol(text.substr(pos, num_count), nullptr));
pos += num_count;
goto next_symbol;
}
}
// 2. check symbol list
FOR_EACH_CONST(sym, symbols) {
if (!sym->code.empty() && is_substr(text, pos, sym->code)) { // symbol matches
out.push_back(DrawableSymbol(sym->code, sym.get()));
pos += sym->code.size();
goto next_symbol; // continue two levels
}
}
// 3. unknown code, draw single character as text
out.push_back(DrawableSymbol(text.substr(pos, 1), 0));
pos += 1;
next_symbol:;
}
}
SymbolInFont* SymbolFont::defaultSymbol() const {
FOR_EACH_CONST(sym, symbols) {
if (sym->code.empty()) return sym.get();
}
return nullptr;
}
// ----------------------------------------------------------------------------- : SymbolFont : drawing
void SymbolFont::draw(RotatedDC& dc, Context& ctx, const RealRect& rect, double font_size, const Alignment& align, const String& text) {
SplitSymbols symbols;
split(text, symbols);
draw(dc, ctx, rect, font_size, align, symbols);
}
void SymbolFont::draw(RotatedDC& dc, Context& ctx, RealRect rect, double font_size, const Alignment& align, const SplitSymbols& text) {
FOR_EACH_CONST(sym, text) {
RealSize size = dc.trInvS(symbolSize(ctx, dc.trS(font_size), sym));
RealRect sym_rect = split_left(rect, size);
if (sym.symbol) {
drawSymbol( dc, ctx, sym_rect, font_size, align, *sym.symbol);
} else {
drawWithText(dc, ctx, sym_rect, font_size, align, sym.text);
}
}
}
void SymbolFont::drawSymbol (RotatedDC& dc, Context& ctx, const RealRect& rect, double font_size, const Alignment& align, SymbolInFont& sym) {
// find bitmap
Bitmap bmp = sym.getBitmap(ctx, *this, dc.trS(font_size));
// draw aligned in the rectangle
dc.DrawBitmap(bmp, align_in_rect(align, dc.trInvS(RealSize(bmp.GetWidth(), bmp.GetHeight())), rect));
}
void SymbolFont::drawWithText(RotatedDC& dc, Context& ctx, const RealRect& rect, double font_size, const Alignment& align, const String& text) {
// 1. draw background bitmap
// Size and position of symbol
RealRect sym_rect = rect;
// find and draw background bitmap
SymbolInFont* def = defaultSymbol();
if (def) {
Bitmap bmp = def->getBitmap(ctx, *this, dc.trS(font_size));
// align symbol
sym_rect.size() = dc.trInvS(RealSize(bmp.GetWidth(), bmp.GetHeight()));
sym_rect.position() = align_in_rect(align, sym_rect.size(), rect);
// draw
dc.DrawBitmap(bmp, sym_rect.position());
}
// 2. draw text
if (!text_font) return;
// subtract margins from size
sym_rect.x += text_margin_left;
sym_rect.y += text_margin_top;
sym_rect.width -= text_margin_left + text_margin_right;
sym_rect.height -= text_margin_top + text_margin_bottom;
// setup text, shrink it
double size = text_font->size; // TODO : incorporate shrink factor?
RealSize ts;
while (true) {
if (size <= 0) return; // text too small
dc.SetFont(text_font->font, size);
ts = dc.GetTextExtent(text);
if (ts.width <= sym_rect.width && ts.height <= sym_rect.height) {
break; // text fits
} else {
// text doesn't fit
size -= dc.getFontSizeStep();
}
}
// align text
RealPoint text_pos = align_in_rect(text_alignment, ts, sym_rect);
// draw text
if (text_font->hasShadow()) {
dc.SetTextForeground(text_font->shadow_color);
dc.DrawText(text, text_pos + text_font->shadow_displacement);
}
dc.SetTextForeground(text_font->color);
dc.DrawText(text, text_pos);
}
// ----------------------------------------------------------------------------- : SymbolFont : sizes
void SymbolFont::getCharInfo(RotatedDC& dc, Context& ctx, double font_size, const String& text, vector<CharInfo>& out) {
SplitSymbols symbols;
split(text, symbols);
getCharInfo(dc, ctx, font_size, symbols, out);
}
void SymbolFont::getCharInfo(RotatedDC& dc, Context& ctx, double font_size, const SplitSymbols& text, vector<CharInfo>& out) {
FOR_EACH_CONST(sym, text) {
size_t count = sym.text.size();
RealSize size = dc.trInvS(symbolSize(ctx, dc.trS(font_size), sym));
out.insert(out.end(), count, RealSize(size.width / count, size.height)); // divide into count parts
}
}
RealSize SymbolFont::symbolSize(Context& ctx, double font_size, const DrawableSymbol& sym) {
if (sym.symbol) {
return addDiagonal(sym.symbol->size(ctx, *this, font_size), spacing);
} else {
return defaultSymbolSize(ctx, font_size);
}
}
RealSize SymbolFont::defaultSymbolSize(Context& ctx, double font_size) {
SymbolInFont* def = defaultSymbol();
if (def) {
return addDiagonal(def->size(ctx, *this, font_size), spacing);
} else {
return addDiagonal(RealSize(1,1), spacing);
}
}
// ----------------------------------------------------------------------------- : SymbolFontRef
SymbolFontRef::SymbolFontRef()
: size(12)
, scale_down_to(1)
, alignment(ALIGN_MIDDLE_CENTER)
{}
bool SymbolFontRef::valid() const {
return !!font;
}
bool SymbolFontRef::update(Context& ctx) {
if (name.update(ctx)) {
// font name changed, load another font
loadFont();
return true;
} else {
if (!font) loadFont();
return false;
}
}
void SymbolFontRef::initDependencies(Context& ctx, const Dependency& dep) const {
name.initDependencies(ctx, dep);
}
void SymbolFontRef::loadFont() {
if (name().empty()) {
font = SymbolFontP();
} else {
font = SymbolFont::byName(name);
}
}
IMPLEMENT_REFLECTION(SymbolFontRef) {
REFLECT(name);
REFLECT(size);
REFLECT(scale_down_to);
REFLECT(alignment);
}