mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 13:06:59 -04:00
Implemented exporting symbol fonts;
Rendering symbols to an image always uses anti-aliassing (by downsampling from a large size); Finished the spoiler export template; Added <soft-line> tag to make line breaks use the line height for soft line breaks git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@440 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
@@ -48,6 +48,7 @@ struct ExportInfo {
|
||||
String directory_relative; ///< The directory for storing extra files (or "" if !export->create_directory)
|
||||
/// This is just the directory name
|
||||
String directory_absolute; ///< The absolute path of the directory
|
||||
set<String> exported_images; ///< Images (from symbol font) already exported
|
||||
};
|
||||
|
||||
DECLARE_DYNAMIC_ARG(ExportInfo*, export_info);
|
||||
|
||||
+66
-23
@@ -74,6 +74,9 @@ class SymbolInFont : public IntrusivePtrBase<SymbolInFont> {
|
||||
public:
|
||||
SymbolInFont();
|
||||
|
||||
/// Get a shrunk, zoomed image
|
||||
Image getImage(Package& pkg, double size);
|
||||
|
||||
/// Get a shrunk, zoomed bitmap
|
||||
Bitmap getBitmap(Package& pkg, double size);
|
||||
|
||||
@@ -107,23 +110,26 @@ SymbolInFont::SymbolInFont()
|
||||
if (img_size <= 0) img_size = 1;
|
||||
}
|
||||
|
||||
Image SymbolInFont::getImage(Package& pkg, double size) {
|
||||
// generate new image
|
||||
if (!image.isReady()) {
|
||||
throw Error(_("No image specified for symbol with code '") + code + _("' in symbol font."));
|
||||
}
|
||||
Image img = image.generate(GeneratedImage::Options(0, 0, &pkg));
|
||||
actual_size = wxSize(img.GetWidth(), img.GetHeight());
|
||||
// scale to match expected size
|
||||
Image resampled_image((int) (actual_size.GetWidth() * size / img_size),
|
||||
(int) (actual_size.GetHeight() * size / img_size), false);
|
||||
if (!resampled_image.Ok()) return Image(1,1);
|
||||
resample(img, resampled_image);
|
||||
return resampled_image;
|
||||
}
|
||||
Bitmap SymbolInFont::getBitmap(Package& pkg, double size) {
|
||||
// is this bitmap already loaded/generated?
|
||||
Bitmap& bmp = bitmaps[size];
|
||||
if (!bmp.Ok()) {
|
||||
// generate new bitmap
|
||||
if (!image.isReady()) {
|
||||
throw Error(_("No image specified for symbol with code '") + code + _("' in symbol font."));
|
||||
}
|
||||
Image img = image.generate(GeneratedImage::Options(0, 0, &pkg));
|
||||
actual_size = wxSize(img.GetWidth(), img.GetHeight());
|
||||
// scale to match expected size
|
||||
Image resampled_image((int) (actual_size.GetWidth() * size / img_size),
|
||||
(int) (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);
|
||||
// generate image, convert to bitmap, store for later use
|
||||
bmp = Bitmap(getImage(pkg, size));
|
||||
}
|
||||
return bmp;
|
||||
}
|
||||
@@ -166,16 +172,6 @@ IMPLEMENT_REFLECTION(SymbolInFont) {
|
||||
|
||||
// ----------------------------------------------------------------------------- : 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() ; ) {
|
||||
@@ -286,6 +282,53 @@ void SymbolFont::drawWithText(RotatedDC& dc, const RealRect& rect, double font_s
|
||||
dc.DrawText(text, text_pos);
|
||||
}
|
||||
|
||||
Image SymbolFont::getImage(double font_size, const DrawableSymbol& sym) {
|
||||
if (sym.symbol) {
|
||||
return sym.symbol->getImage(*this, font_size);
|
||||
} else {
|
||||
if (!text_font) return Image(1,1); // failed
|
||||
// draw on default symbol
|
||||
SymbolInFont* def = defaultSymbol();
|
||||
if (!def) return Image(1,1); // failed
|
||||
Bitmap bmp(def->getImage(*this, font_size));
|
||||
// memory dc to work with
|
||||
wxMemoryDC dc;
|
||||
dc.SelectObject(bmp);
|
||||
RealRect sym_rect(0,0,bmp.GetWidth(),bmp.GetHeight());
|
||||
RotatedDC rdc(dc, 0, sym_rect, 1, QUALITY_AA);
|
||||
// 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 def->getImage(*this, font_size); // text too small
|
||||
rdc.SetFont(*text_font, size / text_font->size);
|
||||
ts = rdc.GetTextExtent(sym.text);
|
||||
if (ts.width <= sym_rect.width && ts.height <= sym_rect.height) {
|
||||
break; // text fits
|
||||
} else {
|
||||
// text doesn't fit
|
||||
size -= rdc.getFontSizeStep();
|
||||
}
|
||||
}
|
||||
// align text
|
||||
RealPoint text_pos = align_in_rect(text_alignment, ts, sym_rect);
|
||||
// draw text
|
||||
if (text_font->hasShadow()) {
|
||||
rdc.SetTextForeground(text_font->shadow_color);
|
||||
rdc.DrawText(sym.text, text_pos + text_font->shadow_displacement);
|
||||
}
|
||||
rdc.SetTextForeground(text_font->color);
|
||||
rdc.DrawText(sym.text, text_pos);
|
||||
// done
|
||||
dc.SelectObject(wxNullBitmap);
|
||||
return bmp.ConvertToImage();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolFont : sizes
|
||||
|
||||
|
||||
@@ -35,12 +35,22 @@ class SymbolFont : public Packaged {
|
||||
// Script update
|
||||
void update(Context& ctx) const;
|
||||
|
||||
class DrawableSymbol;
|
||||
/// A symbol to be drawn
|
||||
class DrawableSymbol {
|
||||
public:
|
||||
inline 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
|
||||
};
|
||||
typedef vector<DrawableSymbol> SplitSymbols;
|
||||
|
||||
/// Split a string into separate symbols for drawing and for determining their size
|
||||
void split(const String& text, SplitSymbols& out) const;
|
||||
|
||||
/// Draw a piece of text prepared using split
|
||||
/// Draw a piece of text
|
||||
void draw(RotatedDC& dc, Context& ctx, const RealRect& rect, double font_size, const Alignment& align, const String& text);
|
||||
/// Get information on characters in a string
|
||||
void getCharInfo(RotatedDC& dc, Context& ctx, double font_size, const String& text, vector<CharInfo>& out);
|
||||
@@ -50,6 +60,9 @@ class SymbolFont : public Packaged {
|
||||
/// Get information on characters in a string
|
||||
void getCharInfo(RotatedDC& dc, double font_size, const SplitSymbols& text, vector<CharInfo>& out);
|
||||
|
||||
/// Get the image for a symbol
|
||||
Image getImage(double font_size, const DrawableSymbol& symbol);
|
||||
|
||||
static String typeNameStatic();
|
||||
virtual String typeName() const;
|
||||
|
||||
|
||||
@@ -19,6 +19,58 @@
|
||||
ScriptType GeneratedImage::type() const { return SCRIPT_IMAGE; }
|
||||
String GeneratedImage::typeName() const { return _TYPE_("image"); }
|
||||
|
||||
Image GeneratedImage::generateConform(const Options& options) const {
|
||||
return conform_image(generate(options),options);
|
||||
}
|
||||
|
||||
Image conform_image(const Image& img, const GeneratedImage::Options& options) {
|
||||
Image image = img;
|
||||
// resize?
|
||||
int iw = image.GetWidth(), ih = image.GetHeight();
|
||||
if ((iw == options.width && ih == options.height) || (options.width == 0 && options.height == 0)) {
|
||||
// already the right size
|
||||
} else if (options.height == 0) {
|
||||
// width is given, determine height
|
||||
int h = options.width * ih / iw;
|
||||
Image resampled_image(options.width, h, false);
|
||||
resample(image, resampled_image);
|
||||
image = resampled_image;
|
||||
} else if (options.width == 0) {
|
||||
// height is given, determine width
|
||||
int w = options.height * iw / ih;
|
||||
Image resampled_image(w, options.height, false);
|
||||
resample(image, resampled_image);
|
||||
image = resampled_image;
|
||||
} else if (options.preserve_aspect == ASPECT_FIT) {
|
||||
// determine actual size of resulting image
|
||||
int w, h;
|
||||
if (iw * options.height > ih * options.width) { // too much height requested
|
||||
w = options.width;
|
||||
h = options.width * ih / iw;
|
||||
} else {
|
||||
w = options.height * iw / ih;
|
||||
h = options.height;
|
||||
}
|
||||
Image resampled_image(w, h, false);
|
||||
resample(image, resampled_image);
|
||||
image = resampled_image;
|
||||
} else {
|
||||
Image resampled_image(options.width, options.height, false);
|
||||
if (options.preserve_aspect == ASPECT_BORDER && (options.width < options.height * 3) && (options.height < options.width * 3)) {
|
||||
// preserve the aspect ratio if there is not too much difference
|
||||
resample_preserve_aspect(image, resampled_image);
|
||||
} else {
|
||||
resample(image, resampled_image);
|
||||
}
|
||||
image = resampled_image;
|
||||
}
|
||||
// saturate?
|
||||
if (options.saturate) {
|
||||
saturate(image, 40);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : BlankImage
|
||||
|
||||
Image BlankImage::generate(const Options& opt) const {
|
||||
@@ -165,7 +217,7 @@ Image SymbolToImage::generate(const Options& opt) const {
|
||||
} else {
|
||||
the_symbol = opt.local_package->readFile<SymbolP>(filename);
|
||||
}
|
||||
return render_symbol(the_symbol, *variation->filter, variation->border_radius);
|
||||
return render_symbol(the_symbol, *variation->filter, variation->border_radius, max(100, 3*max(opt.width,opt.height)));
|
||||
}
|
||||
bool SymbolToImage::operator == (const GeneratedImage& that) const {
|
||||
const SymbolToImage* that2 = dynamic_cast<const SymbolToImage*>(&that);
|
||||
@@ -190,7 +242,7 @@ Image ImageValueToImage::generate(const Options& opt) const {
|
||||
image.LoadFile(*image_file);
|
||||
}
|
||||
if (!image.Ok()) {
|
||||
image = Image(opt.width, opt.height);
|
||||
image = Image(max(1,opt.width), max(1,opt.height));
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ class GeneratedImage : public ScriptValue {
|
||||
Package* local_package; ///< Package to load symbols and ImageValue images from
|
||||
};
|
||||
|
||||
/// Generate the image, and conform to the options
|
||||
Image generateConform(const Options&) const;
|
||||
/// Generate the image
|
||||
virtual Image generate(const Options&) const = 0;
|
||||
/// How must the image be combined with the background?
|
||||
@@ -53,6 +55,9 @@ class GeneratedImage : public ScriptValue {
|
||||
virtual String typeName() const;
|
||||
};
|
||||
|
||||
/// Resize an image to conform to the options
|
||||
Image conform_image(const Image&, const GeneratedImage::Options&);
|
||||
|
||||
// ----------------------------------------------------------------------------- : BlankImage
|
||||
|
||||
/// An image generator that returns a blank image
|
||||
|
||||
@@ -56,10 +56,6 @@ ImagesExportWindow::ImagesExportWindow(Window* parent, const SetP& set)
|
||||
|
||||
// ----------------------------------------------------------------------------- : Exporting the images
|
||||
|
||||
bool is_filename_char(Char c) {
|
||||
return isAlnum(c) || c == _(' ') || c == _('_') || c == _('-') || c == _('.');
|
||||
}
|
||||
|
||||
void ImagesExportWindow::onOk(wxCommandEvent&) {
|
||||
// Update settings
|
||||
GameSettings& gs = settings.gameSettingsFor(*set->game);
|
||||
@@ -85,16 +81,7 @@ void ImagesExportWindow::onOk(wxCommandEvent&) {
|
||||
String filename = untag(ctx.eval(*filename_script)->toString());
|
||||
if (!filename) continue; // no filename -> no saving
|
||||
// sanitize filename
|
||||
String clean_filename;
|
||||
FOR_EACH(c, filename) {
|
||||
if (is_filename_char(c)) {
|
||||
clean_filename += c;
|
||||
}
|
||||
}
|
||||
if (clean_filename.empty() || starts_with(clean_filename, _("."))) {
|
||||
clean_filename = _("no-name") + clean_filename;
|
||||
}
|
||||
fn.SetFullName(clean_filename);
|
||||
fn.SetFullName(clean_filename(filename));
|
||||
// does the file exist?
|
||||
if (fn.FileExists()) {
|
||||
// file exists, what to do?
|
||||
|
||||
@@ -72,13 +72,13 @@ const size_t param_colors_count = sizeof(param_colors) / sizeof(param_colors[0])
|
||||
struct TextElementsFromString {
|
||||
// What formatting is enabled?
|
||||
int bold, italic, symbol;
|
||||
int soft, kwpph, param, line;
|
||||
int soft, kwpph, param, line, soft_line;
|
||||
int code, code_kw, code_string, param_ref, error;
|
||||
int param_id;
|
||||
bool bracket;
|
||||
|
||||
TextElementsFromString()
|
||||
: bold(0), italic(0), symbol(0), soft(0), kwpph(0), param(0), line(0)
|
||||
: bold(0), italic(0), symbol(0), soft(0), kwpph(0), param(0), line(0), soft_line(0)
|
||||
, code(0), code_kw(0), code_string(0), param_ref(0), error(0)
|
||||
, param_id(0), bracket(false) {}
|
||||
|
||||
@@ -125,6 +125,8 @@ struct TextElementsFromString {
|
||||
else if (is_substr(text, tag_start, _("</atom-param"))) param -= 1;
|
||||
else if (is_substr(text, tag_start, _( "<line"))) line += 1;
|
||||
else if (is_substr(text, tag_start, _("</line"))) line -= 1;
|
||||
else if (is_substr(text, tag_start, _( "<soft-line"))) soft_line += 1;
|
||||
else if (is_substr(text, tag_start, _("</soft-line"))) soft_line -= 1;
|
||||
else if (is_substr(text, tag_start, _("<atom"))) {
|
||||
// 'atomic' indicator
|
||||
size_t end_tag = min(end, match_close_tag(text, tag_start));
|
||||
@@ -178,7 +180,8 @@ struct TextElementsFromString {
|
||||
bracket ? pos + 2 : pos + 1,
|
||||
font,
|
||||
soft > 0 ? DRAW_ACTIVE : DRAW_NORMAL,
|
||||
line > 0 ? BREAK_LINE : BREAK_HARD);
|
||||
line > 0 ? BREAK_LINE :
|
||||
soft_line > 0 ? BREAK_SOFT : BREAK_HARD);
|
||||
}
|
||||
if (bracket) {
|
||||
e->content = String(LEFT_ANGLE_BRACKET) + c + RIGHT_ANGLE_BRACKET;
|
||||
|
||||
@@ -34,7 +34,8 @@ enum DrawWhat
|
||||
enum LineBreak
|
||||
{ BREAK_NO // no line break ever
|
||||
, BREAK_MAYBE // break here when in "direction:vertical" mode
|
||||
, BREAK_SOFT // optional line break (' ')
|
||||
, BREAK_SPACE // optional line break (' ')
|
||||
, BREAK_SOFT // always a line break, spacing as a soft break
|
||||
, BREAK_HARD // always a line break ('\n')
|
||||
, BREAK_LINE // line break with a separator line (<line>)
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ void FontTextElement::getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>&
|
||||
} else {
|
||||
RealSize s = dc.GetTextExtent(content.substr(line_start - this->start, i - line_start + 1));
|
||||
out.push_back(CharInfo(RealSize(s.width - prev_width, s.height),
|
||||
c == _(' ') ? BREAK_SOFT : BREAK_MAYBE
|
||||
c == _(' ') ? BREAK_SPACE : BREAK_MAYBE
|
||||
));
|
||||
prev_width = s.width;
|
||||
}
|
||||
|
||||
@@ -419,7 +419,11 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars,
|
||||
bool accept_word = false; // the current word should be added to the line
|
||||
bool hide_breaker = true; // hide the \n or _(' ') that caused a line break
|
||||
double line_height_multiplier = 1; // multiplier for line height for next line top
|
||||
if (c.break_after == BREAK_HARD) {
|
||||
if (c.break_after == BREAK_SOFT) {
|
||||
break_now = true;
|
||||
accept_word = true;
|
||||
line_height_multiplier = style.line_height_soft;
|
||||
} else if (c.break_after == BREAK_HARD) {
|
||||
break_now = true;
|
||||
accept_word = true;
|
||||
line_height_multiplier = style.line_height_hard;
|
||||
@@ -428,7 +432,7 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars,
|
||||
break_now = true;
|
||||
accept_word = true;
|
||||
line_height_multiplier = style.line_height_line;
|
||||
} else if (c.break_after == BREAK_SOFT && style.field().multi_line) {
|
||||
} else if (c.break_after == BREAK_SPACE && style.field().multi_line) {
|
||||
// Soft break == end of word
|
||||
accept_word = true;
|
||||
} else if (c.break_after == BREAK_MAYBE && style.direction == TOP_TO_BOTTOM) {
|
||||
@@ -563,13 +567,13 @@ void TextViewer::alignLines(RotatedDC& dc, const vector<CharInfo>& chars, const
|
||||
double hdelta = s.width - width; // amount of space to distribute
|
||||
int count = 0; // distribute it among this many words
|
||||
for (size_t k = l.start + 1 ; k < l.end() - 1 ; ++k) {
|
||||
if (chars[k].break_after == BREAK_SOFT) ++count;
|
||||
if (chars[k].break_after == BREAK_SPACE) ++count;
|
||||
}
|
||||
if (count == 0) count = 1; // prevent div by 0
|
||||
int i = 0; size_t j = l.start;
|
||||
FOR_EACH(c, l.positions) {
|
||||
c += hdelta * i / count;
|
||||
if (j < l.end() && chars[j++].break_after == BREAK_SOFT) i++;
|
||||
if (j < l.end() && chars[j++].break_after == BREAK_SPACE) i++;
|
||||
}
|
||||
} else {
|
||||
// simple alignment
|
||||
|
||||
+162
-26
@@ -20,6 +20,19 @@
|
||||
#include <wx/wfstream.h>
|
||||
#include <wx/filename.h>
|
||||
|
||||
DECLARE_TYPEOF_COLLECTION(SymbolFont::DrawableSymbol);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Utility
|
||||
|
||||
// Make sure we can export files to a data directory
|
||||
void guard_export_info(const String& fun) {
|
||||
if (!export_info()) {
|
||||
throw ScriptError(_("Can only use ") + fun + _(" from export templates"));
|
||||
} else if (export_info()->directory_relative.empty()) {
|
||||
throw ScriptError(_("Can only use ") + fun + _(" when 'create directory' is set to true"));
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : HTML
|
||||
|
||||
// An HTML tag
|
||||
@@ -124,11 +137,52 @@ class TagStack {
|
||||
}
|
||||
};
|
||||
|
||||
String symbols_to_html(const String& str, const SymbolFontP& symbol_font) {
|
||||
return str; // TODO
|
||||
// html-escape a string
|
||||
String html_escape(const String& str) {
|
||||
String ret;
|
||||
FOR_EACH_CONST(c, str) {
|
||||
if (c == _('\1') || c == _('<')) { // escape <
|
||||
ret += _("<");
|
||||
} else if (c == _('>')) { // escape >
|
||||
ret += _(">");
|
||||
} else if (c == _('&')) { // escape &
|
||||
ret += _("&");
|
||||
} else if (c == _('\'')) { // escape '
|
||||
ret += _("'");
|
||||
} else if (c == _('\"')) { // escape "
|
||||
ret += _(""");
|
||||
} else if (c >= 0x80) { // escape non ascii
|
||||
ret += String(_("&#")) << (int)c << _(';');
|
||||
} else {
|
||||
ret += c;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// write symbols to html
|
||||
String symbols_to_html(const String& str, SymbolFont& symbol_font, double size) {
|
||||
guard_export_info(_("symbols_to_html"));
|
||||
ExportInfo& ei = *export_info();
|
||||
vector<SymbolFont::DrawableSymbol> symbols;
|
||||
symbol_font.split(str, symbols);
|
||||
String html;
|
||||
FOR_EACH(sym, symbols) {
|
||||
String filename = symbol_font.name() + _("-") + clean_filename(sym.text) + _(".png");
|
||||
html += _("<img src='") + filename + _("' alt='") + html_escape(sym.text) + _("'>");
|
||||
if (ei.exported_images.insert(filename).second) {
|
||||
// save symbol image
|
||||
Image img = symbol_font.getImage(size, sym);
|
||||
wxFileName fn;
|
||||
fn.SetPath(ei.directory_absolute);
|
||||
fn.SetFullName(filename);
|
||||
img.SaveFile(fn.GetFullPath());
|
||||
}
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
String to_html(const String& str_in, const SymbolFontP& symbol_font) {
|
||||
String to_html(const String& str_in, const SymbolFontP& symbol_font, double symbol_size) {
|
||||
String str = remove_tag_contents(str_in,_("<sep-soft"));
|
||||
String ret;
|
||||
Tag bold (_("<b>"), _("</b>")),
|
||||
@@ -149,12 +203,12 @@ String to_html(const String& str_in, const SymbolFontP& symbol_font) {
|
||||
} else if (is_substr(str, i, _("/i"))) {
|
||||
tags.close(ret, italic);
|
||||
} else if (is_substr(str, i, _("sym"))) {
|
||||
tags.close(ret, symbol);
|
||||
tags.open (ret, symbol);
|
||||
} else if (is_substr(str, i, _("/sym"))) {
|
||||
if (!symbols.empty()) {
|
||||
// write symbols in a special way
|
||||
tags.write_pending_tags(ret);
|
||||
ret += symbols_to_html(symbols, symbol_font);
|
||||
ret += symbols_to_html(symbols, *symbol_font, symbol_size);
|
||||
symbols.clear();
|
||||
}
|
||||
tags.close(ret, symbol);
|
||||
@@ -184,7 +238,7 @@ String to_html(const String& str_in, const SymbolFontP& symbol_font) {
|
||||
// end of input
|
||||
if (!symbols.empty()) {
|
||||
tags.write_pending_tags(ret);
|
||||
ret += symbols_to_html(symbols, symbol_font);
|
||||
ret += symbols_to_html(symbols, *symbol_font, symbol_size);
|
||||
symbols.clear();
|
||||
}
|
||||
tags.close_all(ret);
|
||||
@@ -194,8 +248,91 @@ String to_html(const String& str_in, const SymbolFontP& symbol_font) {
|
||||
// convert a tagged string to html
|
||||
SCRIPT_FUNCTION(to_html) {
|
||||
SCRIPT_PARAM(String, input);
|
||||
SymbolFontP symbol_font; // TODO
|
||||
SCRIPT_RETURN(to_html(input, symbol_font));
|
||||
// symbol font?
|
||||
SymbolFontP symbol_font;
|
||||
SCRIPT_OPTIONAL_PARAM_N(String, _("symbol font"), font_name) {
|
||||
symbol_font = SymbolFont::byName(font_name);
|
||||
symbol_font->update(ctx);
|
||||
}
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("symbol font size"), symbol_font_size);
|
||||
if (symbol_font_size <= 0) symbol_font_size = 12; // a default
|
||||
SCRIPT_RETURN(to_html(input, symbol_font, symbol_font_size));
|
||||
}
|
||||
|
||||
// convert a symbol string to html
|
||||
SCRIPT_FUNCTION(symbols_to_html) {
|
||||
SCRIPT_PARAM(String, input);
|
||||
SCRIPT_PARAM_N(String, _("symbol font"), font_name);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("symbol font size"), symbol_font_size);
|
||||
SymbolFontP symbol_font = SymbolFont::byName(font_name);
|
||||
symbol_font->update(ctx);
|
||||
if (symbol_font_size <= 0) symbol_font_size = 12; // a default
|
||||
SCRIPT_RETURN(symbols_to_html(input, *symbol_font, symbol_font_size));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : BB Code
|
||||
|
||||
String to_bbcode(const String& str_in) {
|
||||
String str = remove_tag_contents(str_in,_("<sep-soft"));
|
||||
String ret;
|
||||
Tag bold (_("[b]"), _("[/b]")),
|
||||
italic(_("[i]"), _("[/i]"));
|
||||
TagStack tags;
|
||||
String symbols;
|
||||
for (size_t i = 0 ; i < str.size() ; ) {
|
||||
Char c = str.GetChar(i);
|
||||
if (c == _('<')) {
|
||||
++i;
|
||||
if (is_substr(str, i, _("b"))) {
|
||||
tags.open (ret, bold);
|
||||
} else if (is_substr(str, i, _("/b"))) {
|
||||
tags.close(ret, bold);
|
||||
} else if (is_substr(str, i, _("i"))) {
|
||||
tags.open (ret, italic);
|
||||
} else if (is_substr(str, i, _("/i"))) {
|
||||
tags.close(ret, italic);
|
||||
} /*else if (is_substr(str, i, _("sym"))) {
|
||||
tags.open (ret, symbol);
|
||||
} else if (is_substr(str, i, _("/sym"))) {
|
||||
if (!symbols.empty()) {
|
||||
// write symbols in a special way
|
||||
tags.write_pending_tags(ret);
|
||||
ret += symbols_to_html(symbols, symbol_font);
|
||||
symbols.clear();
|
||||
}
|
||||
tags.close(ret, symbol);
|
||||
}*/
|
||||
i = skip_tag(str, i-1);
|
||||
} else {
|
||||
// normal character
|
||||
tags.write_pending_tags(ret);
|
||||
++i;
|
||||
// if (symbol.opened > 0 && symbol_font) {
|
||||
// symbols += c; // write as symbols instead
|
||||
// } else {
|
||||
if (c == _('\1')) { // unescape <
|
||||
ret += _("<");
|
||||
} else {
|
||||
ret += c;
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
// end of input
|
||||
/* if (!symbols.empty()) {
|
||||
tags.write_pending_tags(ret);
|
||||
ret += symbols_to_html(symbols, symbol_font);
|
||||
symbols.clear();
|
||||
}*/
|
||||
tags.close_all(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// convert a tagged string to BBCode
|
||||
SCRIPT_FUNCTION(to_bbcode) {
|
||||
SCRIPT_PARAM(String, input);
|
||||
throw "TODO";
|
||||
// SCRIPT_RETURN(to_bbcode(input, symbol_font));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Text
|
||||
@@ -208,14 +345,6 @@ SCRIPT_FUNCTION(to_text) {
|
||||
|
||||
// ----------------------------------------------------------------------------- : Files
|
||||
|
||||
void guard_export_info(const String& fun) {
|
||||
if (!export_info()) {
|
||||
throw ScriptError(_("Can only use ") + fun + _(" from export templates"));
|
||||
} else if (export_info()->directory_relative.empty()) {
|
||||
throw ScriptError(_("Can only use ") + fun + _(" when 'create directory' is set to true"));
|
||||
}
|
||||
}
|
||||
|
||||
// copy from source package -> destination directory, return new filename (relative)
|
||||
SCRIPT_FUNCTION(copy_file) {
|
||||
guard_export_info(_("copy_file"));
|
||||
@@ -252,21 +381,27 @@ SCRIPT_FUNCTION(write_text_file) {
|
||||
SCRIPT_FUNCTION(write_image_file) {
|
||||
guard_export_info(_("write_image_file"));
|
||||
ExportInfo& ei = *export_info();
|
||||
// get image
|
||||
SCRIPT_PARAM(ScriptValueP, input);
|
||||
ScriptObject<CardP>* card = dynamic_cast<ScriptObject<CardP>*>(input.get()); // is it a card?
|
||||
Image image;
|
||||
if (card) {
|
||||
image = export_bitmap(ei.set, card->getValue()).ConvertToImage();
|
||||
} else {
|
||||
image = image_from_script(input)->generate(GeneratedImage::Options(0,0,ei.export_template.get(),ei.set.get()));
|
||||
}
|
||||
if (!image.Ok()) throw Error(_("Unable to convert .. to image"));
|
||||
// filename
|
||||
SCRIPT_PARAM(String, file); // file to write to
|
||||
wxFileName fn;
|
||||
fn.SetPath(ei.directory_absolute);
|
||||
fn.SetFullName(file);
|
||||
if (!ei.exported_images.insert(fn.GetFullName()).second) {
|
||||
SCRIPT_RETURN(fn.GetFullName()); // already written an image with this name
|
||||
}
|
||||
// get image
|
||||
SCRIPT_PARAM(ScriptValueP, input);
|
||||
SCRIPT_OPTIONAL_PARAM_(int, width);
|
||||
SCRIPT_OPTIONAL_PARAM_(int, height);
|
||||
ScriptObject<CardP>* card = dynamic_cast<ScriptObject<CardP>*>(input.get()); // is it a card?
|
||||
Image image;
|
||||
GeneratedImage::Options options(width, height, ei.export_template.get(),ei.set.get());
|
||||
if (card) {
|
||||
image = conform_image(export_bitmap(ei.set, card->getValue()).ConvertToImage(), options);
|
||||
} else {
|
||||
image = image_from_script(input)->generateConform(options);
|
||||
}
|
||||
if (!image.Ok()) throw Error(_("Unable to generate image for file ") + file);
|
||||
// write
|
||||
image.SaveFile(fn.GetFullPath());
|
||||
SCRIPT_RETURN(fn.GetFullName());
|
||||
@@ -276,6 +411,7 @@ SCRIPT_FUNCTION(write_image_file) {
|
||||
|
||||
void init_script_export_functions(Context& ctx) {
|
||||
ctx.setVariable(_("to html"), script_to_html);
|
||||
ctx.setVariable(_("symbols to html"), script_symbols_to_html);
|
||||
ctx.setVariable(_("to text"), script_to_text);
|
||||
ctx.setVariable(_("copy file"), script_copy_file);
|
||||
ctx.setVariable(_("write text file"), script_write_text_file);
|
||||
|
||||
+1
-30
@@ -61,36 +61,7 @@ Image ScriptableImage::generate(const GeneratedImage::Options& options, bool cac
|
||||
i.SetAlpha(0,0,0);
|
||||
image = i;
|
||||
}
|
||||
// resize?
|
||||
int iw = image.GetWidth(), ih = image.GetHeight();
|
||||
if ((iw == options.width && ih == options.height) || options.width == 0 || options.height == 0) {
|
||||
// already the right size
|
||||
} else if (options.preserve_aspect == ASPECT_FIT) {
|
||||
// determine actual size of resulting image
|
||||
int w, h;
|
||||
if (iw * options.height > ih * options.width) { // too much height requested
|
||||
w = options.width;
|
||||
h = options.width * ih / iw;
|
||||
} else {
|
||||
w = options.height * iw / ih;
|
||||
h = options.height;
|
||||
}
|
||||
Image resampled_image(w, h, false);
|
||||
resample(image, resampled_image);
|
||||
image = resampled_image;
|
||||
} else {
|
||||
Image resampled_image(options.width, options.height, false);
|
||||
if (options.preserve_aspect == ASPECT_BORDER && (options.width < options.height * 3) && (options.height < options.width * 3)) {
|
||||
// preserve the aspect ratio if there is not too much difference
|
||||
resample_preserve_aspect(image, resampled_image);
|
||||
} else {
|
||||
resample(image, resampled_image);
|
||||
}
|
||||
image = resampled_image;
|
||||
}
|
||||
if (options.saturate) {
|
||||
saturate(image, 40);
|
||||
}
|
||||
image = conform_image(image, options);
|
||||
// cache? and return
|
||||
if (cache) cached = image;
|
||||
return image;
|
||||
|
||||
@@ -263,3 +263,22 @@ bool cannocial_name_compare(const String& as, const Char* b) {
|
||||
a++; b++;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Filenames
|
||||
|
||||
bool is_filename_char(Char c) {
|
||||
return isAlnum(c) || c == _(' ') || c == _('_') || c == _('-') || c == _('.');
|
||||
}
|
||||
|
||||
String clean_filename(const String& name) {
|
||||
String clean;
|
||||
FOR_EACH_CONST(c, name) {
|
||||
if (is_filename_char(c)) {
|
||||
clean += c;
|
||||
}
|
||||
}
|
||||
if (clean.empty() || starts_with(clean, _("."))) {
|
||||
clean = _("no-name") + clean;
|
||||
}
|
||||
return clean;
|
||||
}
|
||||
@@ -172,5 +172,10 @@ bool is_substr(const String& str, size_t pos, const String& cmp);
|
||||
/// Compare two strings for equality, b may contain '_' where a contains ' '
|
||||
bool cannocial_name_compare(const String& a, const Char* b);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Filenames
|
||||
|
||||
/// Make sure a string is safe to use as a filename
|
||||
String clean_filename(const String& name);
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user