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: ...@@ -409,6 +409,7 @@ label:
button: button:
# Style panel # Style panel
use for all cards: Use for &all cards use for all cards: Use for &all cards
use custom styling options: Options &specific to this card
# Keywords panel # Keywords panel
insert parameter: Insert Parameter... insert parameter: Insert Parameter...
...@@ -476,7 +477,8 @@ title: ...@@ -476,7 +477,8 @@ title:
# export # export
export images: Export Images export images: Export Images
export cancled: Export Cancled export cancled: Export Cancled
export html: Export HTML export html: Export to HTML
save html: Export to HTML
############################################################## Action (undo/redo) names ############################################################## Action (undo/redo) names
action: action:
......
...@@ -18,10 +18,9 @@ option field: ...@@ -18,10 +18,9 @@ option field:
choice: no choice: no
choice: just the image box, linked choice: just the image box, linked
choice: just the image box, inline choice: just the image box, inline
choice: full images, linked choice: full card image, linked
choice: full images, preview choice: full card image, preview
choice: full images, inline choice: full card image only
choice: full images only
initial: full images, preview initial: full images, preview
option field: option field:
type: boolean type: boolean
...@@ -31,10 +30,11 @@ option field: ...@@ -31,10 +30,11 @@ option field:
type: boolean type: boolean
name: rarity symbols name: rarity symbols
description: Should rarity be shown using a symbol or as text? description: Should rarity be shown using a symbol or as text?
option field: #doesn't work yet:
type: boolean #option field:
name: list keywords # type: boolean
description: Should the keywords be listed? # name: list keywords
# description: Should the keywords be listed?
#option field: #option field:
# type: boolean # type: boolean
# name: fancy scripts # name: fancy scripts
...@@ -83,28 +83,53 @@ option style: ...@@ -83,28 +83,53 @@ option style:
sans-serif: /magic-spoiler.mse-export-template/sans-serif.png sans-serif: /magic-spoiler.mse-export-template/sans-serif.png
script: script:
symbol_font := "magic-mana-small"
symbol_font_size := 12
write_card := { 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") 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") card_image_file := write_image_file(card.image, file:"card{position(of:card,in:set)}.jpg")
else else
card_image_file := "" card_image_file := ""
#if options.images == "full images only" if options.images == "full card image, preview" then
"<li class='card'> card_image_preview := write_image_file(card, file:"card-preview{position(of:card,in:set)}.jpg", height: 100)
<a href='{card_image_file}'><img src='{card_image_file}' alt='' class='image'></a> else
<span class='name' >{ to_html(card.name ) }</span> card_image_preview := card_image_file
<span class='casting-cost' >{ to_html(card.casting_cost, symbol_font: "mana-large", symbol_size: 12 ) }</span> if options.images == "full card image only" then
"<li class='fullcard'><img src='{card_image_file}' alt=''></li>"
else
"<li class='card'>
{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='type' >{ to_html(card.type ) }</span>
<span class='rarity' >{ <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 if options.rarity_symbols then
"<img src='{ write_image_file( "<img src='{ var := if card.rarity == "" then "common"
name: "set-symbol-{card.rarity[0] or else "C"}.png", else if card.rarity == "basic land" then "common"
else card.rarity
write_image_file(
file: "set-symbol-{var}.png",
width: 20, width: 20,
image: symbol_variation(set.symbol, variation:card.rarity) symbol_variation(
)}' alt='{card.rarity}'>" symbol: set.symbol,
else variation: var
to_upper(card.rarity[0] or else "C") # B,C,U,R,S )
)}' alt='{code}' title='{card.rarity}'>"
else code
}</span> }</span>
<span class='rule-text' >{ to_html(card.rule_text ) }</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> <span class='flavor-text' >{ to_html( remove_tag(tag: "<i-flavor>", card.flavor_text) ) }</span>
...@@ -125,11 +150,13 @@ script: ...@@ -125,11 +150,13 @@ script:
"<h2>{title} ({count} {if count == 1 then "card" else "cards"})</h2>" + "<h2>{title} ({count} {if count == 1 then "card" else "cards"})</h2>" +
write_cards() 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'> <html lang='en'>
<head> <head>
<title>{ to_html(set.title) }</title> <title>{ to_html(set.title) }</title>
<link rel='stylesheet' type='text/css' href='{copy_file("style.css")}' /> <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'> <style type='text/css'>
body \{ body \{
background: {options.background_color}; background: {options.background_color};
...@@ -140,7 +167,12 @@ script: ...@@ -140,7 +167,12 @@ script:
\} \}
</style> </style>
</head> </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> <h1>{ to_html(set.title) }</h1>
<div class='copyright'>{ to_html(set.copyright) }</div> <div class='copyright'>{ to_html(set.copyright) }</div>
<div class='description'>{ to_html(set.description) }</div> <div class='description'>{ to_html(set.description) }</div>
...@@ -159,6 +191,9 @@ script: ...@@ -159,6 +191,9 @@ script:
else else
write_cards(cards: set.cards) write_cards(cards: set.cards)
} }
<script><!--
init();
--></script>
</body> </body>
</html>" </html>"
write_text_file(html, file:"index.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 { ul {
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.card { .card {
clear: left;
margin-top: 1em; margin-top: 1em;
} }
...@@ -21,6 +27,7 @@ ul { ...@@ -21,6 +27,7 @@ ul {
display: inline; display: inline;
font-family: "Magic Symbols", "Magic Symbols 2004"; font-family: "Magic Symbols", "Magic Symbols 2004";
font-size: larger; font-size: larger;
margin-left: .1em;
} }
.card .flavor-text { .card .flavor-text {
...@@ -32,16 +39,62 @@ ul { ...@@ -32,16 +39,62 @@ ul {
} }
.card { .with-previews .card {
margin-top: 1.1em; margin-top: 1.1em;
min-height: 100px; min-height: 100px;
margin-left: 90px; margin-left: 90px;
position: relative; position: relative;
} }
.card .image { .card .card-image {
height: 100px; height: 100px;
position: absolute; position: absolute;
left: -85px; left: -85px;
top: 3px; top: 3px;
border: none; 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 { ...@@ -48,6 +48,7 @@ struct ExportInfo {
String directory_relative; ///< The directory for storing extra files (or "" if !export->create_directory) String directory_relative; ///< The directory for storing extra files (or "" if !export->create_directory)
/// This is just the directory name /// This is just the directory name
String directory_absolute; ///< The absolute path of the directory 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); DECLARE_DYNAMIC_ARG(ExportInfo*, export_info);
......
...@@ -74,6 +74,9 @@ class SymbolInFont : public IntrusivePtrBase<SymbolInFont> { ...@@ -74,6 +74,9 @@ class SymbolInFont : public IntrusivePtrBase<SymbolInFont> {
public: public:
SymbolInFont(); SymbolInFont();
/// Get a shrunk, zoomed image
Image getImage(Package& pkg, double size);
/// Get a shrunk, zoomed bitmap /// Get a shrunk, zoomed bitmap
Bitmap getBitmap(Package& pkg, double size); Bitmap getBitmap(Package& pkg, double size);
...@@ -107,23 +110,26 @@ SymbolInFont::SymbolInFont() ...@@ -107,23 +110,26 @@ SymbolInFont::SymbolInFont()
if (img_size <= 0) img_size = 1; 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) { Bitmap SymbolInFont::getBitmap(Package& pkg, double size) {
// is this bitmap already loaded/generated? // is this bitmap already loaded/generated?
Bitmap& bmp = bitmaps[size]; Bitmap& bmp = bitmaps[size];
if (!bmp.Ok()) { if (!bmp.Ok()) {
// generate new bitmap // generate image, convert to bitmap, store for later use
if (!image.isReady()) { bmp = Bitmap(getImage(pkg, size));
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);
} }
return bmp; return bmp;
} }
...@@ -166,16 +172,6 @@ IMPLEMENT_REFLECTION(SymbolInFont) { ...@@ -166,16 +172,6 @@ IMPLEMENT_REFLECTION(SymbolInFont) {
// ----------------------------------------------------------------------------- : SymbolFont : splitting // ----------------------------------------------------------------------------- : 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 { void SymbolFont::split(const String& text, SplitSymbols& out) const {
// read a single symbol until we are done with the text // read a single symbol until we are done with the text
for (size_t pos = 0 ; pos < text.size() ; ) { for (size_t pos = 0 ; pos < text.size() ; ) {
...@@ -286,6 +282,53 @@ void SymbolFont::drawWithText(RotatedDC& dc, const RealRect& rect, double font_s ...@@ -286,6 +282,53 @@ void SymbolFont::drawWithText(RotatedDC& dc, const RealRect& rect, double font_s
dc.DrawText(text, text_pos); 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 // ----------------------------------------------------------------------------- : SymbolFont : sizes
......
...@@ -35,12 +35,22 @@ class SymbolFont : public Packaged { ...@@ -35,12 +35,22 @@ class SymbolFont : public Packaged {
// Script update // Script update
void update(Context& ctx) const; 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; typedef vector<DrawableSymbol> SplitSymbols;
/// Split a string into separate symbols for drawing and for determining their size /// Split a string into separate symbols for drawing and for determining their size
void split(const String& text, SplitSymbols& out) const; 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); 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 /// Get information on characters in a string
void getCharInfo(RotatedDC& dc, Context& ctx, double font_size, const String& text, vector<CharInfo>& out); void getCharInfo(RotatedDC& dc, Context& ctx, double font_size, const String& text, vector<CharInfo>& out);
...@@ -50,6 +60,9 @@ class SymbolFont : public Packaged { ...@@ -50,6 +60,9 @@ class SymbolFont : public Packaged {
/// Get information on characters in a string /// Get information on characters in a string
void getCharInfo(RotatedDC& dc, double font_size, const SplitSymbols& text, vector<CharInfo>& out); 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(); static String typeNameStatic();
virtual String typeName() const; virtual String typeName() const;
......
...@@ -19,6 +19,58 @@ ...@@ -19,6 +19,58 @@
ScriptType GeneratedImage::type() const { return SCRIPT_IMAGE; } ScriptType GeneratedImage::type() const { return SCRIPT_IMAGE; }
String GeneratedImage::typeName() const { return _TYPE_("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 // ----------------------------------------------------------------------------- : BlankImage
Image BlankImage::generate(const Options& opt) const { Image BlankImage::generate(const Options& opt) const {
...@@ -165,7 +217,7 @@ Image SymbolToImage::generate(const Options& opt) const { ...@@ -165,7 +217,7 @@ Image SymbolToImage::generate(const Options& opt) const {
} else { } else {
the_symbol = opt.local_package->readFile<SymbolP>(filename); 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 { bool SymbolToImage::operator == (const GeneratedImage& that) const {
const SymbolToImage* that2 = dynamic_cast<const SymbolToImage*>(&that); const SymbolToImage* that2 = dynamic_cast<const SymbolToImage*>(&that);
...@@ -190,7 +242,7 @@ Image ImageValueToImage::generate(const Options& opt) const { ...@@ -190,7 +242,7 @@ Image ImageValueToImage::generate(const Options& opt) const {
image.LoadFile(*image_file); image.LoadFile(*image_file);
} }
if (!image.Ok()) { if (!image.Ok()) {
image = Image(opt.width, opt.height); image = Image(max(1,opt.width), max(1,opt.height));
} }
return image; return image;
} }
......
...@@ -38,6 +38,8 @@ class GeneratedImage : public ScriptValue { ...@@ -38,6 +38,8 @@ class GeneratedImage : public ScriptValue {
Package* local_package; ///< Package to load symbols and ImageValue images from 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 /// Generate the image
virtual Image generate(const Options&) const = 0; virtual Image generate(const Options&) const = 0;
/// How must the image be combined with the background? /// How must the image be combined with the background?
...@@ -53,6 +55,9 @@ class GeneratedImage : public ScriptValue { ...@@ -53,6 +55,9 @@ class GeneratedImage : public ScriptValue {
virtual String typeName() const; virtual String typeName() const;
}; };
/// Resize an image to conform to the options
Image conform_image(const Image&, const GeneratedImage::Options&);
// ----------------------------------------------------------------------------- : BlankImage // ----------------------------------------------------------------------------- : BlankImage
/// An image generator that returns a blank image /// An image generator that returns a blank image
......
...@@ -56,10 +56,6 @@ ImagesExportWindow::ImagesExportWindow(Window* parent, const SetP& set) ...@@ -56,10 +56,6 @@ ImagesExportWindow::ImagesExportWindow(Window* parent, const SetP& set)
// ----------------------------------------------------------------------------- : Exporting the images // ----------------------------------------------------------------------------- : Exporting the images
bool is_filename_char(Char c) {
return isAlnum(c) || c == _(' ') || c == _('_') || c == _('-') || c == _('.');
}
void ImagesExportWindow::onOk(wxCommandEvent&) { void ImagesExportWindow::onOk(wxCommandEvent&) {
// Update settings // Update settings
GameSettings& gs = settings.gameSettingsFor(*set->game); GameSettings& gs = settings.gameSettingsFor(*set->game);
...@@ -85,16 +81,7 @@ void ImagesExportWindow::onOk(wxCommandEvent&) { ...@@ -85,16 +81,7 @@ void ImagesExportWindow::onOk(wxCommandEvent&) {
String filename = untag(ctx.eval(*filename_script)->toString()); String filename = untag(ctx.eval(*filename_script)->toString());
if (!filename) continue; // no filename -> no saving if (!filename) continue; // no filename -> no saving
// sanitize filename // sanitize filename
String clean_filename; fn.SetFullName(clean_filename(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);
// does the file exist? // does the file exist?
if (fn.FileExists()) { if (fn.FileExists()) {
// file exists, what to do? // file exists, what to do?
......
...@@ -72,13 +72,13 @@ const size_t param_colors_count = sizeof(param_colors) / sizeof(param_colors[0]) ...@@ -72,13 +72,13 @@ const size_t param_colors_count = sizeof(param_colors) / sizeof(param_colors[0])
struct TextElementsFromString { struct TextElementsFromString {
// What formatting is enabled? // What formatting is enabled?
int bold, italic, symbol; 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 code, code_kw, code_string, param_ref, error;
int param_id; int param_id;
bool bracket; bool bracket;
TextElementsFromString() 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) , code(0), code_kw(0), code_string(0), param_ref(0), error(0)
, param_id(0), bracket(false) {} , param_id(0), bracket(false) {}
...@@ -125,6 +125,8 @@ struct TextElementsFromString { ...@@ -125,6 +125,8 @@ struct TextElementsFromString {
else if (is_substr(text, tag_start, _("</atom-param"))) param -= 1; 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, _("</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"))) { else if (is_substr(text, tag_start, _("<atom"))) {
// 'atomic' indicator // 'atomic' indicator
size_t end_tag = min(end, match_close_tag(text, tag_start)); size_t end_tag = min(end, match_close_tag(text, tag_start));
...@@ -178,7 +180,8 @@ struct TextElementsFromString { ...@@ -178,7 +180,8 @@ struct TextElementsFromString {
bracket ? pos + 2 : pos + 1, bracket ? pos + 2 : pos + 1,
font, font,
soft > 0 ? DRAW_ACTIVE : DRAW_NORMAL, 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) { if (bracket) {
e->content = String(LEFT_ANGLE_BRACKET) + c + RIGHT_ANGLE_BRACKET; e->content = String(LEFT_ANGLE_BRACKET) + c + RIGHT_ANGLE_BRACKET;
......
...@@ -34,7 +34,8 @@ enum DrawWhat ...@@ -34,7 +34,8 @@ enum DrawWhat
enum LineBreak enum LineBreak
{ BREAK_NO // no line break ever { BREAK_NO // no line break ever
, BREAK_MAYBE // break here when in "direction:vertical" mode , 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_HARD // always a line break ('\n')
, BREAK_LINE // line break with a separator line (<line>) , BREAK_LINE // line break with a separator line (<line>)
}; };
......
...@@ -43,7 +43,7 @@ void FontTextElement::getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& ...@@ -43,7 +43,7 @@ void FontTextElement::getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>&
} else { } else {
RealSize s = dc.GetTextExtent(content.substr(line_start - this->start, i - line_start + 1)); 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), 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; prev_width = s.width;
} }
......
...@@ -419,7 +419,11 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars, ...@@ -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 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 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 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; break_now = true;
accept_word = true; accept_word = true;
line_height_multiplier = style.line_height_hard; line_height_multiplier = style.line_height_hard;
...@@ -428,7 +432,7 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars, ...@@ -428,7 +432,7 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars,
break_now = true; break_now = true;
accept_word = true; accept_word = true;
line_height_multiplier = style.line_height_line; 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 // Soft break == end of word
accept_word = true; accept_word = true;
} else if (c.break_after == BREAK_MAYBE && style.direction == TOP_TO_BOTTOM) { } 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 ...@@ -563,13 +567,13 @@ void TextViewer::alignLines(RotatedDC& dc, const vector<CharInfo>& chars, const
double hdelta = s.width - width; // amount of space to distribute double hdelta = s.width - width; // amount of space to distribute
int count = 0; // distribute it among this many words int count = 0; // distribute it among this many words
for (size_t k = l.start + 1 ; k < l.end() - 1 ; ++k) { 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 if (count == 0) count = 1; // prevent div by 0
int i = 0; size_t j = l.start; int i = 0; size_t j = l.start;
FOR_EACH(c, l.positions) { FOR_EACH(c, l.positions) {
c += hdelta * i / count; 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 { } else {
// simple alignment // simple alignment
......
...@@ -20,6 +20,19 @@ ...@@ -20,6 +20,19 @@
#include <wx/wfstream.h> #include <wx/wfstream.h>
#include <wx/filename.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 // ----------------------------------------------------------------------------- : HTML
// An HTML tag // An HTML tag
...@@ -124,11 +137,52 @@ class TagStack { ...@@ -124,11 +137,52 @@ class TagStack {
} }
}; };
String symbols_to_html(const String& str, const SymbolFontP& symbol_font) { // html-escape a string
return str; // TODO 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 str = remove_tag_contents(str_in,_("<sep-soft"));
String ret; String ret;
Tag bold (_("<b>"), _("</b>")), Tag bold (_("<b>"), _("</b>")),
...@@ -149,12 +203,12 @@ String to_html(const String& str_in, const SymbolFontP& symbol_font) { ...@@ -149,12 +203,12 @@ String to_html(const String& str_in, const SymbolFontP& symbol_font) {
} else if (is_substr(str, i, _("/i"))) { } else if (is_substr(str, i, _("/i"))) {
tags.close(ret, italic); tags.close(ret, italic);
} else if (is_substr(str, i, _("sym"))) { } else if (is_substr(str, i, _("sym"))) {
tags.close(ret, symbol); tags.open (ret, symbol);
} else if (is_substr(str, i, _("/sym"))) { } else if (is_substr(str, i, _("/sym"))) {
if (!symbols.empty()) { if (!symbols.empty()) {
// write symbols in a special way // write symbols in a special way
tags.write_pending_tags(ret); tags.write_pending_tags(ret);
ret += symbols_to_html(symbols, symbol_font); ret += symbols_to_html(symbols, *symbol_font, symbol_size);
symbols.clear(); symbols.clear();
} }
tags.close(ret, symbol); tags.close(ret, symbol);
...@@ -184,7 +238,7 @@ String to_html(const String& str_in, const SymbolFontP& symbol_font) { ...@@ -184,7 +238,7 @@ String to_html(const String& str_in, const SymbolFontP& symbol_font) {
// end of input // end of input
if (!symbols.empty()) { if (!symbols.empty()) {
tags.write_pending_tags(ret); tags.write_pending_tags(ret);
ret += symbols_to_html(symbols, symbol_font); ret += symbols_to_html(symbols, *symbol_font, symbol_size);
symbols.clear(); symbols.clear();
} }
tags.close_all(ret); tags.close_all(ret);
...@@ -194,8 +248,91 @@ String to_html(const String& str_in, const SymbolFontP& symbol_font) { ...@@ -194,8 +248,91 @@ String to_html(const String& str_in, const SymbolFontP& symbol_font) {
// convert a tagged string to html // convert a tagged string to html
SCRIPT_FUNCTION(to_html) { SCRIPT_FUNCTION(to_html) {
SCRIPT_PARAM(String, input); SCRIPT_PARAM(String, input);
SymbolFontP symbol_font; // TODO // symbol font?
SCRIPT_RETURN(to_html(input, 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 // ----------------------------------------------------------------------------- : Text
...@@ -208,14 +345,6 @@ SCRIPT_FUNCTION(to_text) { ...@@ -208,14 +345,6 @@ SCRIPT_FUNCTION(to_text) {
// ----------------------------------------------------------------------------- : Files // ----------------------------------------------------------------------------- : 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) // copy from source package -> destination directory, return new filename (relative)
SCRIPT_FUNCTION(copy_file) { SCRIPT_FUNCTION(copy_file) {
guard_export_info(_("copy_file")); guard_export_info(_("copy_file"));
...@@ -252,21 +381,27 @@ SCRIPT_FUNCTION(write_text_file) { ...@@ -252,21 +381,27 @@ SCRIPT_FUNCTION(write_text_file) {
SCRIPT_FUNCTION(write_image_file) { SCRIPT_FUNCTION(write_image_file) {
guard_export_info(_("write_image_file")); guard_export_info(_("write_image_file"));
ExportInfo& ei = *export_info(); 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 // get image
SCRIPT_PARAM(ScriptValueP, input); 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? ScriptObject<CardP>* card = dynamic_cast<ScriptObject<CardP>*>(input.get()); // is it a card?
Image image; Image image;
GeneratedImage::Options options(width, height, ei.export_template.get(),ei.set.get());
if (card) { if (card) {
image = export_bitmap(ei.set, card->getValue()).ConvertToImage(); image = conform_image(export_bitmap(ei.set, card->getValue()).ConvertToImage(), options);
} else { } 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")); if (!image.Ok()) throw Error(_("Unable to generate image for file ") + file);
// filename
SCRIPT_PARAM(String, file); // file to write to
wxFileName fn;
fn.SetPath(ei.directory_absolute);
fn.SetFullName(file);
// write // write
image.SaveFile(fn.GetFullPath()); image.SaveFile(fn.GetFullPath());
SCRIPT_RETURN(fn.GetFullName()); SCRIPT_RETURN(fn.GetFullName());
...@@ -276,6 +411,7 @@ SCRIPT_FUNCTION(write_image_file) { ...@@ -276,6 +411,7 @@ SCRIPT_FUNCTION(write_image_file) {
void init_script_export_functions(Context& ctx) { void init_script_export_functions(Context& ctx) {
ctx.setVariable(_("to html"), script_to_html); 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(_("to text"), script_to_text);
ctx.setVariable(_("copy file"), script_copy_file); ctx.setVariable(_("copy file"), script_copy_file);
ctx.setVariable(_("write text file"), script_write_text_file); ctx.setVariable(_("write text file"), script_write_text_file);
......
...@@ -61,36 +61,7 @@ Image ScriptableImage::generate(const GeneratedImage::Options& options, bool cac ...@@ -61,36 +61,7 @@ Image ScriptableImage::generate(const GeneratedImage::Options& options, bool cac
i.SetAlpha(0,0,0); i.SetAlpha(0,0,0);
image = i; image = i;
} }
// resize? image = conform_image(image, options);
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);
}
// cache? and return // cache? and return
if (cache) cached = image; if (cache) cached = image;
return image; return image;
......
...@@ -263,3 +263,22 @@ bool cannocial_name_compare(const String& as, const Char* b) { ...@@ -263,3 +263,22 @@ bool cannocial_name_compare(const String& as, const Char* b) {
a++; 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); ...@@ -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 ' ' /// Compare two strings for equality, b may contain '_' where a contains ' '
bool cannocial_name_compare(const String& a, const Char* b); 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 // ----------------------------------------------------------------------------- : EOF
#endif #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