Google Tag Manager

Showing posts with label Font. Show all posts
Showing posts with label Font. Show all posts

2024/10/31

Move and rotate strings to place them along the Shape curve

Code

public static Shape createTextOnPath(Shape shape, GlyphVector gv) {
  double[] points = new double[6];
  Point2D prevPt = new Point2D.Double();
  double nextAdvance = 0d;
  double next = 0d;
  Path2D result = new Path2D.Double();
  int length = gv.getNumGlyphs();
  int idx = 0;
  PathIterator pi = new FlatteningPathIterator(
      shape.getPathIterator(null), 1d);
  while (idx < length && !pi.isDone()) {
    switch (pi.currentSegment(points)) {
      case PathIterator.SEG_MOVETO:
        result.moveTo(points[0], points[1]);
        prevPt.setLocation(points[0], points[1]);
        nextAdvance = gv.getGlyphMetrics(idx).getAdvance() * .5;
        next = nextAdvance;
        break;

      case PathIterator.SEG_LINETO:
        double dx = points[0] - prevPt.getX();
        double dy = points[1] - prevPt.getY();
        double distance = Math.hypot(dx, dy);
        if (distance >= next) {
          double r = 1d / distance;
          double angle = Math.atan2(dy, dx);
          while (idx < length && distance >= next) {
            double x = prevPt.getX() + next * dx * r;
            double y = prevPt.getY() + next * dy * r;
            double advance = nextAdvance;
            nextAdvance = getNextAdvance(gv, idx, length);
            AffineTransform at = AffineTransform.getTranslateInstance(x, y);
            at.rotate(angle);
            Point2D pt = gv.getGlyphPosition(idx);
            at.translate(-pt.getX() - advance, -pt.getY());
            Shape s = gv.getGlyphOutline(idx);
            result.append(at.createTransformedShape(s), false);
            next += advance + nextAdvance;
            idx++;
          }
        }
        next -= distance;
        prevPt.setLocation(points[0], points[1]);
        break;

      default:
    }
    pi.next();
  }
  return result;
}

References

2022/08/31

Use AffineTransform to place Roman numerals on an analog clock face

Code

private final String[] arabicNumerals = {
  "12", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"
};
private final String[] romanNumerals = {
  "XII", "I", "II", "III", "IIII", "V", "VI", "VII", "VIII", "IX", "X", "XI"
};

private void paintClockNumbers(
    Graphics2D g2, double radius, double hourMarkerLen) {
  AffineTransform at = AffineTransform.getRotateInstance(0d);
  g2.setColor(Color.WHITE);
  Font font = g2.getFont();
  FontRenderContext frc = g2.getFontRenderContext();
  if (isRomanNumerals) {
    AffineTransform si = AffineTransform.getScaleInstance(1d, 2d);
    for (String txt : romanNumerals) {
      Shape s = getTextLayout(txt, font, frc).getOutline(si);
      Rectangle2D r = s.getBounds2D();
      double tx = r.getCenterX();
      double ty = radius - hourMarkerLen - r.getHeight() + r.getCenterY() * .5;
      Shape t = AffineTransform.getTranslateInstance(-tx, -ty)
                               .createTransformedShape(s);
      g2.fill(at.createTransformedShape(t));
      at.rotate(Math.PI / 6d);
    }
  } else {
    Point2D ptSrc = new Point2D.Double();
    for (String txt : arabicNumerals) {
      Shape s = getTextLayout(txt, font, frc).getOutline(null);
      Rectangle2D r = s.getBounds2D();
      double ty = radius - hourMarkerLen - r.getHeight() - r.getCenterY() * .5;
      ptSrc.setLocation(0d, -ty);
      Point2D pt = at.transform(ptSrc, null);
      double dx = pt.getX() - r.getCenterX();
      double dy = pt.getY() - r.getCenterY();
      // g2.drawString(txt, (float) dx, (float) dy);
      g2.fill(AffineTransform.getTranslateInstance(dx, dy)
        .createTransformedShape(s));
      at.rotate(Math.PI / 6d);
    }
  }
}

