diff --git a/data/en.mse-locale/locale b/data/en.mse-locale/locale index e3cb9f34..44d0fd27 100644 --- a/data/en.mse-locale/locale +++ b/data/en.mse-locale/locale @@ -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: diff --git a/data/magic-spoiler.mse-export-template/blank.gif b/data/magic-spoiler.mse-export-template/blank.gif new file mode 100644 index 00000000..75b945d2 Binary files /dev/null and b/data/magic-spoiler.mse-export-template/blank.gif differ diff --git a/data/magic-spoiler.mse-export-template/export-template b/data/magic-spoiler.mse-export-template/export-template index f1be8805..98022147 100644 --- a/data/magic-spoiler.mse-export-template/export-template +++ b/data/magic-spoiler.mse-export-template/export-template @@ -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" - "
  • - - { to_html(card.name ) } - { to_html(card.casting_cost, symbol_font: "mana-large", symbol_size: 12 ) } + 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 + "
  • " + else + "
  • + {if options.images == "full card image, preview" then + " + { to_html(card.name ) }" + else if card_image_file != "" and contains(options.images, match:"linked") then + "{ to_html(card.name) }" + else + "{ to_html(card.name ) }" + }{ symbols_to_html(card.casting_cost ) } + {if card_image_file != "" and contains(options.images, match:"inline") then + "" + } { to_html(card.type ) } { + 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 - "{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 } { to_html(card.rule_text ) } { to_html( remove_tag(tag: "", card.flavor_text) ) } @@ -125,11 +150,13 @@ script: "

    {title} ({count} {if count == 1 then "card" else "cards"})

    " + write_cards() } - html := " + copy_file("blank.gif") + html := " { to_html(set.title) } + - + +

    { to_html(set.title) }

    { to_html(set.description) }
    @@ -159,6 +191,9 @@ script: else write_cards(cards: set.cards) } + " write_text_file(html, file:"index.html") diff --git a/data/magic-spoiler.mse-export-template/script.js b/data/magic-spoiler.mse-export-template/script.js new file mode 100644 index 00000000..e4a003aa --- /dev/null +++ b/data/magic-spoiler.mse-export-template/script.js @@ -0,0 +1,70 @@ +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(); +} + diff --git a/data/magic-spoiler.mse-export-template/style.css b/data/magic-spoiler.mse-export-template/style.css index bf63d1c0..35e144ed 100644 --- a/data/magic-spoiler.mse-export-template/style.css +++ b/data/magic-spoiler.mse-export-template/style.css @@ -1,9 +1,15 @@ +.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 diff --git a/src/data/export_template.hpp b/src/data/export_template.hpp index 5c28c9c6..21445007 100644 --- a/src/data/export_template.hpp +++ b/src/data/export_template.hpp @@ -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 exported_images; ///< Images (from symbol font) already exported }; DECLARE_DYNAMIC_ARG(ExportInfo*, export_info); diff --git a/src/data/symbol_font.cpp b/src/data/symbol_font.cpp index 576099b3..14b92a1f 100644 --- a/src/data/symbol_font.cpp +++ b/src/data/symbol_font.cpp @@ -74,6 +74,9 @@ class SymbolInFont : public IntrusivePtrBase { 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 diff --git a/src/data/symbol_font.hpp b/src/data/symbol_font.hpp index ebc99f96..a1d62dbc 100644 --- a/src/data/symbol_font.hpp +++ b/src/data/symbol_font.hpp @@ -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 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& 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& out); + /// Get the image for a symbol + Image getImage(double font_size, const DrawableSymbol& symbol); + static String typeNameStatic(); virtual String typeName() const; diff --git a/src/gfx/generated_image.cpp b/src/gfx/generated_image.cpp index 36533db0..9cdfcb45 100644 --- a/src/gfx/generated_image.cpp +++ b/src/gfx/generated_image.cpp @@ -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(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(&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; } diff --git a/src/gfx/generated_image.hpp b/src/gfx/generated_image.hpp index 7f69b105..faa82257 100644 --- a/src/gfx/generated_image.hpp +++ b/src/gfx/generated_image.hpp @@ -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 diff --git a/src/gui/images_export_window.cpp b/src/gui/images_export_window.cpp index 517c1e9f..f1880ab2 100644 --- a/src/gui/images_export_window.cpp +++ b/src/gui/images_export_window.cpp @@ -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? diff --git a/src/render/text/element.cpp b/src/render/text/element.cpp index 0214d9af..9366e2e1 100644 --- a/src/render/text/element.cpp +++ b/src/render/text/element.cpp @@ -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, _(" 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; diff --git a/src/render/text/element.hpp b/src/render/text/element.hpp index 981ca4b7..d27addfa 100644 --- a/src/render/text/element.hpp +++ b/src/render/text/element.hpp @@ -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 () }; diff --git a/src/render/text/font.cpp b/src/render/text/font.cpp index 64e76a61..053ae1d3 100644 --- a/src/render/text/font.cpp +++ b/src/render/text/font.cpp @@ -43,7 +43,7 @@ void FontTextElement::getCharInfo(RotatedDC& dc, double scale, vector& } 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; } diff --git a/src/render/text/viewer.cpp b/src/render/text/viewer.cpp index e649e7c1..6b279c1e 100644 --- a/src/render/text/viewer.cpp +++ b/src/render/text/viewer.cpp @@ -419,7 +419,11 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector& 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& 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& 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 diff --git a/src/script/functions/export.cpp b/src/script/functions/export.cpp index 93d2d119..fdce2908 100644 --- a/src/script/functions/export.cpp +++ b/src/script/functions/export.cpp @@ -20,6 +20,19 @@ #include #include +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 symbols; + symbol_font.split(str, symbols); + String html; + FOR_EACH(sym, symbols) { + String filename = symbol_font.name() + _("-") + clean_filename(sym.text) + _(".png"); + html += _("") + 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,_(""), _("")), @@ -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,_(" 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* card = dynamic_cast*>(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* card = dynamic_cast*>(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); diff --git a/src/script/image.cpp b/src/script/image.cpp index 326a9984..a7ee45d1 100644 --- a/src/script/image.cpp +++ b/src/script/image.cpp @@ -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; diff --git a/src/util/string.cpp b/src/util/string.cpp index 98dd9da2..bfac4869 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -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 diff --git a/src/util/string.hpp b/src/util/string.hpp index 7f59963e..ce4820ca 100644 --- a/src/util/string.hpp +++ b/src/util/string.hpp @@ -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