All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.metsci.glimpse.jogamp.opengl.util.awt.text.AbstractGlyphProducer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2012 JogAmp Community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of JogAmp Community.
 */
package com.metsci.glimpse.jogamp.opengl.util.awt.text;

import java.awt.Font;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphMetrics;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.metsci.glimpse.com.jogamp.opengl.util.awt.TextRenderer.RenderDelegate;


/**
 * Skeletal implementation of {@link GlyphProducer}.
 */
abstract class AbstractGlyphProducer implements GlyphProducer {

    /**
     * Reusable array for creating glyph vectors for a single character.
     */
    /*@Nonnull*/
    private final char[] characters = new char[1];

    /**
     * Font glyphs made from.
     */
    /*@Nonnull*/
    private final Font font;

    /**
     * Rendering controller.
     */
    /*@Nonnull*/
    private final RenderDelegate renderDelegate;

    /**
     * Font render details.
     */
    /*@Nonnull*/
    private final FontRenderContext fontRenderContext;

    /**
     * Cached glyph vectors.
     */
    /*@Nonnull*/
    private final Map glyphVectors = new HashMap();

    /**
     * Returned glyphs.
     */
    /*@Nonnull*/
    private final List output = new ArrayList();

    /**
     * View of glyphs.
     */
    /*@Nonnull*/
    private final List outputView = Collections.unmodifiableList(output);

    /**
     * Constructs an abstract glyph producer.
     *
     * @param font Font glyphs will be made from
     * @param rd Object for controlling rendering
     * @param frc Details on how to render fonts
     * @throws NullPointerException if font, render delegate, or font render context is null
     */
    AbstractGlyphProducer(/*@Nonnull*/ final Font font,
                          /*@Nonnull*/ final RenderDelegate rd,
                          /*@Nonnull*/ final FontRenderContext frc) {

        Check.notNull(font, "Font cannot be null");
        Check.notNull(rd, "Render delegate cannot be null");
        Check.notNull(frc, "Font render context cannot be null");

        this.font = font;
        this.renderDelegate = rd;
        this.fontRenderContext = frc;
    }