private static TextLayout getTextLayout(
    String txt, Font font, FontRenderContext frc) {
  return new TextLayout(txt, font, frc);
}

References

2020/07/31

Show a badge using JLayer for icon in a JLabel

Code

class BadgeLayerUI extends LayerUI {
  private static final int BADGE_SIZE = 17;
  private static final Point OFFSET = new Point(6, 2);
  private final Rectangle viewRect = new Rectangle();
  private final Rectangle iconRect = new Rectangle();
  private final Rectangle textRect = new Rectangle();

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    if (c instanceof JLayer) {
      Graphics2D g2 = (Graphics2D) g.create();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      iconRect.setBounds(0, 0, 0, 0);
      textRect.setBounds(0, 0, 0, 0);
      BadgeLabel label = (BadgeLabel) ((JLayer<?>) c).getView();
      SwingUtilities.calculateInnerArea(label, viewRect);
      SwingUtilities.layoutCompoundLabel(
          label,
          label.getFontMetrics(label.getFont()),
          label.getText(),
          label.getIcon(),
          label.getVerticalAlignment(),
          label.getHorizontalAlignment(),
          label.getVerticalTextPosition(),
          label.getHorizontalTextPosition(),
          viewRect,
          iconRect,
          textRect,
          label.getIconTextGap()
      );

      int x = iconRect.x + iconRect.width - BADGE_SIZE + OFFSET.x;
      int y = iconRect.y + iconRect.height - BADGE_SIZE + OFFSET.y;
      g2.translate(x, y);
      Icon badge = new BadgeIcon(label.getCounter(), Color.WHITE, new Color(0xAA_32_16_16, true));
      badge.paintIcon(label, g2, 0, 0);
      g2.dispose();
    }
  }
}

class BadgeIcon implements Icon {
  private final Color badgeBgc;
  private final Color badgeFgc;
  private final int value;

  protected BadgeIcon(int value, Color fgc, Color bgc) {
    this.value = value;
    this.badgeFgc = fgc;
    this.badgeBgc = bgc;
  }

  @Override public void paintIcon(Component c, Graphics g, int x, int y) {
    if (value <= 0) {
      return;
    }
    int w = getIconWidth();
    int h = getIconHeight();
    Graphics2D g2 = (Graphics2D) g.create();
    g2.translate(x, y);
    RoundRectangle2D badge = new RoundRectangle2D.Double(0, 0, w, h, 6, 6);
    g2.setPaint(badgeBgc);
    g2.fill(badge);
    g2.setPaint(badgeBgc.darker());
    g2.draw(badge);

    g2.setPaint(badgeFgc);
    FontRenderContext frc = g2.getFontRenderContext();
    // Java 12:
    // NumberFormat fmt = NumberFormat.getCompactNumberInstance(
    //     Locale.US, NumberFormat.Style.SHORT);
    // String txt = fmt.format(value);
    String txt = value > 999 ? "1K" : Objects.toString(value);
    AffineTransform at = txt.length() < 3 ? null : AffineTransform.getScaleInstance(.66, 1d);
    Shape shape = new TextLayout(txt, g2.getFont(), frc).getOutline(at);
    Rectangle2D b = shape.getBounds();
    Point2D p = new Point2D.Double(
        b.getX() + b.getWidth() / 2d, b.getY() + b.getHeight() / 2d);
    AffineTransform toCenterAT = AffineTransform.getTranslateInstance(
        w / 2d - p.getX(), h / 2d - p.getY());
    g2.fill(toCenterAT.createTransformedShape(shape));
    g2.dispose();
  }

  @Override public int getIconWidth() {
    return 17;
  }

  @Override public int getIconHeight() {
    return 17;
  }
}

