Stufenlos vergrösserbare Fonts in Java2D?

javabar

Mitglied
Hallo!

Ich arbeite an einer Klasse PrintPreview, welches die Vorschau für ein Pageable-Objekt am Screen anzeigen soll.

Der Zoom (scale) passt sich im Beispiel der Fenstergröße an.
Dabei fiel mir auf, dass sich hier die Schriften nicht (wie bei einem PDF) stufenlos vergrößern.
Wenn man das Fenster größer zieht, die Schriften vergrössern sich sprunghaft.
Besonders deutlich wird es, wenn man (im Beispiel) auf die Enden der Zeilen schaut: Zeile 3 ist mal kürzer, mal länger als Zeile 2.

Gibt es in Java2D keine richtige Unterstützung von skalierbaren Fonts, dass sich eine Druckvorschau auch wirklich so stufenlos vergrößert anzeigen lässt wie in einem PDF-Reader?

Mein Code dazu:

Java:
package printpreview;

import java.awt.*;
import java.awt.geom.*;
import java.awt.print.*;
import java.util.logging.*;
import javax.swing.*;

/**
 *
 * @author JavaBar
 */
public class Preview {

    // Beginn PrintPreview
    static class PrintPreview extends JComponent {

        private Pageable pageable;
        private int pageNo;

        public PrintPreview(Pageable pageable) {
            this.pageable = pageable;
            pageNo = 0;
        }

        public int getPageNo() {
            return pageNo;
        }

        public void setPageNo(int pageNo) {
            this.pageNo = pageNo;
            repaint();
        }

        @Override
        protected void paintComponent(Graphics graphics) {
            int w = getWidth();
            int h = getHeight();

            graphics.setColor(new Color(33, 33, 33));
            graphics.fillRect(0, 0, w, h);
            Printable p = pageable.getPrintable(pageNo);
            PageFormat format = pageable.getPageFormat(pageNo);
            double pw = format.getWidth();
            double ph = format.getHeight();

            Graphics2D g = (Graphics2D) graphics;
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            double scaleX = w / pw;
            double scaleY = h / ph;
            double sc = scaleX < scaleY ? scaleX : scaleY;
            g.scale(sc, sc);

            g.setColor(Color.WHITE);
            g.fill(new Rectangle2D.Double(0.0, 0.0, format.getWidth(), format.getHeight()));

            try {
                p.print(graphics, pageable.getPageFormat(pageNo), pageNo);
            } catch (PrinterException ex) {
                Logger.getLogger(PrintPreview.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    // Ende PrintPreview

    // Beginn Document
    static class Document implements Pageable, Printable {

        PageFormat pageFormat;
        static final float mm = 72f / 25.4f;

        public Document() {
            pageFormat = new PageFormat();
            Paper paper = new Paper();
            paper.setSize(120 * mm, 50 * mm);
            paper.setImageableArea(0.0, 0.0, paper.getWidth(), paper.getHeight());
            pageFormat.setPaper(paper);
        }

        @Override
        public int getNumberOfPages() {
            return 1;
        }

        @Override
        public PageFormat getPageFormat(int pageIndex) throws IndexOutOfBoundsException {
            return pageFormat;
        }

        @Override
        public Printable getPrintable(int pageIndex) throws IndexOutOfBoundsException {
            return this;
        }

        @Override
        public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) {
            Graphics2D g = (Graphics2D) graphics;
            Font f1 = new Font("Arial", Font.PLAIN, 1);
            g.setColor(Color.BLACK);
            g.setFont(f1.deriveFont(48f));
            g.drawString("Lorem ipsum", 10, 48);
            g.setFont(f1.deriveFont(24f));
            g.drawString("dolor sit amet", 20, 70);
            g.setFont(f1.deriveFont(13.9f));
            g.drawString("consectetur adipisici elit", 20, 82);
            g.setFont(f1.deriveFont(14.4f));
            g.drawString("consectetur adipisici elit", 20, 95);
            g.setFont(f1.deriveFont(6f));
            g.drawString("sed diam nonumy eirmod tempor invidunt ut labore et dolore magna", 20, 103);
            return PAGE_EXISTS;
        }
    }
    // Ende Document
    
    public static void main(String[] args) {
        JFrame f = new JFrame("Druckvorschau");
        f.setBounds(10, 10, 300, 200);
        f.setLayout(new BorderLayout());
        f.add(new PrintPreview(new Document()), BorderLayout.CENTER);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setVisible(true);
    }
}

(Fürs Beispiel verwende ich deshalb einen Code mit static-Klassen, weil man so mit nur einer .java-Datei auskommt).

Grüße

Egon Schmid
 

Marco13

Top Contributor
Soweit ich weiß geht das nicht so ohne weiteres. Text zu zeichnen ist eine Kunst, und wenn irgendwo ein Plxel zu viel oder zu werig lst, wirc der Text schneii unlesbar. Wenn man einen String hat wie
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
und der Text nun insgesamt (wegen einer Textgröße von 12.9 oder so) einen Pixel schmaler sein soll, kommt im schlimmsten Fall sowas raus wie
nnnnnnnnnnnnnnnnnrnnnnnnnnnnnnn

Der einzige Ansatz, der mir einfallen würde, wäre, den Text "sehr groß" einzustellen, und "per Hand" auf die richtige Größe runterzuskalieren - nur zu Verdeutlichung hingehackt (!!!)
Java:
        @Override
        public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) {
            Graphics2D g = (Graphics2D) graphics;
            Font f1 = new Font("Arial", Font.PLAIN, 100);
            g.setFont(f1);
            g.setColor(Color.BLACK);

            drawSizedString(g, "Test", 10,  5, 25.0f);
            drawSizedString(g, "Test", 10, 10, 25.2f);
            drawSizedString(g, "Test", 10, 15, 25.4f);
            drawSizedString(g, "Test", 10, 20, 25.6f);

/*
            g.setFont(f1.deriveFont(48f));
            g.drawString("Lorem ipsum", 10, 48);
            g.setFont(f1.deriveFont(24f));
            g.drawString("dolor sit amet", 20, 70);
            g.setFont(f1.deriveFont(13.9f));
            g.drawString("consectetur adipisici elit", 20, 82);
            g.setFont(f1.deriveFont(14.4f));
            g.drawString("consectetur adipisici elit", 20, 95);
            g.setFont(f1.deriveFont(6f));
            g.drawString("sed diam nonumy eirmod tempor invidunt ut labore et dolore magna", 20, 103);

*/
            return PAGE_EXISTS;
        }


