/********************************************** * Copyright (C) 2010 Lukas Laag * This file is part of svgreal. * * svgreal is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * svgreal 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with svgreal. If not, see http://www.gnu.org/licenses/ **********************************************/ package org.vectomatic.svg.edit.client.engine; import org.vectomatic.dom.svg.OMSVGDefsElement; import org.vectomatic.dom.svg.OMSVGDocument; import org.vectomatic.dom.svg.OMSVGGElement; import org.vectomatic.dom.svg.OMSVGLength; import org.vectomatic.dom.svg.OMSVGPathElement; import org.vectomatic.dom.svg.OMSVGPathSegList; import org.vectomatic.dom.svg.OMSVGPatternElement; import org.vectomatic.dom.svg.OMSVGPoint; import org.vectomatic.dom.svg.OMSVGRectElement; import org.vectomatic.dom.svg.OMSVGSVGElement; import org.vectomatic.dom.svg.impl.SVGSVGElement; import org.vectomatic.dom.svg.utils.DOMHelper; import org.vectomatic.dom.svg.utils.SVGConstants; import org.vectomatic.svg.edit.client.AppBundle; import org.vectomatic.svg.edit.client.AppCss; import org.vectomatic.svg.edit.client.model.svg.SVGViewBoxElementModel; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.event.dom.client.MouseMoveEvent; import com.google.gwt.event.dom.client.MouseMoveHandler; /** * Class to represent grids. * The grid has the following SVG structure: * <pre> * <defs> * <pattern id="docid-grid"> * <path> // fine grid * <path> // coarse grid * </pattern> * <pattern id="docid-vrule"> // vrule * <rect> // ruler background * <path> // ruler gradations * </pattern> * <pattern id="docid-rule"> // hrule * <rect> // ruler background * <path> // ruler gradations * </pattern> * </defs> * * <g> * <rect style="fill:url('#docid-grid');stroke:black;"/> // grid * * <g> // hruler * <rect style="fill:url('#docid-hrule');"/> * <g> * <text x="0" y="-10">0</text> * ... * <text x="200" y="-10">N</text> * </g> * <path transform='translate(x,y)'> // hmarker * </g> * * <g> // vruler * <rect style="fill:url('#docid-vrule');"/> * <g> * <text x="-15" y="0">0</text> * <text x="-15" y="300">300</text> * </g> * <path transform='translate(x,y)'> // hmarker * </g> * </g> * </pre> * @author laaglu */ public class Grid { /** * True if the grid and rulers are visible */ private boolean showsGrid; /** * True if the coordinates and guides are visible */ private boolean showsGuides; /** * True if the mouse input is rounded to the grid */ private boolean snapsToGrid; /** * The grid root defs */ private OMSVGDefsElement defs; /** * The grid root element */ private OMSVGGElement root; /** * The grid element */ private OMSVGRectElement grid; /** * The horizontal ruler element */ private OMSVGGElement hruler; /** * The vertical ruler element */ private OMSVGGElement vruler; /** * The horizontal position marker */ private OMSVGPathElement hmarker; /** * The vertical position marker */ private OMSVGPathElement vmarker; /** * To update position markers */ private MouseMoveHandler moveHandler; /** * The svg model to which the grid is attached */ private SVGModel svgModel; /** * The grid horizontal spacing */ private float dx; /** * The grid vertical spacing */ private float dy; public Grid() { moveHandler = new MouseMoveHandler() { @Override public void onMouseMove(MouseMoveEvent event) { if (showsGuides && isAttached()) { OMSVGPoint p = svgModel.getCoordinates(event, true); setHMarkerPosition(p.getX()); setVMarkerPosition(p.getY()); } } }; } public boolean showsGrid() { return showsGrid; } public void setShowsGrid(boolean showsGrid) { GWT.log("Grid.setShowsGrid(" + showsGrid + ")"); this.showsGrid = showsGrid; grid.getStyle().setVisibility(showsGrid ? Visibility.VISIBLE : Visibility.HIDDEN); } public boolean showsGuides() { return showsGuides; } public void setShowsGuides(boolean showsGuides) { GWT.log("Grid.setShowsGuides(" + showsGuides + ")"); this.showsGuides = showsGuides; hruler.getStyle().setVisibility(showsGuides ? Visibility.VISIBLE : Visibility.HIDDEN); vruler.getStyle().setVisibility(showsGuides ? Visibility.VISIBLE : Visibility.HIDDEN); } public boolean snapsToGrid() { return snapsToGrid; } public void setSnapsToGrid(boolean snapsToGrid) { GWT.log("Grid.setSnapsToGrid(" + snapsToGrid + ")"); this.snapsToGrid = snapsToGrid; } public OMSVGDefsElement getDefs() { return defs; } public OMSVGGElement getRoot() { return root; } public MouseMoveHandler getMouseMoveHandler() { return moveHandler; } /** * Snaps the specified point to the grid * @param p A point in the model coordinate system * @return A point snapped to grid */ public OMSVGPoint snap(OMSVGPoint p) { SVGViewBoxElementModel viewBox = svgModel.getViewBox(); OMSVGSVGElement svg = svgModel.getSvgElement(); OMSVGPoint p0 = svg.createSVGPoint(viewBox.<Float>get(SVGConstants.SVG_X_ATTRIBUTE), viewBox.<Float>get(SVGConstants.SVG_Y_ATTRIBUTE)); OMSVGPoint p1 = p.substract(p0, svg.createSVGPoint()); p1.setX(dx * (Math.round(p1.getX() / dx))); p1.setY(dy * (Math.round(p1.getY() / dy))); return p1.add(p0); } public void setHMarkerPosition(float xPos) { if (isAttached()) { // Clamp xPos SVGViewBoxElementModel viewBox = svgModel.getViewBox(); float x = viewBox.<Float>get(SVGConstants.SVG_X_ATTRIBUTE); float width = viewBox.<Float>get(SVGConstants.SVG_WIDTH_ATTRIBUTE); xPos = Math.min(Math.max(xPos, x), x + width); hmarker.getTransform().getBaseVal().getItem(0).setTranslate(xPos, 0); } } public void setVMarkerPosition(float yPos) { if (isAttached()) { // Clamp yPos SVGViewBoxElementModel viewBox = svgModel.getViewBox(); float y = viewBox.<Float>get(SVGConstants.SVG_Y_ATTRIBUTE); float height = viewBox.<Float>get(SVGConstants.SVG_HEIGHT_ATTRIBUTE); yPos = Math.min(Math.max(yPos, y), y + height); vmarker.getTransform().getBaseVal().getItem(0).setTranslate(0, yPos); } } public boolean isAttached() { return svgModel != null; } public void attach(SVGModel svgModel) { this.svgModel = svgModel; AppCss css = AppBundle.INSTANCE.css(); OMSVGSVGElement svg = svgModel.getSvgElement(); OMSVGDocument doc = (OMSVGDocument) svg.getOwnerDocument(); String modelId = svg.getId(); // Build the grid pattern dx = dy = 5; OMSVGPathElement gridPath1 = new OMSVGPathElement(); OMSVGPathSegList gridSegs1 = gridPath1.getPathSegList(); for (int i = 1; i < 5; i++) { gridSegs1.appendItem(gridPath1.createSVGPathSegMovetoAbs(0, i * 5)); gridSegs1.appendItem(gridPath1.createSVGPathSegLinetoHorizontalAbs(25)); } for (int i = 1; i < 5; i++) { gridSegs1.appendItem(gridPath1.createSVGPathSegMovetoAbs(i * 5, 0)); gridSegs1.appendItem(gridPath1.createSVGPathSegLinetoVerticalAbs(25)); } gridPath1.setClassNameBaseVal(css.grid1()); OMSVGPathElement gridPath2 = new OMSVGPathElement(); OMSVGPathSegList gridSegs2 = gridPath2.getPathSegList(); gridSegs2.appendItem(gridPath2.createSVGPathSegMovetoAbs(0, 0)); gridSegs2.appendItem(gridPath1.createSVGPathSegLinetoHorizontalAbs(25)); gridSegs2.appendItem(gridPath2.createSVGPathSegMovetoAbs(0, 0)); gridSegs2.appendItem(gridPath1.createSVGPathSegLinetoVerticalAbs(25)); OMSVGPatternElement gridPattern = createPattern(0, 0, 25, 25); gridPath2.setClassNameBaseVal(css.grid2()); String gridPatternId = modelId + "-grid"; gridPattern.setId(gridPatternId); gridPattern.appendChild(gridPath1); gridPattern.appendChild(gridPath2); // Build the horizontal ruler pattern OMSVGRectElement hrulerPatternRect = doc.createSVGRectElement(0, 0, 100, 20, 0, 0); OMSVGPathElement hrulerPatternPath = new OMSVGPathElement(); OMSVGPathSegList hrulerPatternSegs = hrulerPatternPath.getPathSegList(); for (int i = 0; i < 10; i++) { hrulerPatternSegs.appendItem(hrulerPatternPath.createSVGPathSegMovetoAbs(i * 10, 20)); hrulerPatternSegs.appendItem(hrulerPatternPath.createSVGPathSegLinetoVerticalRel(i == 0 ? -18 : (i % 2 == 1 ? -5 : -10))); } OMSVGPatternElement hrulerPattern = createPattern(0, 0, 100, 20); hrulerPattern.setClassNameBaseVal(css.hrulerPattern()); String hrulerPatternId = modelId + "-hruler"; hrulerPattern.setId(hrulerPatternId); hrulerPattern.appendChild(hrulerPatternRect); hrulerPattern.appendChild(hrulerPatternPath); // Build the vertical ruler pattern OMSVGRectElement vrulerPatternRect = doc.createSVGRectElement(0, 0, 20, 100, 0, 0); OMSVGPathElement vrulerPatternPath = new OMSVGPathElement(); OMSVGPathSegList vrulerSegs = vrulerPatternPath.getPathSegList(); for (int i = 0; i < 10; i++) { vrulerSegs.appendItem(vrulerPatternPath.createSVGPathSegMovetoAbs(20, i * 10)); vrulerSegs.appendItem(vrulerPatternPath.createSVGPathSegLinetoHorizontalRel(i == 0 ? -18 : (i % 2 == 1 ? -5 : -10))); } OMSVGPatternElement vrulerPattern = createPattern(0, 0, 20, 100); vrulerPattern.setClassNameBaseVal(css.vrulerPattern()); String vrulerPatternId = modelId + "-vruler"; vrulerPattern.setId(vrulerPatternId); vrulerPattern.appendChild(vrulerPatternRect); vrulerPattern.appendChild(vrulerPatternPath); // Create the definitions defs = new OMSVGDefsElement(); defs.appendChild(gridPattern); defs.appendChild(hrulerPattern); defs.appendChild(vrulerPattern); // Create the grid SVGViewBoxElementModel viewBox = svgModel.getViewBox(); float x = viewBox.<Float>get(SVGConstants.SVG_X_ATTRIBUTE); float y = viewBox.<Float>get(SVGConstants.SVG_Y_ATTRIBUTE); float width = viewBox.<Float>get(SVGConstants.SVG_WIDTH_ATTRIBUTE); float height = viewBox.<Float>get(SVGConstants.SVG_HEIGHT_ATTRIBUTE); grid = doc.createSVGRectElement(x, y, width, height, 0, 0); grid.getStyle().setProperty(SVGConstants.CSS_FILL_PROPERTY, DOMHelper.toUrl(gridPatternId)); grid.getStyle().setProperty(SVGConstants.CSS_STROKE_PROPERTY, SVGConstants.CSS_BLACK_VALUE); // Create the horizontal ruler OMSVGRectElement hrulerRect = doc.createSVGRectElement(x, y - 20, width, 20, 0, 0); hrulerRect.getStyle().setProperty(SVGConstants.CSS_FILL_PROPERTY, DOMHelper.toUrl(hrulerPatternId)); OMSVGGElement hGradations = new OMSVGGElement(); for (int i = (((int)x)/100)*100; i < width; i+=100) { hGradations.appendChild(doc.createSVGTextElement(i, -10, OMSVGLength.SVG_LENGTHTYPE_PX, Integer.toString(i))); } // Create the horizontal marker hmarker = doc.createSVGPathElement(); OMSVGPathSegList hmarkerSegs = hmarker.getPathSegList(); hmarkerSegs.appendItem(hmarker.createSVGPathSegMovetoAbs(0, 0)); hmarkerSegs.appendItem(hmarker.createSVGPathSegLinetoRel(-4, -7)); hmarkerSegs.appendItem(hmarker.createSVGPathSegLinetoHorizontalRel(8)); hmarkerSegs.appendItem(hmarker.createSVGPathSegClosePath()); hmarker.getTransform().getBaseVal().appendItem(svg.createSVGTransform()); hmarker.setClassNameBaseVal(css.gridMarker()); hruler = new OMSVGGElement(); hruler.setClassNameBaseVal(css.hruler()); hruler.appendChild(hrulerRect); hruler.appendChild(hGradations); hruler.appendChild(hmarker); // Create the vertical ruler OMSVGRectElement vrulerRect = doc.createSVGRectElement(x - 20, y, 20, height, 0, 0); vrulerRect.getStyle().setProperty(SVGConstants.CSS_FILL_PROPERTY, DOMHelper.toUrl(vrulerPatternId)); OMSVGGElement vGradations = new OMSVGGElement(); for (int i = (((int)y)/100)*100; i < height; i+=100) { vGradations.appendChild(doc.createSVGTextElement(-15, i, OMSVGLength.SVG_LENGTHTYPE_PX, Integer.toString(i))); } // Create the vertical marker vmarker = doc.createSVGPathElement(); OMSVGPathSegList vmarkerSegs = vmarker.getPathSegList(); vmarkerSegs.appendItem(vmarker.createSVGPathSegMovetoAbs(0, 0)); vmarkerSegs.appendItem(vmarker.createSVGPathSegLinetoRel(-7, -4)); vmarkerSegs.appendItem(vmarker.createSVGPathSegLinetoVerticalRel(8)); vmarkerSegs.appendItem(vmarker.createSVGPathSegClosePath()); vmarker.getTransform().getBaseVal().appendItem(svg.createSVGTransform()); vmarker.setClassNameBaseVal(css.gridMarker()); vruler = new OMSVGGElement(); vruler.setClassNameBaseVal(css.vruler()); vruler.appendChild(vrulerRect); vruler.appendChild(vGradations); vruler.appendChild(vmarker); root = new OMSVGGElement(); root.appendChild(grid); root.appendChild(hruler); root.appendChild(vruler); setShowsGrid(false); setShowsGuides(false); } private static OMSVGPatternElement createPattern(float x, float y, float width, float height) { OMSVGPatternElement pattern = new OMSVGPatternElement(); pattern.getX().getBaseVal().newValueSpecifiedUnits(Unit.PX, x); pattern.getY().getBaseVal().newValueSpecifiedUnits(Unit.PX, y); pattern.getWidth().getBaseVal().newValueSpecifiedUnits(Unit.PX, width); pattern.getHeight().getBaseVal().newValueSpecifiedUnits(Unit.PX, height); if (!pattern.hasAttribute(SVGConstants.SVG_VIEW_BOX_ATTRIBUTE)) { StringBuilder builder = new StringBuilder(); builder.append(x); builder.append(" "); builder.append(y); builder.append(" "); builder.append(width); builder.append(" "); builder.append(height); pattern.setAttribute(SVGConstants.SVG_VIEW_BOX_ATTRIBUTE, builder.toString()); } else { pattern.getViewBox().getBaseVal().setX(x); pattern.getViewBox().getBaseVal().setY(y); pattern.getViewBox().getBaseVal().setWidth(width); pattern.getViewBox().getBaseVal().setHeight(height); } pattern.setAttribute(SVGConstants.SVG_PATTERN_UNITS_ATTRIBUTE, SVGConstants.SVG_USER_SPACE_ON_USE_VALUE); return pattern; } }