Search in sources :

Example 1 with SVGElement

use of org.w3c.dom.svg.SVGElement in project megameklab by MegaMek.

the class PrintRecordSheet method setArmorPips.

// Older method that was unsuitable for mechs but could work for vees and aerospace. Would need
// some updating to work with regions rather than fixed pips in the SVG.
protected void setArmorPips(Element group, int armorVal, boolean symmetric) {
    final String METHOD_NAME = "setArmorPips(SVGElement,int)";
    // First sort pips into rows. We can't rely on the pips to be in order, so we use
    // maps to allow non-sequential loading.
    Map<Integer, Map<Integer, Element>> rowMap = new TreeMap<>();
    int pipCount = 0;
    for (int i = 0; i < group.getChildNodes().getLength(); i++) {
        final Node n = group.getChildNodes().item(i);
        if (n instanceof SVGElement) {
            final SVGElement pip = (SVGElement) n;
            try {
                int index = pip.getId().indexOf(":");
                String[] coords = pip.getId().substring(index + 1).split(",");
                int r = Integer.parseInt(coords[0]);
                rowMap.putIfAbsent(r, new TreeMap<>());
                rowMap.get(r).put(Integer.parseInt(coords[1]), pip);
                pipCount++;
            } catch (Exception ex) {
                MegaMekLab.getLogger().log(getClass(), METHOD_NAME, LogLevel.ERROR, "Malformed id for SVG armor pip element: " + pip.getId());
            }
        }
    }
    if (pipCount < armorVal) {
        MegaMekLab.getLogger().log(getClass(), METHOD_NAME, LogLevel.ERROR, "Armor pip group " + ((SVGElement) group).getId() + " does not contain enough pips for " + armorVal + " armor");
        return;
    } else if (pipCount == armorVal) {
        // Simple case; leave as is
        return;
    }
    // Convert map into array for easier iteration in both directions. This will also skip
    // over gaps in the numbering.
    Element[][] rows = new Element[rowMap.size()][];
    int row = 0;
    for (Map<Integer, Element> r : rowMap.values()) {
        rows[row] = new Element[r.size()];
        int i = 0;
        for (Element e : r.values()) {
            rows[row][i] = e;
            i++;
        }
        row++;
    }
    // Get the ratio of the number of pips to show to the total number of pips
    // and distribute the number of pips proportionally to each side
    double saturation = Math.min(1.0, (double) armorVal / pipCount);
    // Now we find the center row, which is the row that has the same number of pips above
    // and below it as nearly as possible.
    int centerRow = rows.length / 2;
    int pipsAbove = 0;
    for (int r = 0; r < rows.length; r++) {
        pipsAbove += rows[r].length;
        if (pipsAbove > pipCount / 2) {
            centerRow = r;
            break;
        }
    }
    int showAbove = (int) Math.round(pipsAbove * saturation);
    int showBelow = armorVal - showAbove;
    // keep a running total of the number to hide in each row
    int[] showByRow = new int[rows.length];
    double remaining = pipsAbove;
    for (int i = centerRow; i >= 0; i--) {
        showByRow[i] = (int) Math.round(rows[i].length * showAbove / remaining);
        if (symmetric && (showByRow[i] > 0) && (showByRow[i] % 2) != (rows[i].length % 2)) {
            if ((showByRow[i] < showAbove) && (showByRow[i] < rows[i].length)) {
                showByRow[i]++;
            } else {
                showByRow[i]--;
            }
        }
        showAbove -= showByRow[i];
        remaining -= rows[i].length;
    }
    // We may have some odd ones left over due to symmetry imposed on middle pip of the row
    showBelow += showAbove;
    remaining = pipCount - pipsAbove;
    for (int i = centerRow + 1; i < rows.length; i++) {
        showByRow[i] = (int) Math.round(rows[i].length * showBelow / remaining);
        if (symmetric && (showByRow[i] > 0) && (showByRow[i] % 2) != (rows[i].length % 2)) {
            if ((showByRow[i] < showBelow) && (showByRow[i] < rows[i].length)) {
                showByRow[i]++;
            } else {
                showByRow[i]--;
            }
        }
        showBelow -= showByRow[i];
        remaining -= rows[i].length;
    }
    // Now we need to deal with leftovers, starting in the middle and adding one or two at a time
    // (depending on whether there are an odd or even number of pips in the row) moving out toward
    // the top and bottom and repeating until they are all placed.
    remaining = showBelow;
    while (remaining > 0) {
        for (int i = 0; i <= centerRow; i++) {
            row = centerRow - i;
            int toAdd = symmetric ? (2 - rows[row].length % 2) : 1;
            if (remaining < toAdd) {
                continue;
            }
            if (rows[row].length >= showByRow[row] + toAdd) {
                showByRow[row] += toAdd;
                remaining -= toAdd;
            }
            if (i > 0) {
                row = centerRow + i;
                if (row >= rows.length) {
                    continue;
                }
                toAdd = symmetric ? (2 - rows[row].length % 2) : 1;
                if (remaining < toAdd) {
                    continue;
                }
                if (rows[row].length >= showByRow[row] + toAdd) {
                    showByRow[row] += toAdd;
                    remaining -= toAdd;
                }
            }
        }
    }
    // Now select which pips in each row to hide
    for (row = 0; row < rows.length; row++) {
        int toHide = rows[row].length - showByRow[row];
        if (toHide == 0) {
            continue;
        }
        double ratio = (double) toHide / rows[row].length;
        int length = rows[row].length;
        if (symmetric) {
            length /= 2;
            if (toHide % 2 == 1) {
                hideElement(rows[row][length]);
                toHide--;
                ratio = (double) toHide / (rows[row].length - 1);
            }
        }
        Set<Integer> indices = new HashSet<>();
        double accum = 0.0;
        for (int i = length % 2; i < length; i += 2) {
            accum += ratio;
            if (accum >= 1 - saturation) {
                indices.add(i);
                accum -= 1.0;
                toHide--;
                if (symmetric) {
                    indices.add(rows[row].length - 1 - i);
                    toHide--;
                }
            }
            if (toHide == 0) {
                break;
            }
        }
        if (toHide > 0) {
            for (int i = length - 1; i >= 0; i -= 2) {
                accum += ratio;
                if (accum >= saturation) {
                    indices.add(i);
                    accum -= 1.0;
                    toHide--;
                    if (symmetric) {
                        indices.add(rows[row].length - 1 - i);
                        toHide--;
                    }
                }
                if (toHide == 0) {
                    break;
                }
            }
        }
        int i = 0;
        while (toHide > 0) {
            if (!indices.contains(i)) {
                indices.add(i);
                toHide--;
                if (symmetric) {
                    indices.add(rows[row].length - 1 - i);
                    toHide--;
                }
            }
            i++;
        }
        for (int index : indices) {
            hideElement(rows[row][index]);
        }
    }
}
Also used : SVGElement(org.w3c.dom.svg.SVGElement) Node(org.w3c.dom.Node) GraphicsNode(org.apache.batik.gvt.GraphicsNode) SVGRectElement(org.w3c.dom.svg.SVGRectElement) SVGElement(org.w3c.dom.svg.SVGElement) Element(org.w3c.dom.Element) TreeMap(java.util.TreeMap) IOException(java.io.IOException) FileNotFoundException(java.io.FileNotFoundException) PrinterException(java.awt.print.PrinterException) Map(java.util.Map) TreeMap(java.util.TreeMap) HashSet(java.util.HashSet)

