Commit fc9aaf32 authored by twanvl's avatar twanvl

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
parent 620aa8eb
......@@ -409,6 +409,7 @@ label:
button:
# Style panel
use for all cards: Use for &all cards
use custom styling options: Options &specific to this card
# Keywords panel
insert parameter: Insert Parameter...
......@@ -476,7 +477,8 @@ title:
# export
export images: Export Images
export cancled: Export Cancled
export html: Export HTML
export html: Export to HTML
save html: Export to HTML
############################################################## Action (undo/redo) names
action:
......
......@@ -18,10 +18,9 @@ option field:
choice: no
choice: just the image box, linked
choice: just the image box, inline
choice: full images, linked
choice: full images, preview
choice: full images, inline
choice: full images only
choice: full card image, linked
choice: full card image, preview
choice: full card image only
initial: full images, preview
option field:
type: boolean
......@@ -31,10 +30,11 @@ option field:
type: boolean
name: rarity symbols
description: Should rarity be shown using a symbol or as text?
option field:
type: boolean
name: list keywords
description: Should the keywords be listed?
#doesn't work yet:
#option field:
# type: boolean
# name: list keywords
# description: Should the keywords be listed?
#option field:
# type: boolean
# name: fancy scripts
......@@ -83,28 +83,53 @@ option style:
sans-serif: /magic-spoiler.mse-export-template/sans-serif.png
script:
symbol_font := "magic-mana-small"
symbol_font_size := 12
write_card := {
if contains(options.images, match:= "full images") then
if contains(options.images, match:"full card image") then
card_image_file := write_image_file(card, file:"card{position(of:card,in:set)}.jpg")
else if contains(options.images, match:= "image box") then
else if contains(options.images, match:"image box") and
card.image != "" then
card_image_file := write_image_file(card.image, file:"card{position(of:card,in:set)}.jpg")
else
card_image_file := ""
#if options.images == "full images only"
if options.images == "full card image, preview" then
card_image_preview := write_image_file(card, file:"card-preview{position(of:card,in:set)}.jpg", height: 100)
else
card_image_preview := card_image_file
if options.images == "full card image only" then
"<li class='fullcard'><img src='{card_image_file}' alt=''></li>"
else
"<li class='card'>
<a href='{card_image_file}'><img src='{card_image_file}' alt='' class='image'></a>
<span class='name' >{ to_html(card.name ) }</span>
<span class='casting-cost' >{ to_html(card.casting_cost, symbol_font: "mana-large", symbol_size: 12 ) }</span>
{if options.images == "full card image, preview" then
"<a href='{card_image_file}'><img src='{card_image_preview}' alt='' class='card-image'></a>
<span class='name' >{ to_html(card.name ) }</span>"
else if card_image_file != "" and contains(options.images, match:"linked") then
"<span class='name' ><a href='{card_image_file}'>{ to_html(card.name) }</a></span>"
else
"<span class='name' >{ to_html(card.name ) }</span>"
}<span class='casting-cost' >{ symbols_to_html(card.casting_cost ) }</span>
{if card_image_file != "" and contains(options.images, match:"inline") then
"<img src='{card_image_preview}' alt='' class='image'>"
}
<span class='type' >{ to_html(card.type ) }</span>
<span class='rarity' >{
code := if card.rarity == "" then "C"
else if card.rarity == "basic land" then "L"
else to_upper(card.rarity[0]) # L,C,U,R,S
if options.rarity_symbols then
"<img src='{ write_image_file(
name: "set-symbol-{card.rarity[0] or else "C"}.png",
"<img src='{ var := if card.rarity == "" then "common"
else if card.rarity == "basic land" then "common"
else card.rarity
write_image_file(
file: "set-symbol-{var}.png",
width: 20,
image: symbol_variation(set.symbol, variation:card.rarity)
)}' alt='{card.rarity}'>"
else
to_upper(card.rarity[0] or else "C") # B,C,U,R,S
symbol_variation(
symbol: set.symbol,
variation: var
)
)}' alt='{code}' title='{card.rarity}'>"
else code
}</span>
<span class='rule-text' >{ to_html(card.rule_text ) }</span>
<span class='flavor-text' >{ to_html( remove_tag(tag: "<i-flavor>", card.flavor_text) ) }</span>
......@@ -125,11 +150,13 @@ script:
"<h2>{title} ({count} {if count == 1 then "card" else "cards"})</h2>" +
write_cards()
}
html := "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN\' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>
copy_file("blank.gif")
html := "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">
<html lang='en'>
<head>
<title>{ to_html(set.title) }</title>
<link rel='stylesheet' type='text/css' href='{copy_file("style.css")}' />
<script type='text/javascript' src='{copy_file("script.js")}'></script>
<style type='text/css'>
body \{
background: {options.background_color};
......@@ -140,7 +167,12 @@ script:
\}
</style>
</head>
<body>
<body{if options.images == "full card image, preview" then " class='with-previews'"}>
<img src='{ write_image_file(
file: "set-symbol.png",
width: 200,
symbol_variation(symbol: set.symbol, variation: "rare")
)}' alt='' class='set-symbol'>
<h1>{ to_html(set.title) }</h1>
<div class='copyright'>{ to_html(set.copyright) }</div>
<div class='description'>{ to_html(set.description) }</div>
......@@ -159,6 +191,9 @@ script:
else
write_cards(cards: set.cards)
}
<script><!--
init();
--></script>
</body>
</html>"
write_text_file(html, file:"index.html")
......
var isIE = navigator.appVersion.indexOf("MSIE") != -1;
var preview, preview_img;
function show_preview(url) {
preview.style.display = "block";
preview_img.style.backgroundImage = "url("+this.href+")";
return false;
}
function hide_preview() {
preview.style.display = "none";
}
function fix_preview() {
var e = document.documentElement ? document.documentElement : document.body;
preview.style.top = e.scrollTop + "px";
preview.style.height = e.clientHeight;
preview.style.width = e.clientWidth;
}
function nice_preview() {
// attach
var links = document.getElementsByTagName("A");
for (var i in links) {
if (/(.jpg|.png|.gif)$/.test(links[i])) {
links[i].onclick = show_preview;
}
}
// create divs
preview = document.createElement("div");
var bg = document.createElement("div");
var img = document.createElement("div");
preview.id = "preview";
bg.id = "preview-bg";
img.id = "preview-img";
hide_preview();
preview.onclick = bg.onclick = img.onclick = hide_preview;
preview.appendChild(bg);
preview.appendChild(img);
document.body.appendChild(preview);
preview_img = img;
if (isIE) {
window.onscroll = fix_preview;
fix_preview();
}
}
function fix_png_alpha() {
if (!/MSIE (5\.5|6\.)/.test(navigator.userAgent)) return; // only in ie 5.5 and 6
var dir = document.getElementsByTagName("SCRIPT")[0].src.replace(/[^\/]*$/,''); // dir for blank image
var imgs = document.getElementsByTagName("IMG");
for (var i in imgs) {
var img = imgs[i];
if ((/\.png$/i).test(img.src)) {
if (img.currentStyle.width == 'auto' && img.currentStyle.height == 'auto') {
img.style.width = img.offsetWidth + 'px';
img.style.height = img.offsetHeight + 'px';
}
img.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+img.src+'",sizingMethod="scale")';
img.src = dir + "blank.gif";
}
}
}
function init() {
fix_png_alpha();
nice_preview();
}
.set-symbol {
float: left;
margin-right: .5em;
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
.card {
clear: left;
margin-top: 1em;
}
......@@ -21,6 +27,7 @@ ul {
display: inline;
font-family: "Magic Symbols", "Magic Symbols 2004";
font-size: larger;
margin-left: .1em;
}
.card .flavor-text {
......@@ -32,16 +39,62 @@ ul {
}
.card {
.with-previews .card {
margin-top: 1.1em;
min-height: 100px;
margin-left: 90px;
position: relative;
}
.card .image {
.card .card-image {
height: 100px;
position: absolute;
left: -85px;
top: 3px;
border: none;
}
.card .image {
display: block;
border: none;
}
span.symbol {
display: inline;
vertical-align: middle;
}
.fullcard {
float: left;
}
h2 {
clear: both;
}
/* image preview */
#preview-bg {
background-color: rgb(0,0,0);
width: 100%;
height: 100%;
cursor: pointer;
position: absolute;
opacity: 0.7;
-moz-opacity: 0.7;
filter: alpha(opacity=70);
}
#preview-img {
background-position: 50% 50%;
background-repeat: no-repeat;
width: 100%;
height: 100%;
cursor: pointer;
position: absolute;
}
#preview {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
* html #preview {
position: absolute;
}
\ No newline at end of file
......@@ -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);
......
......@@ -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,11 +110,8 @@ SymbolInFont::SymbolInFont()
if (img_size <= 0) img_size = 1;
}
Bitmap SymbolInFont::getBitmap(Package& pkg, double size) {
// is this bitmap already loaded/generated?
Bitmap& bmp = bitmaps[size];
if (!bmp.Ok()) {
// generate new bitmap
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."));
}
......@@ -120,10 +120,16 @@ Bitmap SymbolInFont::getBitmap(Package& pkg, double size) {
// 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);
if (!resampled_image.Ok()) return Image(1,1);
resample(img, resampled_image);
// convert to bitmap, store for later use
bmp = Bitmap(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 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
......
......@@ -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 += _("&lt;");
} else if (c == _('>')) { // escape >
ret += _("&gt;");
} else if (c == _('&')) { // escape &
ret += _("&amp;");
} else if (c == _('\'')) { // escape '
ret += _("&#39;");
} else if (c == _('\"')) { // escape "
ret += _("&quot;");
} 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();
// 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 = export_bitmap(ei.set, card->getValue()).ConvertToImage();
image = conform_image(export_bitmap(ei.set, card->getValue()).ConvertToImage(), options);
} else {
image = image_from_script(input)->generate(GeneratedImage::Options(0,0,ei.export_template.get(),ei.set.get()));
image = image_from_script(input)->generateConform(options);
}
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 (!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);
......
......@@ -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;
}
\ No newline at end of file
......@@ -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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment