/*
 * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Panel;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;

/*
 * @test
 * @bug 4211728 4178140 8145542
 * @summary Justify several lines of text and verify that the lines are the same
   length and cursor positions are correct.
   Bug 4211728: TextLayout.draw() draws characters at wrong position.
   Bug 4178140: TextLayout does not justify.
 * @library /java/awt/regtesthelpers
 * @build PassFailJFrame
 * @run main/manual TestJustification
 */

public class TestJustification {
    private static final String INSTRUCTIONS = "Five lines of text should appear, all justified to the same width,\n" +
            "followed by a sixth line containing only roman characters and\n" +
            "no spaces which is not justified, and instead is centered.\n" +
            "Carets should appear between all characters.\n\n" +
            "PASS the test if this is true, else press FAIL.";

    public static void main(String[] args) throws Exception {

        PassFailJFrame.builder()
                      .title("Test Instructions")
                      .instructions(INSTRUCTIONS)
                      .rows((int) INSTRUCTIONS.lines().count() + 2)
                      .columns(35)
                      .testUI(TestJustification::createUI)
                      .build()
                      .awaitAndCheck();
    }

    private static Frame createUI() {
        Frame frame= new Frame("Test Text Justification");
        JustificationPanel panel = new JustificationPanel("Bitstream Cyberbit");
        frame.add(panel);
        frame.add("Center", panel);
        frame.setSize(500, 450);
        return frame;
    }

    static class JustificationPanel extends Panel {
        TextLayout[] layouts;
        String fontname;
        float height;
        float oldfsize;

        AttributedCharacterIterator lineText;
        TextLayout[] lines;
        int linecount;
        float oldwidth;

        JustificationPanel(String fontname) {
            this.fontname = fontname;
        }

        private static final String[] texts = {
                "This is an english Highlighting demo.", "Highlighting",
                "This is an arabic \u0627\u0628\u062a\u062c \u062e\u0644\u0627\u062e demo.", "arabic \u0627\u0628\u062a\u062c",
                "This is a hebrew \u05d0\u05d1\u05d2 \u05d3\u05d4\u05d5 demo.", "hebrew \u05d0\u05d1\u05d2",
                "This is a cjk \u4e00\u4e01\u4e02\uac00\uac01\uc4fa\uf900\uf901\uf902 demo.", "cjk",
                "NoSpaceCJK:\u4e00\u4e01\u4e02and\uac00\uac01\uc4faand\uf900\uf901\uf902", "No",
                "NoSpaceRoman", "Space"
        };

        public void paint(Graphics g) {
            Graphics2D g2d = (Graphics2D)g;

            Dimension d = getSize();
            Insets insets = getInsets();

            float w = d.width - insets.left - insets.right;
            float h = d.height - insets.top - insets.bottom;
            int fsize = (int)w/25;

            FontRenderContext frc = g2d.getFontRenderContext();

            if (layouts == null || fsize != oldfsize) {
                oldfsize = fsize;

                Font f0 = new Font(fontname, Font.PLAIN, fsize);
                Font f1 = new Font(fontname, Font.ITALIC, (int)(fsize * 1.5));

                if (layouts == null) {
                    layouts = new TextLayout[texts.length / 2];
                }

                height = 0;
                for (int i = 0; i < layouts.length; ++i) {
                    String text = texts[i*2];
                    String target = texts[i*2+1];

                    AttributedString astr = new AttributedString(text);
                    astr.addAttribute(TextAttribute.FONT, f0, 0, text.length());

                    int start = text.indexOf(target);
                    int limit = start + target.length();
                    astr.addAttribute(TextAttribute.FONT, f1, start, limit);

                    TextLayout layout = new TextLayout(astr.getIterator(), frc);

                    layout = layout.getJustifiedLayout(w - 20);

                    layouts[i] = layout;

                    height += layout.getAscent() + layout.getDescent() + layout.getLeading();
                }
            }

            g2d.setColor(Color.white);
            g2d.fill(new Rectangle.Float(insets.left, insets.top, w, h));

            float basey = 20;

            for (TextLayout layout : layouts) {
                float la = layout.getAscent();
                float ld = layout.getDescent();
                float ll = layout.getLeading();
                float lw = layout.getAdvance();
                float lh = la + ld + ll;
                float lx = (w - lw) / 2f;
                float ly = basey + layout.getAscent();

                g2d.setColor(Color.black);
                g2d.translate(insets.left + lx, insets.top + ly);

                Rectangle2D bounds = new Rectangle2D.Float(0, -la, lw, lh);
                g2d.draw(bounds);

                layout.draw(g2d, 0, 0);

                g2d.setColor(Color.red);
                for (int j = 0, e = layout.getCharacterCount(); j <= e; ++j) {
                    Shape[] carets = layout.getCaretShapes(j, bounds);
                    g2d.draw(carets[0]);
                }

                g2d.translate(-insets.left - lx, -insets.top - ly);
                basey += layout.getAscent() + layout.getDescent() + layout.getLeading();
            }

            // add LineBreakMeasurer-generated layouts

            if (lineText == null) {
                String text = "This is a long line of text that should be broken across multiple "
                              + "lines and then justified to fit the break width.  This test should pass if "
                              + "these lines are justified to the same width, and fail otherwise.  It should "
                              + "also format the hebrew (\u05d0\u05d1\u05d2 \u05d3\u05d4\u05d5) and arabic "
                              + "(\u0627\u0628\u062a\u062c \u062e\u0644\u0627\u062e) and CJK "
                              + "(\u4e00\u4e01\u4e02\uac00\uac01\uc4fa\u67b1\u67b2\u67b3\u67b4\u67b5\u67b6\u67b7"
                              + "\u67b8\u67b9) text correctly.";

                Float regular = 16.0F;
                Float big = 24.0F;
                AttributedString astr = new AttributedString(text);
                astr.addAttribute(TextAttribute.SIZE, regular, 0, text.length());
                astr.addAttribute(TextAttribute.FAMILY, fontname, 0, text.length());

                int ix = text.indexOf("broken");
                astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 6);
                ix = text.indexOf("hebrew");
                astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 6);
                ix = text.indexOf("arabic");
                astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 6);
                ix = text.indexOf("CJK");
                astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 3);

                lineText = astr.getIterator();
            }

            float width = w - 20;
            if (lines == null || width != oldwidth) {
                oldwidth = width;

                lines = new TextLayout[10];
                linecount = 0;

                LineBreakMeasurer measurer = new LineBreakMeasurer(lineText, frc);

                for (;;) {
                    TextLayout layout = measurer.nextLayout(width);
                    if (layout == null) {
                        break;
                    }

                    // justify all but last line
                    if (linecount > 0) {
                        lines[linecount - 1] = lines[linecount - 1].getJustifiedLayout(width);
                    }

                    if (linecount == lines.length) {
                        TextLayout[] nlines = new TextLayout[lines.length * 2];
                        System.arraycopy(lines, 0, nlines, 0, lines.length);
                        lines = nlines;
                    }

                    lines[linecount++] = layout;
                }
            }

            float basex = insets.left + 10;
            basey += 10;
            g2d.setColor(Color.black);

            for (int i = 0; i < linecount; ++i) {
                TextLayout layout = lines[i];

                basey += layout.getAscent();
                float adv = layout.getAdvance();
                float dx = layout.isLeftToRight() ? 0 : width - adv;

                layout.draw(g2d, basex + dx, basey);

                basey += layout.getDescent() + layout.getLeading();
            }
        }
    }
}