Example 2 with SVGElement

use of org.w3c.dom.svg.SVGElement in project elki by elki-project.

the class BatikUtil method getRelativeCoordinates.

/**
 * Get the relative coordinates of a point within the coordinate system of a
 * particular SVG Element.
 *
 * @param evt Event, needs to be a DOMMouseEvent
 * @param reference SVG Element the coordinate system is used of
 * @return Array containing the X and Y values
 */
public static double[] getRelativeCoordinates(Event evt, Element reference) {
    if (evt instanceof DOMMouseEvent && reference instanceof SVGLocatable && reference instanceof SVGElement) {
        // Get the screen (pixel!) coordinates
        DOMMouseEvent gnme = (DOMMouseEvent) evt;
        SVGMatrix mat = ((SVGLocatable) reference).getScreenCTM();
        SVGMatrix imat = mat.inverse();
        SVGPoint cPt = ((SVGElement) reference).getOwnerSVGElement().createSVGPoint();
        cPt.setX(gnme.getClientX());
        cPt.setY(gnme.getClientY());
        // Have Batik transform the screen (pixel!) coordinates into SVG element
        // coordinates
        cPt = cPt.matrixTransform(imat);
        return new double[] { cPt.getX(), cPt.getY() };
    }
    return null;
}
Also used : SVGPoint(org.w3c.dom.svg.SVGPoint) SVGLocatable(org.w3c.dom.svg.SVGLocatable) SVGElement(org.w3c.dom.svg.SVGElement) SVGMatrix(org.w3c.dom.svg.SVGMatrix) DOMMouseEvent(org.apache.batik.dom.events.DOMMouseEvent)