        private void drawSizedString(Graphics graphics, String s, int x, int y, float size)
        {
            Graphics2D g = (Graphics2D) graphics;
            AffineTransform at = g.getTransform();
            g.scale(size/100.0f, size/100.0f);
            g.drawString(s, (int)(x*size), (int)(y*size));
            g.setTransform(at);
        }

Aber wie man schon sieht: Irgendwelche "intuitiven" Größenangaben sind damit schwierig, bzw. mit einigem an Rechnerei verbunden. Ich denke schon, dass man das ganze so deichseln kann, dass die "size" die dort übergeben wird GENAU den Effekt hat, den ein [c]setFont(f.deriveFont(size))[/c] hätte (und natürlich die Position (x,y) noch "stimmt" - im Moment ist die kompletter Murks :oops: ) aber dazu muss man wohl mehr als ein paar Minuten Zeit investieren... Wenn's nicht klappt, könnte ich nochmal schauen, aber ich weiß nicht, ob das für dich so überhaupt Sinn macht....
 

Melfis

Aktives Mitglied
Denke diese Sprünge haben was mit der AffTrans Matrix in dem FontRenderContext zutun,
damit solte das problem nicht mehr auftretten:

Code:
private void drawSizedString(Graphics graphics, String s, int x, int y, float size)
        {
        	Shape sh;
        	Font f = graphics.getFont().deriveFont(size);
        	
        	FontRenderContext frc=new FontRenderContext(new AffineTransform(), false, false);   	
        	GlyphVector v = f.createGlyphVector(frc, s);
                sh = new AffineTransform(1.0, 0.0f, 0.0f, 1.0, x,y).createTransformedShape(v.getOutline());
            
        	Graphics2D g = (Graphics2D) graphics;
            g.fill(sh);  
        }
 

javabar

Mitglied
Danke, Melfis, einen Text in Pfade umwandeln und dann skalieren und darstellen löst ein Problem, so funktioniert es mit der proportionalen Vergrößerung.
Ich dachte, vielleicht gibt es einen anderen Weg.

Eine Verbesserung zu Deinem Code. Bei kleinen Fonts stimmten die Textabstände nicht. So ist's besser:

Java:
private void drawSizedString(Graphics graphics, String s, int x, int y, float size) {
    Shape sh;
    Font f = graphics.getFont().deriveFont(1000f);

    FontRenderContext frc = new FontRenderContext(new AffineTransform(), false, false);
    GlyphVector v = f.createGlyphVector(frc, s);
    sh = new AffineTransform(size*.001f, 0.0f, 0.0f, size*.001f, x, y).createTransformedShape(v.getOutline());

    Graphics2D g = (Graphics2D) graphics;
    g.fill(sh);
}

Eigentlich sollte nichts an der Methode print() von Printable geändert werden müssen, es kann sich ja um ein beliebiges Printable-Objekt.
Die Klasse PrintPreview, die hier quasi als Bildschirmdrucker dient, diese muss sich drum kümmern, dass die Skalierungen beim Zeichnen der Texte stimmen.

Texte vor dem Drucken in Pfade umzuwandeln, hat auch einen grossen Nachteil: Wenn der Drucker ein PDF- oder PS-Drucker ist. Die Dateien werden deutlich größer, und die Texte können von Programmen nicht mehr gelesen werden.

Das Grundproblem bei der ganzen Sache liegt wohl daran, dass Java bei Font-Grössen und Zeichenbreiten mit Integer-Werten rechnet. Bei der Einführung von Java2D hätten Klassen für Fonts entwickelt werden sollen, bei denen mit float oder gar double gerechnet wird.

Grüße

Egon Schmid
 

Melfis

Aktives Mitglied
Eine Skalierung mit dem Faktor 1000 bringt keine Vorteile,
es kommt exakt das selbe raus, kann man gut Testen wenn man mit .draw statt .fill die beiden Möglichkeiten übereinander zeichnet.

Aber ja, optimal ist die Lösung nicht.

MFG Melfis
 

javabar

Mitglied
Das mit dem Skalieren macht sehr wohl einen Unterschied, vor allem bei kleinen Schriften machen sich Fehler bei Zeichenabständen bemerkbar (siehe Anhang).

Das liegt daran, weil die Methoden der Klasse FontMetrics (charWidth(), stringWidth()) int-Werte zurückliefern.
Einen grossen Font nehmen und den auf die gewünschte Größe skaliern minimiert die Rundungsungenauigkeiten.

Grüße

Egon
 

Ähnliche Java Themen

Neue Themen


Oben