OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Blame | Last modification | View Log | RSS feed

/*
 * Copyright 2014-2015 Robin Stuart, Robert Elliott, Daniel Gredler
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package uk.org.okapibarcode.output;

import static uk.org.okapibarcode.backend.HumanReadableAlignment.CENTER;
import static uk.org.okapibarcode.backend.HumanReadableAlignment.JUSTIFY;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.font.FontRenderContext;
import java.awt.font.TextAttribute;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.Collections;
import java.util.List;

import uk.org.okapibarcode.backend.Hexagon;
import uk.org.okapibarcode.backend.HumanReadableAlignment;
import uk.org.okapibarcode.backend.Symbol;
import uk.org.okapibarcode.backend.TextBox;

/**
 * Renders symbologies using the Java 2D API.
 */
public class Java2DRenderer implements SymbolRenderer {

    /** The graphics to render to. */
    private final Graphics2D g2d;

    /** The magnification factor to apply. */
    private final double magnification;

    /** The paper (background) color. */
    private final Color paper;

    /** The ink (foreground) color. */
    private final Color ink;

    /**
     * Creates a new Java 2D renderer. If the specified paper color is <tt>null</tt>, the symbol is
     * drawn without clearing the existing <tt>g2d</tt> background.
     *
     * @param g2d the graphics to render to
     * @param magnification the magnification factor to apply
     * @param paper the paper (background) color (may be <tt>null</tt>)
     * @param ink the ink (foreground) color
     */
    public Java2DRenderer(final Graphics2D g2d, final double magnification, final Color paper, final Color ink) {
        this.g2d = g2d;
        this.magnification = magnification;
        this.paper = paper;
        this.ink = ink;
    }

    /** {@inheritDoc} */
    @Override
    public void render(final Symbol symbol) {

        final int marginX = (int) (symbol.getQuietZoneHorizontal() * this.magnification);
        final int marginY = (int) (symbol.getQuietZoneVertical() * this.magnification);

        Font f = symbol.getFont();
        if (f != null) {
            f = f.deriveFont((float) (f.getSize2D() * this.magnification));
        } else {
            f = new Font(symbol.getFontName(), Font.PLAIN, (int) (symbol.getFontSize() * this.magnification));
            f = f.deriveFont(Collections.singletonMap(TextAttribute.TRACKING, 0));
        }

        final Font oldFont = this.g2d.getFont();
        final Color oldColor = this.g2d.getColor();

        if (this.paper != null) {
            final int w = (int) (symbol.getWidth() * this.magnification);
            final int h = (int) (symbol.getHeight() * this.magnification);
            this.g2d.setColor(this.paper);
            this.g2d.fillRect(0, 0, w, h);
        }

        this.g2d.setColor(this.ink);

        for (final Rectangle2D.Double rect : symbol.getRectangles()) {
            final double x = rect.x * this.magnification + marginX;
            final double y = rect.y * this.magnification + marginY;
            final double w = rect.width * this.magnification;
            final double h = rect.height * this.magnification;
            this.g2d.fillRect((int) x, (int) y, (int) w, (int) h);
        }

        for (final TextBox text : symbol.getTexts()) {
            final HumanReadableAlignment alignment = text.alignment == JUSTIFY && text.text.length() == 1 ? CENTER : text.alignment;
            final Font font = alignment != JUSTIFY ? f : addTracking(f, text.width * this.magnification, text.text, this.g2d);
            this.g2d.setFont(font);
            final FontMetrics fm = this.g2d.getFontMetrics();
            final Rectangle2D bounds = fm.getStringBounds(text.text, this.g2d);
            final float y = (float) (text.y * this.magnification) + marginY;
            float x;
            switch (alignment) {
            case LEFT:
            case JUSTIFY:
                x = (float) (this.magnification * text.x + marginX);
                break;
            case RIGHT:
                x = (float) (this.magnification * text.x + this.magnification * text.width - bounds.getWidth() + marginX);
                break;
            case CENTER:
                x = (float) (this.magnification * text.x + this.magnification * text.width / 2 - bounds.getWidth() / 2 + marginX);
                break;
            default:
                throw new IllegalStateException("Unknown alignment: " + alignment);
            }
            this.g2d.drawString(text.text, x, y);
        }

        for (final Hexagon hexagon : symbol.getHexagons()) {
            final Polygon polygon = new Polygon();
            for (int j = 0; j < 6; j++) {
                polygon.addPoint((int) (hexagon.pointX[j] * this.magnification + marginX), (int) (hexagon.pointY[j] * this.magnification + marginY));
            }
            this.g2d.fill(polygon);
        }

        final List<Ellipse2D.Double> target = symbol.getTarget();
        for (int i = 0; i + 1 < target.size(); i += 2) {
            final Ellipse2D.Double outer = adjust(target.get(i), this.magnification, marginX, marginY);
            final Ellipse2D.Double inner = adjust(target.get(i + 1), this.magnification, marginX, marginY);
            final Area area = new Area(outer);
            area.subtract(new Area(inner));
            this.g2d.fill(area);
        }

        this.g2d.setFont(oldFont);
        this.g2d.setColor(oldColor);
    }

    private static Ellipse2D.Double adjust(final Ellipse2D.Double ellipse, final double magnification, final int marginX, final int marginY) {
        final double x = ellipse.x * magnification + marginX;
        final double y = ellipse.y * magnification + marginY;
        final double w = ellipse.width * magnification + marginX;
        final double h = ellipse.height * magnification + marginY;
        return new Ellipse2D.Double(x, y, w, h);
    }

    private static Font addTracking(final Font baseFont, final double maxTextWidth, final String text, final Graphics2D g2d) {
        final FontRenderContext frc = g2d.getFontRenderContext();
        final double originalWidth = baseFont.getStringBounds(text, frc).getWidth();
        final double extraSpace = maxTextWidth - originalWidth;
        final double extraSpacePerGap = extraSpace / (text.length() - 1);
        final double scaleX = baseFont.isTransformed() ? baseFont.getTransform().getScaleX() : 1;
        final double tracking = extraSpacePerGap / (baseFont.getSize2D() * scaleX);
        return baseFont.deriveFont(Collections.singletonMap(TextAttribute.TRACKING, tracking));
    }
}