Example 3 with SVGElement

use of org.w3c.dom.svg.SVGElement in project megameklab by MegaMek.

the class PrintRecordSheet method addPips.

/**
 * Adds pips to the SVG diagram. The rows are defined in the SVG diagram with a series of <rect>
 * elements, each of which determines the bounds of a row of pips. The spacing between pips is
 * determined by the height of the first row. If rows overlap the pips are offset by half in the next
 * row.
 *
 * @param group           A <g> element that has <rect> children that describe pip rows
 * @param pipCount        The number of pips to place in the region
 * @param symmetric       If true, the left and right sides will be mirror images (assuming the row
 *                        bounds are symmetric). Used for regions on a unit's center line.
 * @param size            The ratio of pip radius to the spacing between pips.
 * @param strokeWidth     The value to use for the stroke-width attribute when drawing the pips.
 */
protected void addPips(Element group, int pipCount, boolean symmetric, PipType pipType, double size, double strokeWidth) {
    if (pipCount == 0) {
        return;
    }
    final String METHOD_NAME = "addArmorPips(SVGElement,int)";
    double spacing = 6.15152;
    double left = Double.MAX_VALUE;
    double top = Double.MAX_VALUE;
    double right = 0;
    double bottom = 0;
    List<Rectangle2D> regions = new ArrayList<>();
    for (int i = 0; i < group.getChildNodes().getLength(); i++) {
        final Node r = group.getChildNodes().item(i);
        if (r instanceof SVGRectElement) {
            Rectangle2D bbox = getRectBBox((SVGRectElement) r);
            if (bbox.getX() < left) {
                left = bbox.getX();
            }
            if (bbox.getY() < top) {
                top = bbox.getY();
            }
            if (bbox.getX() + bbox.getWidth() > right) {
                right = bbox.getX() + bbox.getWidth();
            }
            if (bbox.getY() + bbox.getHeight() > bottom) {
                bottom = bbox.getY() + bbox.getHeight();
            }
            regions.add(bbox);
        }
    }
    if (regions.isEmpty()) {
        MegaMekLab.getLogger().log(getClass(), METHOD_NAME, LogLevel.WARNING, "No pip rows defined for region " + group.getAttribute("id"));
        return;
    }
    Rectangle2D bounds = new Rectangle2D.Double(left, top, right - left, bottom - top);
    double aspect = bounds.getWidth() / bounds.getHeight();
    double centerLine = regions.get(0).getX() + regions.get(0).getWidth() / 2.0;
    int maxWidth = 0;
    Collections.sort(regions, (r1, r2) -> (int) r1.getY() - (int) r2.getY());
    // Maximum number of pips that can be displayed on each row
    int[] rowLength = null;
    int[][] halfPipCount = null;
    int totalPips = 0;
    double scale = 1.0;
    List<Rectangle2D> rows = null;
    while (totalPips < pipCount) {
        totalPips = 0;
        rows = rescaleRows(regions, scale);
        rowLength = new int[rows.size()];
        halfPipCount = new int[rows.size()][];
        double prevRowBottom = 0;
        int centerPip = 0;
        spacing = rows.stream().mapToDouble(Rectangle2D::getHeight).min().orElse(spacing);
        for (int i = 0; i < rows.size(); i++) {
            final Rectangle2D rect = rows.get(i);
            int halfPipsLeft = (int) ((centerLine - rect.getX()) / (spacing / 2));
            int halfPipsRight = (int) ((rect.getX() + rect.getWidth() - centerLine) / (spacing / 2));
            if ((i > 0) && (rect.getY() < prevRowBottom)) {
                centerPip = (1 - centerPip);
                if (halfPipsLeft % 2 != centerPip) {
                    halfPipsLeft--;
                }
                if (halfPipsRight % 2 != centerPip) {
                    halfPipsRight--;
                }
                rowLength[i] = (halfPipsLeft + halfPipsRight) / 2;
            } else {
                rowLength[i] = (halfPipsLeft + halfPipsRight) / 2;
                centerPip = rowLength[i] % 2;
            }
            if (rowLength[i] > maxWidth) {
                maxWidth = rowLength[i];
            }
            halfPipCount[i] = new int[] { halfPipsLeft, halfPipsRight };
            totalPips += rowLength[i];
            prevRowBottom = rect.getY() + spacing;
        }
        scale *= 0.9;
    }
    ;
    int nRows = adjustedRows(pipCount, rows.size(), maxWidth, aspect);
    // Now we need to select the rows to use. If the total pips available in those rows is
    // insufficient, add a row and try again.
    int available = 0;
    int minWidth = maxWidth;
    List<Integer> useRows = new ArrayList<>();
    while (available < pipCount) {
        int start = rows.size() / (nRows * 2);
        for (int i = 0; i < nRows; i++) {
            int r = start + i * rows.size() / nRows;
            if (rowLength[r] > 0) {
                useRows.add(r);
                available += rowLength[r];
                if (rowLength[r] < minWidth) {
                    minWidth = rowLength[r];
                }
            }
        }
        if (available < pipCount) {
            nRows++;
            available = 0;
            useRows.clear();
            minWidth = maxWidth;
        }
    }
    // Sort the rows into the order pips should be added: longest rows first, then for rows of
    // equal length the one closest to the middle first
    final int rowCount = rows.size();
    final int[] rowSize = Arrays.copyOf(rowLength, rowLength.length);
    Collections.sort(useRows, (r1, r2) -> {
        if (rowSize[r1] == rowSize[r2]) {
            return Math.abs(r1 - rowCount / 2) - Math.abs(r2 - rowCount / 2);
        } else {
            return rowSize[r2] - rowSize[r1];
        }
    });
    // Now we iterate through the rows and assign pips as many times as it takes to get all assigned.
    int[] pipsByRow = new int[rows.size()];
    int remaining = pipCount;
    while (remaining > 0) {
        for (int r : useRows) {
            if (rowLength[r] > pipsByRow[r]) {
                int toAdd = Math.min(remaining, Math.min(rowLength[r] / minWidth, rowLength[r] - pipsByRow[r]));
                pipsByRow[r] += toAdd;
                remaining -= toAdd;
            }
        }
    }
    // an odd number of pips.
    if (symmetric) {
        // First we remove all the odd pips in even rows
        remaining = 0;
        for (int r = 0; r < rows.size(); r++) {
            if ((rowLength[r] % 2 == 0) && (pipsByRow[r] % 2 == 1)) {
                pipsByRow[r]--;
                remaining++;
            }
        }
        // Now we go through all the selected rows and assign them; this time even rows can
        // only be assigned pips in pairs.
        int toAdd = 0;
        boolean added = false;
        do {
            for (int r : useRows) {
                toAdd = 2 - rowLength[r] % 2;
                if ((remaining >= toAdd) && (pipsByRow[r] + toAdd <= rowLength[r])) {
                    pipsByRow[r] += toAdd;
                    remaining -= toAdd;
                }
            }
        } while ((remaining > 0) && added);
        // We may still have one or more left. At this point all rows are considered available.
        int centerRow = rows.size() / 2;
        while (remaining > 0) {
            for (int i = 0; i <= centerRow; i++) {
                int r = centerRow - i;
                toAdd = 2 - rowLength[r] % 2;
                if (remaining < toAdd) {
                    continue;
                }
                if (rowLength[r] >= pipsByRow[r] + toAdd) {
                    pipsByRow[r] += toAdd;
                    remaining -= toAdd;
                }
                if (i > 0) {
                    r = centerRow + i;
                    if (r >= rows.size()) {
                        continue;
                    }
                    toAdd = 2 - rowLength[r] % 2;
                    if (remaining < toAdd) {
                        continue;
                    }
                    if (rowLength[r] >= pipsByRow[r] + toAdd) {
                        pipsByRow[r] += toAdd;
                        remaining -= toAdd;
                    }
                }
            }
            // with the remaining pip to one of the even rows.
            if (remaining == 1) {
                boolean noSingle = true;
                int fromRow = -1;
                for (int r = 0; r < rows.size(); r++) {
                    if (rowLength[r] % 2 == 1) {
                        if (pipsByRow[r] < rowLength[r]) {
                            noSingle = false;
                            break;
                        } else {
                            fromRow = r;
                        }
                    }
                }
                if (noSingle) {
                    pipsByRow[fromRow]--;
                    remaining++;
                }
            }
        }
    }
    // It's likely that there's extra spacing between rows, so we're going to check whether
    // we can increase horizontal spacing between pips to keep the approximate aspect ratio.
    int firstRow = 0;
    int lastRow = rows.size() - 1;
    int r = 0;
    while (r < rows.size()) {
        if (pipsByRow[r] > 0) {
            firstRow = r;
            break;
        }
        r++;
    }
    r = rows.size() - 1;
    while (r >= 0) {
        if (pipsByRow[r] > 0) {
            lastRow = r;
            break;
        }
        r--;
    }
    double targetWidth = aspect * (rows.get(lastRow).getY() + rows.get(lastRow).getHeight() - rows.get(firstRow).getY());
    double hSpacing = targetWidth / pipsByRow[firstRow] - spacing;
    for (r = firstRow + 1; r <= lastRow; r++) {
        if (pipsByRow[r] > 0) {
            hSpacing = Math.min(hSpacing, (Math.min(targetWidth, rows.get(r).getWidth()) - spacing) / pipsByRow[r]);
        }
    }
    if (hSpacing < spacing) {
        hSpacing = spacing;
    }
    for (r = 0; r < pipsByRow.length; r++) {
        if (pipsByRow[r] > 0) {
            double radius = rows.get(r).getHeight() * size;
            Element pip = null;
            // Symmetric and this row is centered
            if (symmetric && (halfPipCount[r][0] == halfPipCount[r][1])) {
                double leftX = centerLine - hSpacing;
                double rightX = centerLine;
                if (rowLength[r] % 2 == 1) {
                    leftX -= radius;
                    rightX += hSpacing - radius;
                    if (pipsByRow[r] % 2 == 1) {
                        pip = createPip(leftX + hSpacing, rows.get(r).getY(), radius, strokeWidth, pipType);
                        group.appendChild(pip);
                        pipsByRow[r]--;
                    }
                } else {
                    leftX += hSpacing / 2 - radius;
                    rightX += hSpacing / 2 - radius;
                }
                while (pipsByRow[r] > 0) {
                    pip = createPip(leftX, rows.get(r).getY(), radius, strokeWidth, pipType);
                    group.appendChild(pip);
                    pip = createPip(rightX, rows.get(r).getY(), radius, strokeWidth, pipType);
                    group.appendChild(pip);
                    leftX -= hSpacing;
                    rightX += hSpacing;
                    pipsByRow[r] -= 2;
                }
            } else {
                // If the location is symmetric but the middle of the current row is to the left
                // of the centerline, right justify. If non-symmetric, balance the extra space at the
                // ends of the rows with any odd space going on the right margin.
                double x = centerLine - halfPipCount[r][0] * spacing / 2.0;
                if (symmetric && halfPipCount[r][0] > halfPipCount[r][1]) {
                    x += (rowLength[r] - pipsByRow[r]) * hSpacing;
                } else if (!symmetric) {
                    x += ((rowLength[r] - pipsByRow[r]) / 2) * hSpacing;
                }
                while (pipsByRow[r] > 0) {
                    pip = createPip(x, rows.get(r).getY(), radius, strokeWidth, pipType);
                    group.appendChild(pip);
                    pipsByRow[r]--;
                    x += hSpacing;
                }
            }
        }
    }
}
Also used : Node(org.w3c.dom.Node) GraphicsNode(org.apache.batik.gvt.GraphicsNode) SVGRectElement(org.w3c.dom.svg.SVGRectElement) SVGElement(org.w3c.dom.svg.SVGElement) Element(org.w3c.dom.Element) Rectangle2D(java.awt.geom.Rectangle2D) ArrayList(java.util.ArrayList) SVGRectElement(org.w3c.dom.svg.SVGRectElement)

Aggregations

SVGElement (org.w3c.dom.svg.SVGElement)3 GraphicsNode (org.apache.batik.gvt.GraphicsNode)2 Element (org.w3c.dom.Element)2 Node (org.w3c.dom.Node)2 SVGRectElement (org.w3c.dom.svg.SVGRectElement)2 Rectangle2D (java.awt.geom.Rectangle2D)1 PrinterException (java.awt.print.PrinterException)1 FileNotFoundException (java.io.FileNotFoundException)1 IOException (java.io.IOException)1 ArrayList (java.util.ArrayList)1 HashSet (java.util.HashSet)1 Map (java.util.Map)1 TreeMap (java.util.TreeMap)1 DOMMouseEvent (org.apache.batik.dom.events.DOMMouseEvent)1 SVGLocatable (org.w3c.dom.svg.SVGLocatable)1 SVGMatrix (org.w3c.dom.svg.SVGMatrix)1 SVGPoint (org.w3c.dom.svg.SVGPoint)1