Explanation

  • Set JLabel to JLayer to display Badge near the specified corner of the Icon area inside the JLabel body
    • Icon area inside JLabel can be retrieved with SwingUtilities.layoutCompoundLabel(...) method
    • Show 6px and Badge offset in x axis and 2px offset in y axis, so need to set more margin in JLabel
    • If you also want to display text in JLabel, the text may overlap with Badge if IconTextGap is not set considering the above offset
  • Icons for Badge created with a fixed size of 17x17 using RoundRectangle2D or Ellipse2D
    • Suppress Badge if value is 0
    • Set to 1K for all numbers with more than 4 digits
    • If the number to be displayed is 3 digits, apply a transformation of 66% to set the length.

References

2020/02/29

Display the Unicode code point of the character at the Caret position in the JTextArea

Code

String u1F60x = "😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏";
String u1F61x = "😐😑😒😓😔😕😖😗😘😙😚😛😜😝😞😟";
String u1F62x = "😠😡😢😣😤😥😦😧😨😩😪😫😬😭😮😯";
String u1F63x = "😰😱😲😳😴😵😶😷😸😹😺😻😼😽😾😿";
String u1F64x = "🙀🙁🙂  🙅🙆🙇🙈🙉🙊🙋🙌🙍🙎🙏";

JTextField label = new JTextField();
label.setEditable(false);
label.setFont(label.getFont().deriveFont(32f));

List<String> l = Arrays.asList(u1F60x, u1F61x, u1F62x, u1F63x, u1F64x);
JTextArea textArea = new JTextArea(String.join("\n", l));
textArea.addCaretListener(e -> {
  try {
    int dot = e.getDot();
    int mark = e.getMark();
    if (dot - mark == 0) {
      Document doc = textArea.getDocument();
      String txt = doc.getText(dot, 1);
      int code = txt.codePointAt(0);
      if (Character.isHighSurrogate((char) code)) {
        txt = doc.getText(dot, 2);
        code = txt.codePointAt(0);
      }
      label.setText(String.format("%s: U+%04X", txt, code));
    } else {
      label.setText("");
    }
  } catch (BadLocationException ex) {
    ex.printStackTrace();
  }
});

References

2017/03/30

Make a condensed Font and use it with JTextArea

Code

Font font = new Font(Font.MONOSPACED, Font.PLAIN, 18).deriveFont(
   AffineTransform.getScaleInstance(.9, 1d));
textArea.setFont(font);

References

2013/11/11

Using drop caps on a JLabel

Code

@Override protected void paintComponent(Graphics g) {
  Graphics2D g2 = (Graphics2D) g.create();
  g2.setPaint(getBackground());
  g2.fillRect(0, 0, getWidth(), getHeight());

  Insets i = getInsets();
  float x0 = i.left;
  float y0 = i.top;

  Font font = getFont();
  String txt = getText();

  AttributedString as = new AttributedString(txt.substring(1));
  as.addAttribute(TextAttribute.FONT, font);
  AttributedCharacterIterator aci = as.getIterator();
  FontRenderContext frc = g2.getFontRenderContext();

  Shape shape = new TextLayout(txt.substring(0, 1), font, frc).getOutline(null);

  AffineTransform at1 = AffineTransform.getScaleInstance(5d, 5d);
  Shape s1 = at1.createTransformedShape(shape);
  Rectangle r = s1.getBounds();
  r.grow(6, 2);
  int rw = r.width;
  int rh = r.height;

  AffineTransform at2 = AffineTransform.getTranslateInstance(x0, y0 + rh);
  Shape s2 = at2.createTransformedShape(s1);
  g2.setPaint(getForeground());
  g2.fill(s2);

  float x = x0 + rw;
  float y = y0;
  int w0 = getWidth() - i.left - i.right;
  int w = w0 - rw;
  LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);
  while (lbm.getPosition() < aci.getEndIndex()) {
    TextLayout tl = lbm.nextLayout(w);
    tl.draw(g2, x, y + tl.getAscent());
    y += tl.getDescent() + tl.getLeading() + tl.getAscent();
    if (y0 + rh < y) {
      x = x0;
      w = w0;
    }
  }
  g2.dispose();
}

References