    /**
     * Adds outer space around a rectangle.
     *
     * 

* This method was formally called "normalize." * *

* Give ourselves a boundary around each entity on the backing store in order to prevent * bleeding of nearby Strings due to the fact that we use linear filtering * *

* Note that this boundary is quite heuristic and is related to how far away in 3D we may view * the text -- heuristically, 1.5% of the font's height. * * @param src Original rectangle * @param font Font being used to create glyphs * @return Rectangle with margin added, not null * @throws NullPointerException if rectangle or font is null */ /*@Nonnull*/ private static Rectangle2D addMarginTo(/*@Nonnull*/ final Rectangle2D src, /*@Nonnull*/ final Font font) { final int boundary = (int) Math.max(1, 0.015 * font.getSize()); final int x = (int) Math.floor(src.getMinX() - boundary); final int y = (int) Math.floor(src.getMinY() - boundary); final int w = (int) Math.ceil(src.getWidth() + 2 * boundary); final int h = (int) Math.ceil(src.getHeight() + 2 * boundary);; return new Rectangle2D.Float(x, y, w, h); } /** * Adds inner space to a rectangle. * *

* This method was formally called "preNormalize." * *

* Need to round to integer coordinates. * *

* Also give ourselves a little slop around the reported bounds of glyphs because it looks like * neither the visual nor the pixel bounds works perfectly well. * * @param src Original rectangle * @return Rectangle with padding added, not null * @throws NullPointerException if rectangle is null */ /*@Nonnull*/ private static Rectangle2D addPaddingTo(/*@Nonnull*/ final Rectangle2D src) { final int minX = (int) Math.floor(src.getMinX()) - 1; final int minY = (int) Math.floor(src.getMinY()) - 1; final int maxX = (int) Math.ceil(src.getMaxX()) + 1; final int maxY = (int) Math.ceil(src.getMaxY()) + 1; return new Rectangle2D.Float(minX, minY, maxX - minX, maxY - minY); } /** * Adds a glyph to the reusable list for output. * * @param glyph Glyph to add to output * @throws NullPointerException if glyph is null */ protected final void addToOutput(/*@Nonnull*/ final Glyph glyph) { Check.notNull(glyph, "Glyph cannot be null"); output.add(glyph); } /** * Clears the reusable list for output. */ protected final void clearOutput() { output.clear(); } /** * Makes a glyph vector for a character. * * @param c Character to create glyph vector from * @return Glyph vector for the character, not null */ /*@Nonnull*/ protected final GlyphVector createGlyphVector(final char c) { characters[0] = c; return font.createGlyphVector(fontRenderContext, characters); } /** * Makes a glyph vector for a string. * * @param font Style of text * @param frc Details on how to render font * @param str Text as a string * @return Glyph vector for the string, not null * @throws NullPointerException if string is null */ /*@Nonnull*/ protected final GlyphVector createGlyphVector(/*@Nonnull*/ final String str) { Check.notNull(str, "String cannot be null"); GlyphVector gv = glyphVectors.get(str); // Check if already made if (gv != null) { return gv; } // Otherwise make and store it final char[] text = str.toCharArray(); final int len = str.length(); gv = font.layoutGlyphVector(fontRenderContext, text, 0, len, 0); glyphVectors.put(str, gv); return gv; } /*@CheckForSigned*/ @Override public final float findAdvance(final char c) { // Check producer's inventory first final Glyph glyph = createGlyph(c); if (glyph != null) { return glyph.advance; } // Otherwise create the glyph vector final GlyphVector gv = createGlyphVector(c); final GlyphMetrics gm = gv.getGlyphMetrics(0); return gm.getAdvance(); } /*@Nonnull*/ @Override public final Rectangle2D findBounds(/*@Nonnull*/ final String str) { Check.notNull(str, "String cannot be null"); final List glyphs = createGlyphs(str); // Check if already computed bounds if (glyphs.size() == 1) { final Glyph glyph = glyphs.get(0); return glyph.bounds; } // Otherwise just recompute it return addPaddingTo(renderDelegate.getBounds(str, font, fontRenderContext)); } /** * Returns the font used to create glyphs. * * @return Font used to create glyphs, not null */ /*@Nonnull*/ protected final Font getFont() { return font; } /** * Returns a read-only view of this producer's reusable list for output. * * @return Read-only view of reusable list, not null */ /*@Nonnull*/ protected final List getOutput() { return outputView; } /** * Checks if any characters in a string require full layout. * *

* The process of creating and laying out glyph vectors is relatively complex and can slow down * text rendering significantly. This method is intended to increase performance by not * creating glyph vectors for strings with characters that can be treated independently. * *

* Currently the decision is very simple. It just treats any characters above the IPA * Extensions block as complex. This is convenient because most Latin characters are * treated as simple but Spacing Modifier Letters and Combining Diacritical Marks * are not. Ideally it would also be nice to have a few other blocks included, especially * Greek and maybe symbols, but that is perhaps best left for later work. * *

* A truly correct implementation may require a lot of research or developers with more * experience in the area. However, the following Unicode blocks are known to require full * layout in some form: * *

    *
  • Spacing Modifier Letters (02B0-02FF) *
  • Combining Diacritical Marks (0300-036F) *
  • Hebrew (0590-05FF) *
  • Arabic (0600-06FF) *
  • Arabic Supplement (0750-077F) *
  • Combining Diacritical Marks Supplement (1DC0-1FFF) *
  • Combining Diacritical Marks for Symbols (20D0-20FF) *
  • Arabic Presentation Forms-A (FB50–FDFF) *
  • Combining Half Marks (FE20–FE2F) *
  • Arabic Presentation Forms-B (FE70–FEFF) *
* *

* Asian scripts will also have letters that combine together, but it appears that the input * method may take care of that so it may not be necessary to check for them here. * *

* Finally, it should be noted that even Latin has characters that can combine into glyphs * called ligatures. The classic example is an 'f' and an 'i'. Java however will not make the * replacements itself so we do not need to consider that here. * * @param str Text of unknown character types * @return True if a complex character is found * @throws NullPointerException if string is null */ protected static boolean hasComplexCharacters(/*@Nonnull*/ final String str) { Check.notNull(str, "String cannot be null"); final int len = str.length(); for (int i = 0; i < len; ++i) { if (str.charAt(i) > 0x2AE) { return true; } } return false; } /** * Checks if a glyph vector is complex. * * @param gv Glyph vector to check * @return True if glyph vector is complex * @throws NullPointerException if glyph vector is null */ protected static boolean isComplex(/*@CheckForNull*/ final GlyphVector gv) { Check.notNull(gv, "Glyph vector cannot be null"); return gv.getLayoutFlags() != 0; } /** * Measures a glyph. * *

* Sets all the measurements in a glyph after it's created. * * @param glyph Visual representation of a character * @throws NullPointerException if glyph is null */ protected final void measure(/*@Nonnull*/ final Glyph glyph) { Check.notNull(glyph, "Glyph cannot be null"); // Compute visual boundary final Rectangle2D visualBox; if (glyph.str != null) { visualBox = renderDelegate.getBounds(glyph.str, font, fontRenderContext); } else { visualBox = renderDelegate.getBounds(glyph.glyphVector, fontRenderContext); } // Compute rectangles final Rectangle2D paddingBox = addPaddingTo(visualBox); final Rectangle2D marginBox = addMarginTo(paddingBox, font); // Set fields glyph.padding = new Glyph.Boundary(paddingBox, visualBox); glyph.margin = new Glyph.Boundary(marginBox, paddingBox); glyph.width = (float) paddingBox.getWidth(); glyph.height = (float) paddingBox.getHeight(); glyph.ascent = (float) paddingBox.getMinY() * -1; glyph.descent = (float) paddingBox.getMaxY(); glyph.kerning = (float) paddingBox.getMinX(); glyph.bounds = paddingBox; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy