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

playn.flash.FlashTextLayout Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2010 The PlayN Authors
 *
 * 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 playn.flash;

import java.util.ArrayList;
import java.util.List;

import playn.core.AbstractTextLayout;
import playn.core.Font;
import playn.core.TextFormat;
import pythagoras.f.Rectangle;
import pythagoras.f.IRectangle;

import static playn.core.PlayN.graphics;

class FlashTextLayout extends AbstractTextLayout {

  private FlashFontMetrics metrics;
  private List lines = new ArrayList();

  private static class Line {
    public final String text;
    public final float width;
    public Line(String text, float width) {
      this.text = text;
      this.width = width;
    }
  }

  FlashTextLayout(FlashCanvas.Context2d ctx, String text, TextFormat format) {
    super(text, format);
    Font font = getFont(format);
    this.metrics = ((FlashGraphics)graphics()).getFontMetrics(font);
    configContext(ctx);

    // normalize newlines in the text (Windows: CRLF -> LF, Mac OS pre-X: CR -> LF)
    text = text.replace("\r\n", "\n").replace('\r', '\n');

    if (format.shouldWrap() || text.indexOf('\n') != -1) {
      for (String line : text.split("\\n")) {
        String[] words = line.split("\\s"); // TODO: preserve intra-line whitespace
        for (int idx = 0; idx < words.length; ) {
          // note: measureLine has the side effect of adding the measured line to this.lines and
          // setting this.width to the maximum of the current width and the measured line width
          idx = measureLine(ctx, words, idx);
        }
      }
      height = metrics.height * lines.size();

    } else {
      width = ctx.measureText(text).getWidth();
      height = metrics.height;
      lines.add(new Line(text, width));
    }

    // Canvas.measureText does not account for the extra width consumed by italic characters, so we
    // fudge in a fraction of an em and hope the font isn't too slanted
    switch (font.style()) {
    case ITALIC:      width += metrics.emwidth/8; break;
    case BOLD_ITALIC: width += metrics.emwidth/6; break;
    default: break; // nada
    }
  }

  @Override
  public IRectangle bounds() {
    throw new UnsupportedOperationException();
  }

  @Override
  public int lineCount() {
    return lines.size();
  }

  @Override
  public Rectangle lineBounds(int line) {
    throw new UnsupportedOperationException("Line bounds not supported in Flash backend."); // TODO
  }

  @Override
  public float ascent() {
    throw new UnsupportedOperationException("Text ascent not supported in Flash backend."); // TODO
  }

  @Override
  public float descent() {
    throw new UnsupportedOperationException("Text descent not supported in Flash backend."); // TODO
  }

  @Override
  public float leading() {
    throw new UnsupportedOperationException("Text leading not supported in Flash backend."); // TODO
  }

  void stroke(FlashCanvas.Context2d ctx, float x, float y) {
    configContext(ctx);
    float ypos = 0;
    for (Line line : lines) {
      ctx.strokeText(line.text, x + format.align.getX(line.width, width), y + ypos);
      ypos += metrics.height;
    }
  }

  void fill(FlashCanvas.Context2d ctx, float x, float y) {
    configContext(ctx);
    float ypos = 0;
    for (Line line : lines) {
      ctx.fillText(line.text, x + format.align.getX(line.width, width), y + ypos);
      ypos += metrics.height;
    }
  }

  void configContext(FlashCanvas.Context2d ctx) {
    Font font = getFont(format);
    String italic = "normal";
    String bold = "normal";
    switch (font.style()) {
      case BOLD:        bold = "bold";   break;
      case ITALIC:      italic = "italic"; break;
      case BOLD_ITALIC: bold = "bold"; italic = "italic"; break;
      default: break; // nada
    }

    ctx.setFont(italic + " " + bold + " " + font.size() + " " + font.name());
    ctx.setTextBaseline(FlashCanvas.Context2d.TextBaseline.TOP.getValue());
  }

  Font getFont(TextFormat format) {
    return format.font == null ? FlashFont.DEFAULT : format.font;
  }

  int measureLine(FlashCanvas.Context2d ctx, String[] words, int idx) {
    // we always put at least one word on a line
    String line = words[idx++];
    int startIdx = idx;

    // build a rough estimate line based on character count and emwidth
    for (; idx < words.length; idx++) {
      String nline = line + " " + words[idx];
      if (nline.length() * metrics.emwidth > format.wrapWidth) break;
      line = nline;
    }

    // now, based on exact measurements, either add more words...
    double lineWidth = ctx.measureText(line).getWidth();
    if (lineWidth < format.wrapWidth) {
      for (; idx < words.length; idx++) {
        String nline = line + " " + words[idx];
        double nlineWidth = ctx.measureText(nline).getWidth();
        if (nlineWidth > format.wrapWidth) break;
        line = nline;
        lineWidth = nlineWidth;
      }
    }

    // or pop words off...
    while (lineWidth > format.wrapWidth && idx > (startIdx+1)) { // don't pop off the last word
      line = line.substring(0, line.length() - words[--idx].length() - 1);
      lineWidth = ctx.measureText(line).getWidth();
    }

    // finally, if we're still over the limit (we have a single looong word), hard break
    if (lineWidth > format.wrapWidth) {
      StringBuilder remainder = new StringBuilder();
      while (lineWidth > format.wrapWidth && line.length() > 1) {
        // this could be more efficient, but this edge case should be rare enough not to matter
        int lastIdx = line.length()-1;
        remainder.insert(0, line.charAt(lastIdx));
        line = line.substring(0, lastIdx);
        lineWidth = ctx.measureText(line).getWidth();
      }
      words[--idx] = remainder.toString();
    }

    lines.add(new Line(line, (float)lineWidth));
    width = (float)Math.max(width, lineWidth);
    return idx;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy