/** Called when a glyph is freed to clear the state of the glyph for possible later reuse. */ static void reset (Glyph glyph) { glyph.id = 0; glyph.srcX = 0; glyph.srcY = 0; glyph.width = 0; glyph.height = 0; glyph.u = 0; glyph.v = 0; glyph.u2 = 0; glyph.v2 = 0; glyph.xoffset = 0; glyph.yoffset = 0; glyph.xadvance = 0; glyph.kerning = null; glyph.fixedWidth = false; }
/** Copies all contents from the first glyph to the second one. */ static void clone (Glyph from, Glyph to) { to.id = from.id; to.srcX = from.srcX; to.srcY = from.srcY; to.width = from.width; to.height = from.height; to.u = from.u; to.v = from.v; to.u2 = from.u2; to.v2 = from.v2; to.xoffset = from.xoffset; to.yoffset = from.yoffset; to.xadvance = from.xadvance; to.kerning = from.kerning; // Keep the same instance, there's no reason to deep clone it to.fixedWidth = from.fixedWidth; }
@Override protected void onApply (Glyph glyph, int localIndex) { // Calculate progress float progressModifier = (1f / intensity) * DEFAULT_INTENSITY; float normalFrequency = (1f / frequency) * DEFAULT_FREQUENCY; float progressOffset = localIndex / normalFrequency; float progress = calculateProgress(progressModifier, progressOffset); // Calculate offset float y = getLineHeight() * distance * Interpolation.sine.apply(-1, 1, progress) * DEFAULT_DISTANCE; // Calculate fadeout float fadeout = calculateFadeout(); y *= fadeout; // Apply changes glyph.yoffset += y; }
@Override protected void onApply (Glyph glyph, int localIndex) { // Calculate progress float progressModifier = (1f / intensity) * DEFAULT_INTENSITY; float normalFrequency = (1f / frequency) * DEFAULT_FREQUENCY; float progressOffset = localIndex / normalFrequency; float progress = calculateProgress(progressModifier, -progressOffset, false); // Calculate offset float interpolation = 0; float split = 0.2f; if (progress < split) { interpolation = Interpolation.pow2Out.apply(0, 1, progress / split); } else { interpolation = Interpolation.bounceOut.apply(1, 0, (progress - split) / (1f - split)); } float y = getLineHeight() * distance * interpolation * DEFAULT_DISTANCE; // Calculate fadeout float fadeout = calculateFadeout(); y *= fadeout; // Apply changes glyph.yoffset += y; }
/** Adds cached glyphs to the active BitmapFontCache as the char index progresses. */ private void addMissingGlyphs () { // Add additional glyphs to layout array, if any int glyphLeft = glyphCharIndex - cachedGlyphCharIndex; if (glyphLeft < 1) return; // Get runs GlyphLayout layout = super.getGlyphLayout(); Array<GlyphRun> runs = layout.runs; // Iterate through GlyphRuns to find the next glyph spot int glyphCount = 0; for (int runIndex = 0; runIndex < glyphRunCapacities.size; runIndex++) { int runCapacity = glyphRunCapacities.get(runIndex); if ((glyphCount + runCapacity) < cachedGlyphCharIndex) { glyphCount += runCapacity; continue; } // Get run and increase glyphCount up to its current size Array<Glyph> glyphs = runs.get(runIndex).glyphs; glyphCount += glyphs.size; // Next glyphs go here while (glyphLeft > 0) { // Skip run if this one is full int runSize = glyphs.size; if (runCapacity == runSize) { break; } // Put new glyph to this run cachedGlyphCharIndex++; glyphCount++; glyphLeft--; glyphs.add(glyphCache.get(cachedGlyphCharIndex)); } } }
@Override protected void onApply (Glyph glyph, int localIndex) { // Make sure we can hold enough entries for the current index if (localIndex >= lastOffsets.size / 2) { lastOffsets.setSize(lastOffsets.size + 16); } // Get last offsets float lastX = lastOffsets.get(localIndex * 2); float lastY = lastOffsets.get(localIndex * 2 + 1); // Calculate new offsets float x = getLineHeight() * distance * MathUtils.random(-1, 1) * DEFAULT_DISTANCE; float y = getLineHeight() * distance * MathUtils.random(-1, 1) * DEFAULT_DISTANCE; // Apply intensity float normalIntensity = MathUtils.clamp(intensity * DEFAULT_INTENSITY, 0, 1); x = Interpolation.linear.apply(lastX, x, normalIntensity); y = Interpolation.linear.apply(lastY, y, normalIntensity); // Apply fadeout float fadeout = calculateFadeout(); x *= fadeout; y *= fadeout; x = Math.round(x); y = Math.round(y); // Store offsets for the next tick lastOffsets.set(localIndex * 2, x); lastOffsets.set(localIndex * 2 + 1, y); // Apply changes glyph.xoffset += x; glyph.yoffset += y; }
@Override public Glyph getGlyph (char ch) { Glyph glyph = super.getGlyph(ch); if ((glyph == null || glyph == missingGlyph) && generators.size > 0) { for (int i = 0; i < generators.size; i++) { FreeTypeFontGenerator generator = generators.get(i); glyph = getGlyph(ch, generator); if (glyph != null && glyph != missingGlyph) break; } } if (glyph == null) { glyph = missingGlyph; } return glyph; }
/** Ugly code needed because Array uses an unchecked cast from Object[] to T[] */ private static void setGlyphs(Array<Glyph> array, Object[] newGlyphs) { array.clear(); for (Object obj : newGlyphs) { array.add((Glyph)obj); } }
@Override public float getKerning(int glyphId) { if (glyphCount == 0) { return 0f; } else if (glyphId != (char)glyphId) { return 0f; // libGDX Glyph.getKerning uses char instead of int } Glyph finalGlyph = getGlyph(glyphLayout, glyphCount - 1); int kerning = finalGlyph.getKerning((char)glyphId); return scaleXY * kerning; }
private static Glyph getGlyph(GlyphLayout layout, int index) { int offset = 0; for (GlyphRun run : layout.runs) { if (index < offset) { break; } if (index - offset < run.glyphs.size) { return run.glyphs.get(index - offset); } offset += run.glyphs.size; } throw new ArrayIndexOutOfBoundsException(index); }
private void requireSequence (CharSequence seq, int start, int end) { if (vertexData.length == 1) { // don't scan sequence if we just have one page and markup is disabled int newGlyphCount = font.markupEnabled ? countGlyphs(seq, start, end) : end - start; require(0, newGlyphCount); } else { for (int i = 0, n = tmpGlyphCount.length; i < n; i++) tmpGlyphCount[i] = 0; // determine # of glyphs in each page while (start < end) { char ch = seq.charAt(start++); if (ch == '[' && font.markupEnabled) { if (!(start < end && seq.charAt(start) == '[')) { // non escaped '[' while (start < end && seq.charAt(start) != ']') start++; start++; continue; } start++; } Glyph g = font.data.getGlyph(ch); if (g == null) continue; tmpGlyphCount[g.page]++; } // require that many for each page for (int i = 0, n = tmpGlyphCount.length; i < n; i++) require(i, tmpGlyphCount[i]); } }
protected void reset (Glyph glyph) { GlyphUtils.reset(glyph); }
/** Returns a glyph from this pool. The glyph may be new (from {@link Pool#newObject()}) or reused (previously * {@link Pool#free(Object) freed}). */ static Glyph obtain () { return pool.obtain(); }
/** Returns a glyph from this pool and clones it from the given one. The glyph may be new (from {@link Pool#newObject()}) or * reused (previously {@link Pool#free(Object) freed}). */ static Glyph obtainClone (Glyph from) { Glyph glyph = pool.obtain(); clone(from, glyph); return glyph; }
/** Puts the specified glyph in the pool, making it eligible to be returned by {@link #obtain()}. If the pool already contains * {@link #max} free glyphs, the specified glyph is reset but not added to the pool. */ static void free (Glyph glyph) { pool.free(glyph); }
/** Puts the specified glyphs in the pool. Null glyphs within the array are silently ignored. * @see #free(Object) */ static void freeAll (Array<Glyph> glyphs) { pool.freeAll(glyphs); }
/** Applies the effect to the given glyph. */ public final void apply (Glyph glyph, int glyphIndex) { int localIndex = glyphIndex - indexStart; onApply(glyph, localIndex); }
/** Called when this effect should be applied to the given glyph. */ protected abstract void onApply (Glyph glyph, int localIndex);
private void addToCache (GlyphLayout layout, float x, float y) { // y += 5f * font.getScaleY(); // Check if the number of font pages has changed. int pageCount = font.regions.size; if (pageVertices.length < pageCount) { float[][] newPageVertices = new float[pageCount][]; System.arraycopy(pageVertices, 0, newPageVertices, 0, pageVertices.length); pageVertices = newPageVertices; int[] newIdx = new int[pageCount]; System.arraycopy(idx, 0, newIdx, 0, idx.length); idx = newIdx; IntArray[] newPageGlyphIndices = new IntArray[pageCount]; int pageGlyphIndicesLength = 0; if (pageGlyphIndices != null) { pageGlyphIndicesLength = pageGlyphIndices.length; System.arraycopy(pageGlyphIndices, 0, newPageGlyphIndices, 0, pageGlyphIndices.length); } for (int i = pageGlyphIndicesLength; i < pageCount; i++) newPageGlyphIndices[i] = new IntArray(); pageGlyphIndices = newPageGlyphIndices; tempGlyphCount = new int[pageCount]; } layouts.add(layout); requireGlyphs(layout); for (int i = 0, n = layout.runs.size; i < n; i++) { GlyphRun run = layout.runs.get(i); Array<Glyph> glyphs = run.glyphs; FloatArray xAdvances = run.xAdvances; float color = run.color.toFloatBits(); float gx = x + run.x, gy = y + run.y; for (int ii = 0, nn = glyphs.size; ii < nn; ii++) { Glyph glyph = glyphs.get(ii); gx += xAdvances.get(ii); addGlyph(glyph, gx, gy, color); } } currentTint = whiteTint; // Cached glyphs have changed, reset the current tint. }
private void addGlyph (Glyph glyph, float x, float y, float color) { final float scaleX = font.data.scaleX, scaleY = font.data.scaleY; x += glyph.xoffset * scaleX; y += glyph.yoffset * scaleY; float width = glyph.width * scaleX, height = glyph.height * scaleY; final float u = glyph.u, u2 = glyph.u2, v = glyph.v, v2 = glyph.v2; if (integer) { x = Math.round(x); y = Math.round(y); width = Math.round(width); height = Math.round(height); } final float x2 = x + width, y2 = y + height; final int page = glyph.page; int idx = this.idx[page]; this.idx[page] += 20; if (pageGlyphIndices != null) pageGlyphIndices[page].add(glyphCount++); final float[] vertices = pageVertices[page]; vertices[idx++] = x; vertices[idx++] = y; vertices[idx++] = color; vertices[idx++] = u; vertices[idx++] = v; vertices[idx++] = x; vertices[idx++] = y2; vertices[idx++] = color; vertices[idx++] = u; vertices[idx++] = v2; vertices[idx++] = x2; vertices[idx++] = y2; vertices[idx++] = color; vertices[idx++] = u2; vertices[idx++] = v2; vertices[idx++] = x2; vertices[idx++] = y; vertices[idx++] = color; vertices[idx++] = u2; vertices[idx] = v; }
/** Returns null if glyph was not found. If there is nothing to render, for example with various space characters, then bitmap * is null. */ public GlyphAndBitmap generateGlyphAndBitmap (int c, int size, boolean flip) { setPixelSizes(0, size); SizeMetrics fontMetrics = face.getSize().getMetrics(); int baseline = FreeType.toInt(fontMetrics.getAscender()); // Check if character exists in this font. // 0 means 'undefined character code' if (face.getCharIndex(c) == 0) { return null; } // Try to load character if (!loadChar(c)) { throw new GdxRuntimeException("Unable to load character!"); } GlyphSlot slot = face.getGlyph(); // Try to render to bitmap Bitmap bitmap; if (bitmapped) { bitmap = slot.getBitmap(); } else if (!slot.renderGlyph(FreeType.FT_RENDER_MODE_NORMAL)) { bitmap = null; } else { bitmap = slot.getBitmap(); } GlyphMetrics metrics = slot.getMetrics(); Glyph glyph = new Glyph(); if (bitmap != null) { glyph.width = bitmap.getWidth(); glyph.height = bitmap.getRows(); } else { glyph.width = 0; glyph.height = 0; } glyph.xoffset = slot.getBitmapLeft(); glyph.yoffset = flip ? -slot.getBitmapTop() + baseline : -(glyph.height - slot.getBitmapTop()) - baseline; glyph.xadvance = FreeType.toInt(metrics.getHoriAdvance()); glyph.srcX = 0; glyph.srcY = 0; glyph.id = c; GlyphAndBitmap result = new GlyphAndBitmap(); result.glyph = glyph; result.bitmap = bitmap; return result; }
public void draw(Batch batch, float dx, float dy, float visibleGlyphs) { if (visibleGlyphs == 0 || glyphLayout.runs.size == 0) { return; // Nothing to draw } applyScale(); { if (visibleGlyphs < 0f || visibleGlyphs >= glyphCount) { // Text fully visible drawUnderline(batch, glyphLayout, dx, dy); drawLayout(batch, glyphLayout, dx, dy); } else { // Text partially visible int visible = (int)visibleGlyphs; GlyphRun run = glyphLayout.runs.first(); Array<Glyph> glyphs = run.glyphs; FloatArray xAdvances = run.xAdvances; Object[] oldGlyphs = glyphs.items; float[] oldXAdvances = xAdvances.items; int oldSize = glyphs.size; if (isRightToLeft()) { int invisible = oldSize - visible; for (int n = 0; n < invisible; n++) { dx += xAdvances.get(n); } setGlyphs(glyphs, Arrays.copyOfRange(oldGlyphs, invisible, oldSize)); xAdvances.items = Arrays.copyOfRange(oldXAdvances, invisible, xAdvances.size); } glyphs.size = visible; drawUnderline(batch, glyphLayout, dx, dy); drawLayout(batch, glyphLayout, dx, dy); if (isRightToLeft()) { setGlyphs(glyphs, oldGlyphs); xAdvances.items = oldXAdvances; } glyphs.size = oldSize; } } resetScale(); }
private void addGlyph (Glyph glyph, float x, float y, float width, float height) { float x2 = x + width; float y2 = y + height; final float u = glyph.u; final float u2 = glyph.u2; final float v = glyph.v; final float v2 = glyph.v2; final int page = glyph.page; if (glyphIndices != null) { glyphIndices[page].add(glyphCount++); } final float[] vertices = vertexData[page]; if (integer) { x = Math.round(x); y = Math.round(y); x2 = Math.round(x2); y2 = Math.round(y2); } int idx = this.idx[page]; this.idx[page] += 20; vertices[idx++] = x; vertices[idx++] = y; vertices[idx++] = color; vertices[idx++] = u; vertices[idx++] = v; vertices[idx++] = x; vertices[idx++] = y2; vertices[idx++] = color; vertices[idx++] = u; vertices[idx++] = v2; vertices[idx++] = x2; vertices[idx++] = y2; vertices[idx++] = color; vertices[idx++] = u2; vertices[idx++] = v2; vertices[idx++] = x2; vertices[idx++] = y; vertices[idx++] = color; vertices[idx++] = u2; vertices[idx] = v; }
/** Returns null if glyph was not found. If there is nothing to render, for example with various space characters, then bitmap * is null. */ public GlyphAndBitmap generateGlyphAndBitmap (int c, int size, boolean flip) { if (!bitmapped && !FreeType.setPixelSizes(face, 0, size)) throw new GdxRuntimeException("Couldn't set size for font"); SizeMetrics fontMetrics = face.getSize().getMetrics(); int baseline = FreeType.toInt(fontMetrics.getAscender()); // Check if character exists in this font. // 0 means 'undefined character code' if (FreeType.getCharIndex(face, c) == 0) { return null; } // Try to load character if (!FreeType.loadChar(face, c, FreeType.FT_LOAD_DEFAULT)) { throw new GdxRuntimeException("Unable to load character!"); } GlyphSlot slot = face.getGlyph(); // Try to render to bitmap Bitmap bitmap; if (bitmapped) { bitmap = slot.getBitmap(); } else if (!FreeType.renderGlyph(slot, FreeType.FT_RENDER_MODE_LIGHT)) { bitmap = null; } else { bitmap = slot.getBitmap(); } GlyphMetrics metrics = slot.getMetrics(); Glyph glyph = new Glyph(); if (bitmap != null) { glyph.width = bitmap.getWidth(); glyph.height = bitmap.getRows(); } else { glyph.width = 0; glyph.height = 0; } glyph.xoffset = slot.getBitmapLeft(); glyph.yoffset = flip ? -slot.getBitmapTop() + baseline : -(glyph.height - slot.getBitmapTop()) - baseline; glyph.xadvance = FreeType.toInt(metrics.getHoriAdvance()); glyph.srcX = 0; glyph.srcY = 0; glyph.id = c; GlyphAndBitmap result = new GlyphAndBitmap(); result.glyph = glyph; result.bitmap = bitmap; return result; }
@Override public void drawText(String string, float x, float y, Paint paint) { if (bitmap == null) { // log.debug("no bitmap set"); return; } // IosPaint p = (IosPaint) paint; Pixmap pixmap = bitmap.pixmap; TextureData td = font.getRegion().getTexture().getTextureData(); if (!td.isPrepared()) td.prepare(); Pixmap f = td.consumePixmap(); int adv = (int) x; Glyph last = null; int ch = (int) font.getCapHeight(); int h = (int) font.getLineHeight(); int yy = (int) (y - font.getLineHeight()); if (y < 0) y = 0; // pixmap.setColor(0xff0000ff); // int w = (int) font.getBounds(string).width; // pixmap.drawRectangle((int) x - 4, (int) y - 4, w + 8, h + 8); for (int i = 0; i < string.length(); i++) { char c = string.charAt(i); Glyph g = font.getData().getGlyph(c); if (g == null) g = font.getData().getGlyph(' '); if (i > 0) adv += last.getKerning(c); pixmap.drawPixmap(f, adv, //- g.xoffset, yy - (g.height + g.yoffset) - (h - ch), g.srcX, g.srcY, g.width, g.height); adv += g.width; last = g; } }