/** * eAdventure (formerly <e-Adventure> and <e-Game>) is a research project of the * <e-UCM> research group. * * Copyright 2005-2010 <e-UCM> research group. * * You can access a list of all the contributors to eAdventure at: * http://e-adventure.e-ucm.es/contributors * * <e-UCM> is a research group of the Department of Software Engineering * and Artificial Intelligence at the Complutense University of Madrid * (School of Computer Science). * * C Profesor Jose Garcia Santesmases sn, * 28040 Madrid (Madrid), Spain. * * For more info please visit: <http://e-adventure.e-ucm.es> or * <http://www.e-ucm.es> * * **************************************************************************** * * This file is part of eAdventure, version 2.0 * * eAdventure is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * eAdventure is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with eAdventure. If not, see <http://www.gnu.org/licenses/>. */ package ead.importer.subimporters.books; import com.google.inject.Inject; import es.eucm.ead.model.assets.AssetDescriptor; import es.eucm.ead.model.assets.drawable.EAdDrawable; import es.eucm.ead.model.assets.drawable.basics.Caption; import es.eucm.ead.model.assets.drawable.basics.Image; import es.eucm.ead.model.assets.drawable.basics.shapes.CircleShape; import es.eucm.ead.model.assets.drawable.compounds.ComposedDrawable; import es.eucm.ead.model.assets.text.BasicFont; import es.eucm.ead.model.assets.text.EAdFont; import es.eucm.ead.model.elements.BasicElement; import es.eucm.ead.model.elements.conditions.EmptyCond; import es.eucm.ead.model.elements.conditions.OperationCond; import es.eucm.ead.model.elements.conditions.enums.Comparator; import es.eucm.ead.model.elements.effects.ChangeSceneEf; import es.eucm.ead.model.elements.effects.InterpolationEf; import es.eucm.ead.model.elements.effects.enums.InterpolationLoopType; import es.eucm.ead.model.elements.effects.enums.InterpolationType; import es.eucm.ead.model.elements.effects.variables.ChangeFieldEf; import es.eucm.ead.model.elements.events.ConditionedEv; import es.eucm.ead.model.elements.events.SceneElementEv; import es.eucm.ead.model.elements.events.enums.ConditionedEvType; import es.eucm.ead.model.elements.events.enums.SceneElementEvType; import es.eucm.ead.model.elements.huds.InventoryHud; import es.eucm.ead.model.elements.operations.ElementField; import es.eucm.ead.model.elements.operations.ValueOp; import es.eucm.ead.model.elements.predef.effects.ChangeAppearanceEf; import es.eucm.ead.model.elements.scenes.*; import es.eucm.ead.model.params.fills.ColorFill; import es.eucm.ead.model.params.guievents.MouseGEv; import es.eucm.ead.model.params.util.Position; import es.eucm.ead.model.params.util.Position.Corner; import ead.importer.EAdElementImporter; import ead.importer.annotation.ImportAnnotator; import ead.importer.interfaces.ResourceImporter; import ead.importer.resources.ResourceImporterImpl; import es.eucm.ead.tools.StringHandler; import es.eucm.eadventure.common.data.chapter.book.Book; import es.eucm.eadventure.common.data.chapter.book.BookPage; import es.eucm.eadventure.common.data.chapter.book.BookParagraph; import gui.ava.html.image.generator.HtmlImageGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; import java.awt.*; import java.awt.font.FontRenderContext; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; public class BookImporter implements EAdElementImporter<Book, Scene> { private static final int BOOK_WIDTH = 800; /** * X position of the first column of text */ public static final int TEXT_X_1 = 110; /** * X position for the second column of text */ public static final int TEXT_X_2 = 445; /** * Y position for both columns of text */ public static final int TEXT_Y = 75; /** * Width of each column of text */ public static final int TEXT_WIDTH = 250; /** * Width of each column of the bullet text */ public static final int TEXT_WIDTH_BULLET = 225; public static final int BULLET_WIDTH = TEXT_WIDTH - TEXT_WIDTH_BULLET; /** * Height of each column of text */ public static final int PAGE_TEXT_HEIGHT = 500; /** * Height of each line of text */ public static final int LINE_HEIGHT = 25; /** * Height of each line of a title */ public static final int TITLE_HEIGHT = 50; static private Logger logger = LoggerFactory.getLogger(BookImporter.class); private FontRenderContext frc = new FontRenderContext(null, true, true); private Font titleFont = new Font("Arial", Font.PLAIN, 33); private EAdFont titleEAdFont = BasicFont.BIG; private Font textFont = new Font("Arial", Font.PLAIN, 18); private EAdFont textEAdFont = BasicFont.REGULAR; private int paragraphDispY = 0; private int paragraphColumn; private StringHandler stringHandler; private ResourceImporter resourceImporter; @Inject public BookImporter(ResourceImporter resourceImporter, StringHandler stringHandler, ImportAnnotator annotator) { this.resourceImporter = resourceImporter; this.stringHandler = stringHandler; } @Override public Scene init(Book oldObject) { Scene scene = new Scene(); return scene; } @Override public Scene convert(Book oldObject, Object newElement) { Scene book = (Scene) newElement; ChangeFieldEf hideInventory = new ChangeFieldEf( new ElementField(new BasicElement(InventoryHud.ID), SceneElement.VAR_VISIBLE), EmptyCond.FALSE); SceneElementEv hideEvent = new SceneElementEv(); hideEvent.addEffect(SceneElementEvType.ADDED, hideInventory); book.addEvent(hideEvent); // Import background AssetDescriptor background = resourceImporter.getAssetDescritptor( oldObject.getResources().get(0).getAssetPath( Book.RESOURCE_TYPE_BACKGROUND), Image.class); book.getBackground().getDefinition().addAsset( SceneElementDef.appearance, background); ChangeFieldEf showInventory = new ChangeFieldEf( new ElementField(new BasicElement(InventoryHud.ID), SceneElement.VAR_VISIBLE), EmptyCond.TRUE); EAdDrawable image = null; // Create content paragraphColumn = 0; if (oldObject.getType() == Book.TYPE_PAGES) { image = this.generatePagesBookContent(oldObject); } else if (oldObject.getType() == Book.TYPE_PARAGRAPHS) { paragraphDispY = TEXT_Y; image = generateParagraphBookContent(oldObject); } SceneElement content = new SceneElement(image); ElementField xField = new ElementField(content, SceneElement.VAR_X); // Event to restart the x variable SceneElementEv xEvent = new SceneElementEv(); content.addEvent(xEvent); ChangeFieldEf changeX = new ChangeFieldEf(xField, new ValueOp(0)); xEvent.addEffect(SceneElementEvType.ADDED, changeX); book.setReturnable(false); book.getSceneElements().add(content); addArrowsParagraphs(book, content, oldObject, xField, showInventory); return book; } private void addArrowsParagraphs(GroupElement book, SceneElement content, Book oldObject, ElementField xField, Effect showInventory) { content.setPosition(Corner.TOP_LEFT, 0, 0); SceneElementEv event = new SceneElementEv(); event.addEffect(SceneElementEvType.INIT, new ChangeFieldEf(xField, new ValueOp(0))); content.addEvent(event); Condition leftCondition = new OperationCond(xField, 0, Comparator.LESS); SceneElement leftArrow = getArrow(oldObject, content, Book.RESOURCE_TYPE_ARROW_LEFT_NORMAL, Book.RESOURCE_TYPE_ARROW_LEFT_OVER, BOOK_WIDTH, leftCondition); Point p = oldObject.getPreviousPagePoint(); int x = 10; int y = 10; if (p != null) { x = p.x; y = p.y; } leftArrow.setPosition(x, y); Condition rightCondition = EmptyCond.TRUE; SceneElement rightArrow = getArrow(oldObject, content, Book.RESOURCE_TYPE_ARROW_RIGHT_NORMAL, Book.RESOURCE_TYPE_ARROW_RIGHT_OVER, -BOOK_WIDTH, rightCondition); p = oldObject.getNextPagePoint(); x = 790; y = 10; Corner c = Corner.TOP_RIGHT; if (p != null) { x = p.x; y = p.y; c = Corner.TOP_LEFT; } rightArrow.setPosition(new Position(c, x, y)); Condition endCondition = new OperationCond(xField, -(((paragraphColumn / 2) - 1) * BOOK_WIDTH + BOOK_WIDTH / 2), Comparator.LESS); ChangeSceneEf changeScene = new ChangeSceneEf(); changeScene.setCondition(endCondition); rightArrow.addBehavior(MouseGEv.MOUSE_LEFT_PRESSED, changeScene); changeScene.getNextEffects().add(showInventory); book.getSceneElements().add(leftArrow); book.getSceneElements().add(rightArrow); } private EAdDrawable generateParagraphBookContent(Book oldObject) { ComposedDrawable image = new ComposedDrawable(); for (BookParagraph p : oldObject.getParagraphs()) { if (p.getContent() != null && !p.getContent().equals("")) switch (p.getType()) { case BookParagraph.TITLE: addTextDrawable(image, p.getContent(), titleFont, titleEAdFont, 0, TITLE_HEIGHT, TEXT_WIDTH); break; case BookParagraph.TEXT: addTextDrawable(image, p.getContent(), textFont, textEAdFont, 0, LINE_HEIGHT, TEXT_WIDTH); break; case BookParagraph.BULLET: if (paragraphDispY + LINE_HEIGHT > PAGE_TEXT_HEIGHT) { paragraphColumn++; paragraphDispY = TEXT_Y; } CircleShape bullet = new CircleShape(BULLET_WIDTH / 3); bullet.setPaint(ColorFill.BLACK); image.addDrawable(bullet, getDispX() + BULLET_WIDTH / 2, paragraphDispY + LINE_HEIGHT / 2); addTextDrawable(image, p.getContent(), textFont, textEAdFont, BULLET_WIDTH, LINE_HEIGHT, TEXT_WIDTH_BULLET); break; case BookParagraph.IMAGE: Image i = (Image) resourceImporter.getAssetDescritptor(p .getContent(), Image.class); try { BufferedImage im = ImageIO.read(new File( resourceImporter.getNewProjecFolder(), i .getUri().toString().substring(1))); int height = im.getHeight(); if (paragraphDispY + height > PAGE_TEXT_HEIGHT) { paragraphColumn++; paragraphDispY = TEXT_Y; } image.addDrawable(i, getDispX(), paragraphDispY); paragraphDispY += height; } catch (IOException e) { e.printStackTrace(); } break; } } return image; } private EAdDrawable generatePagesBookContent(Book oldObject) { ComposedDrawable image = new ComposedDrawable(); HtmlImageGenerator imgGenerator = new HtmlImageGenerator(); int x = 0; for (BookPage page : oldObject.getPageURLs()) { int leftMargin = page.getMarginStart(); int topMargin = page.getMarginTop(); int rightMargin = page.getMarginEnd(); int bottomMargin = page.getMarginBottom(); Image i = null; switch (page.getType()) { case BookPage.TYPE_IMAGE: i = (Image) resourceImporter.getAssetDescritptor(page.getUri(), Image.class); break; case BookPage.TYPE_RESOURCE: URL url = resourceImporter.getInputStreamCreator().buildURL( page.getUri()); if (url != null) { // Generate image int width = rightMargin - leftMargin > 0 ? rightMargin - leftMargin : 800; int height = bottomMargin - topMargin > 0 ? bottomMargin - topMargin : 600; imgGenerator.setSize(new Dimension(width, height)); imgGenerator.loadUrl(url); BufferedImage img = imgGenerator.getBufferedImage(); String path = ResourceImporterImpl.DRAWABLE + "/" + page.getUri().replace('/', '_') + Math.round(Math.random() * 100) + ".png"; File f = new File(resourceImporter.getNewProjecFolder(), path); try { ImageIO.write(img, "PNG", f); i = new Image("@" + path); } catch (IOException e) { logger.error("Error writing image {}", e); } } else { logger.warn("{} page not found.", page.getUri()); } break; case BookPage.TYPE_URL: logger .warn("Remote URLs are no longer supported by eAdventure. {} wasn't imported." + page.getUri()); break; } if (i != null) { image.addDrawable(i, x + leftMargin, topMargin); x += BOOK_WIDTH; paragraphColumn += 2; } } return image; } private SceneElement getArrow(Book book, SceneElement content, String resourceNormal, String resourceOver, Integer expression, Condition condition) { SceneElement arrow = new SceneElement(); this.addAppearance(book, arrow, resourceNormal, resourceOver); ElementField xVar = new ElementField(content, SceneElement.VAR_X); ElementField visibleVar = new ElementField(arrow, SceneElement.VAR_VISIBLE); InterpolationEf move = new InterpolationEf(xVar, 0, expression, 500, InterpolationLoopType.NO_LOOP, InterpolationType.DESACCELERATE); ConditionedEv event = new ConditionedEv(); event.setCondition(condition); event.addEffect(ConditionedEvType.CONDITIONS_MET, new ChangeFieldEf( visibleVar, EmptyCond.TRUE)); event.addEffect(ConditionedEvType.CONDITIONS_UNMET, new ChangeFieldEf( visibleVar, EmptyCond.FALSE)); arrow.addEvent(event); arrow.addBehavior(MouseGEv.MOUSE_LEFT_PRESSED, move); return arrow; } private void addTextDrawable(ComposedDrawable image, String text, Font font, EAdFont eadFont, int xOffset, int lineHeight, int textWidth) { List<String> lines = getLines(text, font, textWidth); for (String l : lines) { Caption caption = new Caption(stringHandler.generateNewString()); stringHandler.setString(caption.getText(), l); caption.setFont(eadFont); caption.setPadding(0); if (paragraphDispY + lineHeight > PAGE_TEXT_HEIGHT) { paragraphColumn++; paragraphDispY = TEXT_Y; } image.addDrawable(caption, getDispX() + xOffset, paragraphDispY); paragraphDispY += lineHeight; } } private int getDispX() { int offset = paragraphColumn / 2 * BOOK_WIDTH; if (paragraphColumn % 2 == 0) { offset += TEXT_X_1; } else offset += TEXT_X_2; return offset; } private ArrayList<String> getLines(String completeLine, Font f, int maximumWidth) { ArrayList<String> lines = new ArrayList<String>(); String[] words = completeLine.split(" "); String line = ""; int contWord = 0; int currentLineWidth = 0; while (contWord < words.length) { int nextWordWidth = (int) f.getStringBounds(words[contWord] + " ", frc).getWidth(); if (currentLineWidth + nextWordWidth <= maximumWidth) { currentLineWidth += nextWordWidth; line += words[contWord++] + " "; } else if (line != "") { lines.add(line); currentLineWidth = 0; line = ""; } else { line = splitLongWord(f, lines, words[contWord++], maximumWidth); currentLineWidth = (int) f.getStringBounds(line, frc) .getWidth(); } } if (line != "") lines.add(line); return lines; } private String splitLongWord(Font f, List<String> lines, String word, int lineWidth) { boolean finished = false; String currentLine = ""; int i = 0; while (!finished) { currentLine = ""; while (i < word.length() && f.getStringBounds(currentLine, frc).getWidth() < lineWidth) { currentLine += word.charAt(i++); } if (i == word.length()) { finished = true; } else { lines.add(currentLine); } } return currentLine; } private void addAppearance(Book book, SceneElement arrow, String normal, String over) { AssetDescriptor normalAsset = getArrowAsset(book, normal); AssetDescriptor overAsset = getArrowAsset(book, over); arrow.getDefinition().addAsset(SceneElementDef.appearance, normalAsset); String bundle = "over"; arrow.getDefinition().addAsset(bundle, SceneElementDef.appearance, overAsset); ChangeAppearanceEf change1 = new ChangeAppearanceEf(arrow, bundle); arrow.addBehavior(MouseGEv.MOUSE_ENTERED, change1); ChangeAppearanceEf change2 = new ChangeAppearanceEf(arrow, SceneElementDef.INITIAL_BUNDLE); arrow.addBehavior(MouseGEv.MOUSE_EXITED, change2); } private final Image normalLeft = new Image( "@drawable/default_left_arrow.png"); private final Image overLeft = new Image( "@drawable/default_left_over_arrow.png"); private final Image normalRight = new Image( "@drawable/default_right_arrow.png"); private final Image overRight = new Image( "@drawable/default_right_over_arrow.png"); private AssetDescriptor getArrowAsset(Book book, String resource) { String path = book.getResources().get(0).getAssetPath(resource); if (path != null) { return resourceImporter.getAssetDescritptor(path, Image.class); } else { if (resource.equals(Book.RESOURCE_TYPE_ARROW_LEFT_NORMAL)) { return normalLeft; } else if (resource.equals(Book.RESOURCE_TYPE_ARROW_LEFT_OVER)) { return overLeft; } else if (resource.equals(Book.RESOURCE_TYPE_ARROW_RIGHT_NORMAL)) { return normalRight; } else if (resource.equals(Book.RESOURCE_TYPE_ARROW_RIGHT_OVER)) { return overRight; } } return null; } }