# Funktion gesucht: Text vektorisieren



## malufi89 (19. Okt 2011)

Hallo ihr!

Ich suche eine Funktion in Java, wie ich mir Vektoren eines Textes bzw von Buchstaben anzeigen lassen kann.
Die idee die dahintersteht ist eigentlich, dass ich eine eps-Datei mit Vektorgrafik und Schrift aus einem Programm erstellen möchte, was für diese Grafiken den input entgegennimmt.
Die Formen in der Vektorgrafik machen mir keine Probleme - wie ich die berechne habe ich geschnallt, genauso die Pfeile die nötig sein werden, aber kann ich mir durch Java auch die Vektoren der Buchstaben ausgeben lassen? Bisher habe ich die passende Funktion nicht gefunden.

Liebe grüße
M.


----------



## AlexSpritze (19. Okt 2011)

Was sind für dich Vektoren von Buchstaben? Einfach eine Angabe von mehreren (x,y) Paaren, die den Verlauf eines Pfades angeben, der dann einen Buchstaben darstellen soll?


----------



## malufi89 (19. Okt 2011)

ja genau das meine ich.


----------



## delphiking1980 (19. Okt 2011)

dazu benötigt man doch nur 2 For Schleifen die eine für die Zeilen die andere für die Spalten.


----------



## SlaterB (19. Okt 2011)

kann ein Buchstabe nicht auf zig verschiedene Weisen dargestellt werden,
je nach bekannter Schriftart oder eigener Neuerfindung?
suchst du eine bestimmte Darstellung in was für einem Format, einfach nur Zahlen hintereinanderweg oder gleich ein bestimmtes Framework?

all das klingt nicht gerade nach Aufgabe für Java an sich, da musst du doch entweder hoffen irgendwo was fertiges zu finden
(vielleicht gerade das deine Frage) oder eben selber machen, ein v ist (0,5, 2,0, 4,5)..

> kann ich mir durch Java auch die Vektoren der Buchstaben ausgeben lassen?
klingt sehr unspezifisch


----------



## Landei (19. Okt 2011)

Wenn du eine Schriftart hast, kannst du [c]Font.getGlyphVector(new FontRenderContext(), "Hallo Welt")[/c] aufrufen. Letzterer kann dir wiederum über [c]getOutLine()[/c] eine Shape liefern. Damit hast du prinzipiell erst einmal die Möglichkeit, den Umriss als Bezier-Kurven oder Linienstücke mit der gewünschten Präzision liefern zu lassen. Musst du noch wissen, was "innen" und "außen" ist, darfst du dich mit Windungsregeln und so herumschlagen. 

Ich hatte mal den ersten Entwurf einer Font3D-Klasse für JME geschrieben, aber die Klasse scheint es in der aktuellen Version nicht mehr zu geben. Schade, da wäre alles drin gewesen...

[Edit]

Hab noch eine Version gefunden, da kannst du das ganze Grauen mit dem Pathiterator und so bewundern:


```
package com.jmex.font3d;

import java.awt.Font;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.PathIterator;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.state.BlendState;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jmex.font3d.math.ClosedPolygon;

/**
 * This class represents a font ready to be used for 3D.
 *
 * Known bugs:
 * 
 * - When glyphs are constructed from other glyphs, the shape returned by
 *   gv.getGlyphOutline(0); has them all cluddered up. This might be a bug in the
 *   VM, and I have no time to fix it, that is why the loading of each glyph has a
 *   try-catch-all statement around it.
 * 
 * @author emanuel
 */
public class Font3D implements TextFactory {
    private static final Logger logger = Logger.getLogger(Font3D.class
            .getName());
    
    private static Hashtable<String, Font3D> loadedFonts = new Hashtable<String, Font3D>();

    // This Node is only used for rendering
    Node renderNode = new Node();

    // The glyphs created from the font.
    Glyph3D glyph3Ds[] = new Glyph3D[256];

    // Settings
    Font font;
    private double flatness;
    private boolean drawSides;
    private boolean drawFront;
    private boolean drawBack;

	private static BlendState general_alphastate = null;
	private static MaterialState general_diffuse_material = null;
	boolean has_alpha_blending = false;
	boolean has_diffuse_material = false;


    // Create the
    public Font3D(Font font, double flatness, boolean drawSides,
            boolean drawFront, boolean drawBack) {
        if (font.getSize() != 1) {
            font = font.deriveFont(1.0f);
        }
        // Save for later
        this.font = font;
        this.flatness = flatness;
        this.drawSides = drawSides;
        this.drawFront = drawFront;
        this.drawBack = drawBack;

        // Clear our "parent node"
        renderNode.detachAllChildren();

        // Generate the glyphs
        for (int g = 0; g < 256; g++) {
            try {
                // if(g != 'H') // TEST
                // continue;
                // logger.info("Glyph: "+g+":"+(char)g);

                // GlyphVector gv = font.createGlyphVector(new
                // FontRenderContext(null, true, true), new char[] { (char)g });
                GlyphVector gv = font.layoutGlyphVector(new FontRenderContext(
                        null, true, true), new char[] { (char) g }, 0, 1, 0);
                gv.performDefaultLayout();
                ClosedPolygon closedPolygon = null;
                Glyph3D fontGlyph = new Glyph3D((char)g);

                // Get the shape
                Shape s = gv.getGlyphOutline(0);
                // GlyphMetrics metrics = gv.getGlyphMetrics(0);
                PathIterator pi = new FlatteningPathIterator(s
                        .getPathIterator(new AffineTransform()), flatness);
                // logger.info("\n\n\n\nWIND IS BLOWING:
                // "+(pi.getWindingRule() == PathIterator.WIND_EVEN_ODD ?
                // "WIND_EVEN_ODD" : "WIND_NON_ZERO"));
                float[] coords = new float[6];
                while (!pi.isDone()) {
                    int seg = pi.currentSegment(coords);
                    switch (seg) {
                        case PathIterator.SEG_MOVETO:
                            closedPolygon = new ClosedPolygon();
                            closedPolygon.addPoint(new Vector3f(coords[0],
                                    -coords[1], 0));
                            break;
                        case PathIterator.SEG_LINETO:
                            closedPolygon.addPoint(new Vector3f(coords[0],
                                    -coords[1], 0));
                            break;
                        case PathIterator.SEG_CLOSE:
                            closedPolygon.close();
                            fontGlyph.addPolygon(closedPolygon);
                            closedPolygon = null;
                            break;
                        default:
                            throw new IllegalArgumentException(
                                    "unknown segment type " + seg);
                    }
                    pi.next();
                }

                // If we added something then we have a valid glyph !
                fontGlyph.setBounds(gv.getGlyphLogicalBounds(0).getBounds2D());
                if (!fontGlyph.isEmpty()) {
                    // Time to triangulate the surface of the glyph
                    fontGlyph.triangulate();

                    // And create the actual geometry.
                    fontGlyph.generateMesh(drawSides, drawFront, drawBack);
                    if(fontGlyph.getMesh() != null)
                    {
                    	fontGlyph.setChildIndex(renderNode.getQuantity());
                    	renderNode.attachChild(fontGlyph.getMesh());
                    }
                }
                glyph3Ds[g] = fontGlyph;
            } catch (Exception e) {
                logger.log(Level.WARNING, "Error in char: ("
                                + g
                                + ":"
                                + (char) g
                                + "), the following is most likely due to glyphs constructed " +
                                        "from other glyphs.... that does not work.", e);
            }
        }
        
        // Apply a Z-state
        ZBufferState zstate = DisplaySystem.getDisplaySystem().getRenderer().createZBufferState();
		zstate.setFunction(ZBufferState.TestFunction.LessThan);
		zstate.setWritable(true);
		zstate.setEnabled(true);
		renderNode.setRenderState(zstate);
		
        // Finally create display-lists for each mesh
        renderNode.lockMeshes();
    }

    /**
     * This method is used when text wants to render, much like the shared
     * 
     * @return
     */
    public Node getRenderNode() {
        return renderNode;
    }

    /**
     * Method for creating the text from the font. TODO: react on the flags
     * parameter.
     * 
     * @param text
     * @param size
     * @param flags
     * @return
     */
    public Text3D createText(String text, float size, int flags) {

        Text3D text_obj = new Text3D(this, text, size);

        return text_obj;
    }

    /**
     * This method loads and caches a font, call this before calls to
     * {@link #createText(String, int)}.
     * 
     * @param fontname
     * @param font
     */
    public static void loadFont3D(String fontname, Font font, double flatness,
            boolean drawSides, boolean drawFront, boolean drawBack) {
        logger.info("FontSize:  " + font.getSize());
        logger.info("FontSize2D:" + font.getSize2D());
        Font3D f = new Font3D(font, flatness, drawSides, drawFront, drawBack);
        loadedFonts.put(fontname, f);
    }

    /**
     * Removes a cached Font3D.
     * 
     * @param fontname
     */
    public static void unloadFont(String fontname) {
        loadedFonts.remove(fontname);
    }

    /**
     * This method will create a peace of 3d text from this font.
     * 
     * @param fontname
     * @param text
     * @param size
     * @return
     */
    public static Text3D createText(String fontname, String text, float size,
            int flags) {
        // Find the cached font and create a text instance.
        Font3D cachedf = loadedFonts.get(fontname);

        return cachedf.createText(text, size, flags);
    }

    public Glyph3D getGlyph(char c) {
        return glyph3Ds[c];
    }

    public Font getFont() {
        return font;
    }
    
    public double getFlatness()
    {
    	return flatness;
    }
    
    public boolean drawSides()
    {
    	return drawSides;
    }
    
    public boolean drawFront()
    {
    	return drawFront;
    }
    
    public boolean drawBack()
    {
    	return drawBack;
    }

	public Glyph3D[] getGlyphs()
	{
		return glyph3Ds;
	}

	public boolean isMeshLocked()
	{
		return (renderNode.getLocks() & Spatial.LOCKED_MESH_DATA) != 0;
	}

	public void unlockMesh()
	{
		renderNode.unlockMeshes();		
	}

	public void lockMesh()
	{
		renderNode.lockMeshes();
	}
	
	public void enableBlendState()
	{
		if(has_alpha_blending)
			return;
		
		if(general_alphastate == null)
		{
	        general_alphastate = DisplaySystem.getDisplaySystem().getRenderer().createBlendState();
	        general_alphastate.setBlendEnabled(true);
	        general_alphastate.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
	        general_alphastate.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
	        general_alphastate.setTestEnabled(true);
	        general_alphastate.setTestFunction(BlendState.TestFunction.Always);
	        general_alphastate.setEnabled(true);
		}
		renderNode.setRenderState(general_alphastate);
		renderNode.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
		has_alpha_blending = true;
		renderNode.updateRenderState();
	}
	
	public void enableDiffuseMaterial()
	{
		if(has_diffuse_material)
			return;
		
		if(general_diffuse_material == null)
		{
			general_diffuse_material = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
			general_diffuse_material.setEnabled(true);
			general_diffuse_material.setColorMaterial(MaterialState.ColorMaterial.Diffuse);
		}
		renderNode.setRenderState(general_diffuse_material);
		renderNode.updateRenderState();
	}
	
}
```


----------



## malufi89 (19. Okt 2011)

Hallo ihr

erstmal danke für die Antworten. Es tut mir leid dass ich ganz offensichtlich so unzureichende Angaben gemacht habe, das möchte ich nachholen.

im Grunde geht es darum, dass ich einfache geometrische Formen als Vektoren erfassen möchte und als EPS abspeichern möchte (am Ende soll dabei sowas wie ein Baum rauskommen, nur dass die Knoten unterschiedlich aussehen und die Linien Pfeile sein werden).
Da alle Knoten die gleiche Größe haben sollen würde ich diese, wenn ich einmal die Form habe und das ist nicht das Problem, quasi nur verschieben und das kann man ja gut berechnen.

Anfangs war mein Plan das Gleiche mit den Buchstaben zu machen. (In den Knoten und an den Pfeilen wird es schrift geben) Also quasi die "Blanko Vektoren" für alle Buchstaben zu hinterlegen und dann nur die Verschiebungen anzugeben (ich weiß, zeugt nicht von Können...). Das wäre auch kein Problem, wenn da nicht die Pfeile wären, an denen ich natürlich auch gern geneigte Schrift hätte. Im Grunde ist das zwar auch nur eine Verschiebung, aber da die Pfeile nicht immer im gleichen Winkel geneigt sind übersteigt diese Berechnung tatsächlich mein mathematisches Verständnis. Das Problem der Neigung hätte ich aber auch noch nicht gelöst, wenn ich hier eine Antwort auf meine Frage gefunden hätte, wie ich einfach die Vektoren der Buchstaben ausgeben kann.

Schade... vielleicht ist das in Java einfach super umständlich und ich sollte es in einer anderen (mit noch unbekannten... Vorschläge??) Programmiersprache versuchen... eine GUI brauche ich aber schon...

BTW: Wenn sich jemand fragt warum ichs nicht in Gaphics2D mache: Ich möchte es eben in EPS und ich glaube wenn ich in Graphics2D zeichne und das speichere, dann erhalte ich keine Vektorgrafik, kann also nicht Verlustfrei speichern oder unterliege ich einem Irrtum?

Liebe Grüße
M.


----------



## Spacerat (19. Okt 2011)

@Landei: Sorry, dein Code ist mir zu lang. Meiner ist zwar nicht viel kürzer, jedoch ohne diese hier eher verwirrenden 3D-Zeilen. Wenn dir also etwas bekannt vorkommt... ignorier es. 
Was bitte ist EPS? Ist mir nicht geläufig, aber...
Also das vektorisieren von Grafiken im allgemeinen funktioniert in Java mit [c]java.awt.geom.GeneralPath[/c]. Diese Klasse wird auch standardmässig von der von Landei erwähnten [c]getOutline()[/c]-Methode zurückgegeben (implementiert über X-Kanten die Klasse Shape, sauberer ist's aber, wenn man per [c]new GeneralPath(myGlyphVector.getOutline())[/c] selbst instanziert). Von diesem GP erhält man einen [c]PathIterator[/c] über welchen man (wie der Name schon sagt) iterieren kann. Die Daten dieser Iterationen landen in einem vorher definiertem double-Array, welches je nach Elementtyp unterschiedlich (maximal 6 Werte) befüllt wird. Dieses kann man anschliessend speichern. Im folgendem Code verwirren eigentlich nur die [c]draw[/c]-Methoden. Das wesentliche findet sich in der [c]do-while[/c]-Schleife ab Zeile 88.
	
	
	
	





```
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Panel;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

import static java.lang.Math.*;

public class FontOutlines
extends Panel
{
	private static final long serialVersionUID = 3084369841425890033L;
	static final int DM = 8;
	final char[] text;
	final float prec;
	Font bigFont = GraphicsEnvironment.getLocalGraphicsEnvironment().createGraphics(new BufferedImage(1,1,2)).getFont().deriveFont(600.f);

	private FontOutlines(String text, float prec)
	{
		this.text = text.toCharArray();
		this.prec = (prec % 1);
		setFont(bigFont);
		AffineTransform at = bigFont.getTransform();
		FontRenderContext frc = new FontRenderContext(at, true, false);
		Rectangle2D b = bigFont.getStringBounds(text, frc);
		Dimension d = new Dimension((int) b.getWidth(), (int) b.getHeight());
		setBounds((int) b.getX(), (int) b.getY(), d.width, d.height);
		setPreferredSize(d);
		setMaximumSize(d);
		setMinimumSize(d);
	}

	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		if(args == null || args.length == 0 || args[0].equals("")) {
			args = new String[] {String.valueOf('@')};
		}
		final Frame f = new Frame("FontOutlines");
		FontOutlines fo = new FontOutlines(args[0], .1f);
		f.addWindowListener(new WindowAdapter()
		{
			@Override
			public void windowClosing(WindowEvent e)
			{
				f.dispose();
				System.exit(0);
			}
		});
		f.setBackground(Color.GRAY);
		f.add(fo);
		f.pack();
		f.setVisible(true);
	}

	@Override
	public void paint(Graphics g)
	{
		AffineTransform at = bigFont.getTransform();
		FontMetrics fm = g.getFontMetrics(bigFont);
		g.drawChars(text, 0, text.length, 0, fm.getAscent());
		FontRenderContext frc = new FontRenderContext(at, true, false);
		float h = fm.getAscent();
		float w = 0;
		float[] l = new float[2], m = new float[2];
		for(int n = 0; n < text.length; n++) {
			GlyphVector gv = bigFont.createGlyphVector(frc, String.valueOf(text[n]));
			GeneralPath gp = new GeneralPath(gv.getOutline());
			PathIterator pi = gp.getPathIterator(at);
			float cp[] = new float[2];
			float dp[] = new float[6];
			int seg;
			float p;
			// DAS WESENTLICHE
			do {
				seg = pi.currentSegment(dp);
				switch(seg) {
				case PathIterator.SEG_MOVETO:
					cp[0] = dp[0];
					cp[1] = dp[1];
					drawPoint((int) (cp[0] + w), (int) (cp[1] + h), Color.BLUE, g);
					break;
				case PathIterator.SEG_LINETO:
					drawLine((int) (cp[0] + w), (int) (cp[1] + h), (int) (dp[0] + w), (int) (dp[1] + h), Color.YELLOW, g); 
					cp[0] = dp[0];
					cp[1] = dp[1];
					drawPoint((int) (cp[0] + w), (int) (cp[1] + h), Color.BLUE, g);
					break;
				case PathIterator.SEG_CUBICTO:
					g.setColor(Color.YELLOW);
					drawLine((int) (cp[0] + w), (int) (cp[1] + h), (int) (dp[0] + w), (int) (dp[1] + h), Color.YELLOW, g); 
					drawPoint((int) (dp[0] + w), (int) (dp[1] + h), Color.RED, g);
					drawLine((int) (dp[0] + w), (int) (dp[1] + h), (int) (dp[2] + w), (int) (dp[3] + h), Color.YELLOW, g); 
					drawPoint((int) (dp[2] + w), (int) (dp[3] + h), Color.RED, g);
					drawLine((int) (dp[2] + w), (int) (dp[3] + h), (int) (dp[4] + w), (int) (dp[5] + h), Color.YELLOW, g); 
					for(p = prec; prec < 1.0f; p += prec) {
						l = cubicTo(cp, dp, w, h, pi.getWindingRule(), 1 - p);
						if((int) l[0] != (int) m[0] || (int) l[1] != (int) m[1]) {
							drawLine((int) m[0], (int) m[1], (int) l[0], (int) l[1], Color.GREEN, g); 
						}
					}
					cp[0] = dp[4];
					cp[1] = dp[5];
					drawPoint((int) (cp[0] + w), (int) (cp[1] + h), Color.BLUE, g);
					break;
				case PathIterator.SEG_QUADTO:
					drawLine((int) (cp[0] + w), (int) (cp[1] + h), (int) (dp[0] + w), (int) (dp[1] + h), Color.YELLOW, g); 
					drawPoint((int) (dp[0] + w), (int) (dp[1] + h), Color.RED, g);
					drawLine((int) (dp[0] + w), (int) (dp[1] + h), (int) (dp[2] + w), (int) (dp[3] + h), Color.YELLOW, g); 
					m[0] = cp[0] + w;
					m[1] = cp[1] + h;
					for(p = prec; p <= 1.0f; p += prec) {
						l = quadTo(cp, dp, w, h, pi.getWindingRule(), 1 - p);
						if((int) l[0] != (int) m[0] || (int) l[1] != (int) m[1]) {
							drawLine((int) m[0], (int) m[1], (int) l[0], (int) l[1], Color.GREEN, g); 
						}
						m = l;
					}
					cp[0] = dp[2];
					cp[1] = dp[3];
					drawPoint((int) (cp[0] + w), (int) (cp[1] + h), Color.BLUE, g);
					break;
				case PathIterator.SEG_CLOSE:
					break;
				}
				pi.next();
			} while(!pi.isDone());
			// ENDE VON DAS WESENTLICHE
			w +=  fm.getStringBounds(String.valueOf(text[n]), g).getWidth();
		}
	}

	private float[] quadTo(float[] st, float[] pts, float w, float h, int wr, float pr)
	{
		float[] a = new float[] {st[0] + w, st[1] + h};
		float[] b = new float[] {pts[0] + w, pts[1] + h};
		float[] c = new float[] {pts[2] + w, pts[3] + h};
		pointTimes((float) pow(pr,2), a);
		pointTimes((float) (2*pr - 2*pow(pr,2)), b);
		pointTimes((float) (1 - 2*pr + pow(pr,2)), c);
		return pointAdd(pointAdd(a,b),c);
	}

	private float[] cubicTo(float[] st, float[] pts, float w, float h, int wr, float pr)
	{
		float[] a = new float[] {st[0] + w, st[1] + h};
		float[] b = new float[] {pts[0] + w, pts[1] + h};
		float[] c = new float[] {pts[2] + w, pts[3] + h};
		float[] d = new float[] {pts[4] + w, pts[5] + h};
		pointTimes((float) pow(pr, 3), a);
		pointTimes((float) (3*pow(pr, 2) * (1 - pr)), b);
		pointTimes((float) (3 * pr * pow((1 - pr),2)), c);
		pointTimes((float) (pow((1 - pr), 3)), d);
		return pointAdd(pointAdd(a, b), pointAdd(c, d));
	}

	private void pointTimes(float pr, float[] p)
	{
		p[0] *= pr;
		p[1] *= pr;
	}

	private float[] pointAdd(float[] p1, float[] p2)
	{
		p1[0] += p2[0];
		p1[1] += p2[1];
		return p1;
	}

	private void drawPoint(int x, int y, Color col, Graphics g)
	{
//		Color tmp = g.getColor();
		g.setColor(col);
		g.fillOval(x - (DM / 2), y - (DM / 2), DM, DM);
//		g.setColor(tmp);
	}

	private void drawLine(int x, int y, int width, int height, Color col, Graphics g)
	{
//		Color tmp = g.getColor();
		g.setColor(col);
		g.drawLine(x, y, width, height); 
//		g.setColor(tmp);
	}
}
```


----------



## malufi89 (19. Okt 2011)

Danke für die Antwort! 
Um die Vektoren da ausgegeben zu bekommen muss ich aber irgendwo den String platzieren und tatsächlich "zeichnen", wenn ich das richtig verstehe, oder? 
Das war im Grunde nicht der Plan, man könnte das aber bestimmt im Verborgenen ablaufen lassen...

Im grunde ist dadurch die neigungsproblematik nicht gelöst, leider leider... da hätte das buchstaben-rumgeschiebe, worüber ich zuerst nachgedacht hatte natürlich schonwieder seine Vorteile gegenüber der von dir vorgeschlagenen methode (nämlich dass ich nirgendwo zeichnen muss).

EPS sind übrigens Post-Script Dateien die oftmals beim Druck verwendet werden. Eps Files unterstützen eben Vektorgrafiken und können von den verschiedensten Programmen interpretiert werden. Außerdem kannst du die Auflösung, in der du die Dinger öffnest, selbst festlegen, was in der Druckerei große Vorteile haben kann.
EPS Dateien kann man bspw mit Adobe PS, oder auch open office draw erstellen.


----------



## Landei (19. Okt 2011)

Warum nicht einfach PDF erzeugen (iText, JasperReports...) und dann zu EPS konvertieren?


----------



## Spacerat (19. Okt 2011)

Nein, du musst nicht Zeichnen. Diese [c]draw[/c]-Methoden kannst du ohne weiteres weglassen. Wichtig ist das [c]double[/c]-Array, die verschiedenen Elementtypen, die WindingRule sowie Schriftart und -grösse.


----------



## malufi89 (20. Okt 2011)

> Warum nicht einfach PDF erzeugen (iText, JasperReports...) und dann zu EPS konvertieren?


Das ist doch ein zwischenschritt den man manuell ausführen müsste, mit der Convertierung?
Und meintest du jetzt dass ich die PDF mit Inhalten die ich in Graphics2D zeichne befüllen soll? Dann würde die Convertierung zwar generell funktionieren, aber die Vektorstruktur bliebe nicht erhalten und gerade das ist ja, was ich möchte.

Außerdem soll es ja auch andersrum funktionieren (was dann aber unproblematischer ist, hoffe ich), nämlich von EPS zu einer Grafik. Dabei fällt mir aber gerade ein, dass es wirklich nur die Grafik geben wird und nicht den Aufbau des Graphen erfassen wird, der dabei herauskommt, denn dann müsste ich ja die Schrift irgendwie nicht als Grafik sondern eben als Schrift erkennen... puh...  nochn paar sone dinger und ich überlegs mir anders...

EDIT: Wobei das sogar vielleicht ginge, wenn ich mich für die Kopieren-Variante entscheide: Ich hinterlege ja quasi die Punkte der einzelnen Buchstaben im Verhältnis zueinander. 

@ spacerat:
hilf mir auf die Sprünge: Die funktion kann also die Vektoren von Schrift berechnen, ohne dass sie irgendwo auf nem JPanel oder sonst wo auftauchen muss? Also rein theoretisch? 
Um das ganz zu verstehen was du da machst muss ich mich leider noch etwas belesen, denn ich kenne leider den Path Iterator noch nicht...


----------



## Spacerat (20. Okt 2011)

Jep  Vllt. hättest du ja lieber 'ne Stringausgabe in der Console?
	
	
	
	





```
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.image.BufferedImage;
import java.util.List;

import datatypes.collections.ArrayList;

public class FontOutlines2Text
{
	private static final long serialVersionUID = 3084369841425890033L;
	static final int DM = 8;
	static final String LINE_SEPARATOR = System.getProperty("line.separator");

	final List<SegmentType> segments = new ArrayList<SegmentType>();
	final char[] text;
	final float prec;
	final Font bigFont;

	private FontOutlines2Text(String text, float prec)
	{
		Graphics g = GraphicsEnvironment.getLocalGraphicsEnvironment().createGraphics(new BufferedImage(1,1,2));
		bigFont = g.getFont().deriveFont(600.f);
		this.text = text.toCharArray();
		this.prec = (prec % 1);
		AffineTransform at = bigFont.getTransform();
		FontRenderContext frc = new FontRenderContext(at, true, false);
		for(int n = 0; n < this.text.length; n++) {
			System.out.println(this.text[n]);
			GlyphVector gv = bigFont.createGlyphVector(frc, String.valueOf(this.text[n]));
			GeneralPath gp = new GeneralPath(gv.getOutline());
			PathIterator pi = gp.getPathIterator(at);
			double dp[] = new double[6];
			int seg;
			// DAS WESENTLICHE
			do {
				seg = pi.currentSegment(dp);
				switch(seg) {
				case PathIterator.SEG_MOVETO:
					segments.add(new Move(dp));
					break;
				case PathIterator.SEG_LINETO:
					segments.add(new Line(dp));
					break;
				case PathIterator.SEG_CUBICTO:
					segments.add(new Cubic(dp));
					break;
				case PathIterator.SEG_QUADTO:
					segments.add(new Quad(dp));
					break;
				case PathIterator.SEG_CLOSE:
					segments.add(new Close());
					break;
				}
				pi.next();
			} while(!pi.isDone());
			// ENDE VON DAS WESENTLICHE
		}
	}

	@Override
	public String toString()
	{
		StringBuilder sb = new StringBuilder();
		for(SegmentType st : segments) {
			sb.append(st);
			sb.append(LINE_SEPARATOR);
		}
		return sb.toString();
	}

	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		if(args == null || args.length == 0 || args[0].equals("")) {
			args = new String[] {String.valueOf('@')};
		}
		System.out.println(new FontOutlines2Text(args[0], .1f));
	}

	private static abstract class SegmentType
	{
		final String output;

		SegmentType(double[] data)
		{
			output = getClass().getSimpleName() + ": " + format(data);
		}

		public final String toString()
		{
			return output;
		}

		abstract String format(double[] data);
	}

	private static class Move
	extends SegmentType
	{
		private Move(double ... data)
		{
			super(data);
			if(data == null || data.length < 2) {
				throw new IllegalArgumentException("invalid data");
			}
		}

		@Override
		String format(double[] data)
		{
			return "[TX: " + data[0] + ", TY: " + data[1] + "]";
		}
	}

	private static class Line
	extends SegmentType
	{
		private Line(double ... data)
		{
			super(data);
			if(data == null || data.length < 2) {
				throw new IllegalArgumentException("invalid data");
			}
		}

		@Override
		String format(double[] data)
		{
			return "[TX: " + data[0] + ", TY: " + data[1] + "]";
		}
	}

	private static class Quad
	extends SegmentType
	{
		private Quad(double ... data)
		{
			super(data);
			if(data == null || data.length < 4) {
				throw new IllegalArgumentException("invalid data");
			}
		}

		@Override
		String format(double[] data)
		{
			return "[TX: " + data[2] + ", TY: " + data[3] + ", AX: " + data[0] + ", AY: " + data[1] + "]";
		}
	}

	private static class Cubic
	extends SegmentType
	{
		private Cubic(double ... data)
		{
			super(data);
			if(data == null || data.length < 2) {
				throw new IllegalArgumentException("invalid data");
			}
		}

		@Override
		String format(double[] data)
		{
			return "[TX: " + data[4] + ", TY: " + data[5] + ", AX: " + data[0] + ", AY: " + data[1] + ", BX: " + data[2] + ", BY: " + data[3] + "]";
		}
	}

	private static class Close
	extends SegmentType
	{
		private Close()
		{
			super(null);
		}

		@Override
		String format(double[] data)
		{
			return "[]";
		}
	}
}
```


----------



## malufi89 (21. Okt 2011)

Hallo!

Vielen Dank dafür! Das sieht ja schonmal sehr nach den Werten aus, die ich gern hätte. Jetzt muss ich mal ein bisschen ausprobieren wie ich das einsetzen kann. 

Liebe Grüße


----------



## malufi89 (22. Okt 2011)

Hallo zusammen,

ich habe die letzten Tage nochmal etwas gegrübelt. Im Grunde denke ich schon, dass dieses Programm das richtige ist, allerdings habe ich festgestellt, dass in der EPS datei 2 Zahlenpaare mehr vorliegen. Um das zu erklären, habe ich mal was erstellt:

Buchstabe "a" im EPS:
p ef
714 -41 m  694 -23 675 -11 655 -4 ct 636 3 615 7 593 7 ct 557 7 529 -2 509 -19 ct 
490 -37 480 -60 480 -87 ct 480 -103 484 -118 491 -131 ct 498 -144 508 -155 520 -163 ct 
532 -171 546 -177 561 -181 ct 572 -184 588 -187 610 -190 ct 656 -195 689 -202 711 -209 ct 
711 -217 711 -222 711 -224 ct 711 -247 706 -263 695 -272 ct 681 -285 660 -291 632 -291 ct 
606 -291 586 -286 574 -277 ct 561 -268 552 -252 546 -229 ct 490 -236 l  495 -259 503 -278 515 -292 ct 
526 -307 542 -318 564 -325 ct 585 -333 610 -337 639 -337 ct 667 -337 690 -334 707 -327 ct 
725 -321 738 -312 746 -302 ct 754 -292 760 -280 763 -264 ct 765 -255 766 -238 766 -213 ct 
766 -139 l  766 -87 767 -54 769 -40 ct 772 -26 776 -13 783 0 ct 726 0 l  720 -12 716 -25 714 -41 ct 
p

ct steht für curve to, trennt also meiner meinung nach die einzelnen vektorpunkte. l steht für line.
m ist für move to. das p ist das equivalent zu deinem close.

Problem: Dein Programm gibt mir nur 4 Werte und ich habe keinen schimmer, welche die anderen beiden sein sollen (wir sind ja schließlich in 2d...)! Ich habe jetzt (relativ oberflächlich) mal nach dem Aufbau von Vektorgrafiken gegooglt und bin leider nicht schlauer geworden.  außerdem bekomme ich viel öfter ein "close" ausgegebn als ein "p".

Soviel nur als information, denn ich weiß nicht ob mir da hier jemand weiterhelfen kann... Trotzdem danke ich dir ganz ganz herzlich.


----------



## Spacerat (22. Okt 2011)

Die beiden letzten Werte (x, y) sind ein weiterer Stützpunkt, welcher bei SEG_QUAD verwendet wird. SEG_QUAD ist allerdings nur der Vollständigkeit halber mit implementiert. Mir selbst ist noch kein Zeichensatz untergekommen, der es verwendet, weil es afaik für jedes SEG_QUAD ein entsprechendes SEG_CURVE gibt, korregiert mich, wenn ich da falsch liege.
Bei deinem EPS-Beispiel müsste ct dann manchmal wohl eher qt heissen. Wie dass mit dem p bzw. dem close zustande kommt, kann ich nicht nachvollziehen. Ebenso sind dort 2 Buchstaben (ef) mit denen ich nichts anfangen kann. Wurde dieses Beispiel von dir erstellt oder woher stammt es? Wie gesagt, EPS sagt mir bisher nichts - ok... ich weis inzwischen, wofür es ist, mehr aber auch nicht.


----------



## malufi89 (22. Okt 2011)

Hallo

tschuldige dass ich das ef nicht erläutert habe - das ist das eofill. und wo ich das gerade schreibe fällt mir auf dass ich das a unvollständig kopiert habe.

vollständige darstellung des 'a' in eps ist wie folgt: 
714 -41 m  694 -23 675 -11 655 -4 ct 636 3 615 7 593 7 ct 557 7 529 -2 509 -19 ct 
490 -37 480 -60 480 -87 ct 480 -103 484 -118 491 -131 ct 498 -144 508 -155 520 -163 ct 
532 -171 546 -177 561 -181 ct 572 -184 588 -187 610 -190 ct 656 -195 689 -202 711 -209 ct 
711 -217 711 -222 711 -224 ct 711 -247 706 -263 695 -272 ct 681 -285 660 -291 632 -291 ct 
606 -291 586 -286 574 -277 ct 561 -268 552 -252 546 -229 ct 490 -236 l  495 -259 503 -278 515 -292 ct 
526 -307 542 -318 564 -325 ct 585 -333 610 -337 639 -337 ct 667 -337 690 -334 707 -327 ct 
725 -321 738 -312 746 -302 ct 754 -292 760 -280 763 -264 ct 765 -255 766 -238 766 -213 ct 
766 -139 l  766 -87 767 -54 769 -40 ct 772 -26 776 -13 783 0 ct 726 0 l  720 -12 716 -25 714 -41 ct 
p
711 -165 m  691 -157 660 -150 619 -144 ct 596 -141 580 -137 570 -133 ct 560 -129 553 -123 548 -115 ct 
543 -107 540 -98 540 -89 ct 540 -74 546 -62 557 -52 ct 568 -42 585 -37 607 -37 ct 
628 -37 648 -42 665 -51 ct 681 -61 694 -73 702 -90 ct 708 -102 711 -120 711 -145 ct 
711 -165 l  p ef
850 0 m  850 -455 l  906 -455 l  906 0 l  850 0 l  p ef

das "p ef" heißt quasi dann soviel wie "close und ende des buchstabens. dann kommt ein neuer, also jetzt verbinden!"

das p ef was ich im letzten beitrag mit kopiert hatte, stammt vom vorhergehenden buchstaben. 

über den aufbau von eps-vektorgrafiken findet man halt wirklich wenig im inet - deshalb stammt dieses beispiel auch von mir. Ich habe es mal in so ein code-Fenster kopiert (erzeugt die drei Buchstaben 'H' 'a' 'l' nebeneinander): 


```
%!PS-Adobe-3.0 EPSF-3.0 
%%BoundingBox: 0 0 539 785
%%Pages: 0
%%Creator: Sun Microsystems, Inc.
%%Title: none
%%CreationDate: none
%%LanguageLevel: 2
%%EndComments
%%BeginProlog
%%BeginResource: procset SDRes-Prolog 1.0 0
/b4_inc_state save def
/dict_count countdictstack def
/op_count count 1 sub def
userdict begin
0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin 10 setmiterlimit[] 0 setdash newpath
/languagelevel where {pop languagelevel 1 ne {false setstrokeadjust false setoverprint} if} if
/bdef {bind def} bind def
/c {setgray} bdef
/l {neg lineto} bdef
/rl {neg rlineto} bdef
/lc {setlinecap} bdef
/lj {setlinejoin} bdef
/lw {setlinewidth} bdef
/ml {setmiterlimit} bdef
/ld {setdash} bdef
/m {neg moveto} bdef
/ct {6 2 roll neg 6 2 roll neg 6 2 roll neg curveto} bdef
/r {rotate} bdef
/t {neg translate} bdef
/s {scale} bdef
/sw {show} bdef
/gs {gsave} bdef
/gr {grestore} bdef
/f {findfont dup length dict begin
{1 index /FID ne {def} {pop pop} ifelse} forall /Encoding ISOLatin1Encoding def
currentdict end /NFont exch definefont pop /NFont findfont} bdef
/p {closepath} bdef
/sf {scalefont setfont} bdef
/ef {eofill}bdef
/pc {closepath stroke}bdef
/ps {stroke}bdef
/pum {matrix currentmatrix}bdef
/pom {setmatrix}bdef
/bs {/aString exch def /nXOfs exch def /nWidth exch def currentpoint nXOfs 0 rmoveto pum nWidth aString stringwidth pop div 1 scale aString show pom moveto} bdef
%%EndResource
%%EndProlog
%%BeginSetup
%%EndSetup
%%Page: 1 1
%%BeginPageSetup
%%EndPageSetup
pum
0.02836 0.02833 s 
0 -27700 t
/tm matrix currentmatrix def
gs
tm setmatrix
-1000 -1000 t 
1 1 s 
1000 1000 m 19999 1000 l 19999 28699 l 1000 28699 l 1000 1000 l eoclip newpath
gs
0 0 m 18999 0 l 18999 27699 l 0 27699 l 0 0 l eoclip newpath

pum
1250 1697 t
0.000 c 51 0 m  51 -455 l  111 -455 l  111 -268 l  348 -268 l  348 -455 l 
408 -455 l  408 0 l  348 0 l  348 -214 l  111 -214 l  111 0 l  51 0 l 
p ef
714 -41 m  694 -23 675 -11 655 -4 ct 636 3 615 7 593 7 ct 557 7 529 -2 509 -19 ct 
490 -37 480 -60 480 -87 ct 480 -103 484 -118 491 -131 ct 498 -144 508 -155 520 -163 ct 
532 -171 546 -177 561 -181 ct 572 -184 588 -187 610 -190 ct 656 -195 689 -202 711 -209 ct 
711 -217 711 -222 711 -224 ct 711 -247 706 -263 695 -272 ct 681 -285 660 -291 632 -291 ct 
606 -291 586 -286 574 -277 ct 561 -268 552 -252 546 -229 ct 490 -236 l  495 -259 503 -278 515 -292 ct 
526 -307 542 -318 564 -325 ct 585 -333 610 -337 639 -337 ct 667 -337 690 -334 707 -327 ct 
725 -321 738 -312 746 -302 ct 754 -292 760 -280 763 -264 ct 765 -255 766 -238 766 -213 ct 
766 -139 l  766 -87 767 -54 769 -40 ct 772 -26 776 -13 783 0 ct 726 0 l  720 -12 716 -25 714 -41 ct 
p
711 -165 m  691 -157 660 -150 619 -144 ct 596 -141 580 -137 570 -133 ct 560 -129 553 -123 548 -115 ct 
543 -107 540 -98 540 -89 ct 540 -74 546 -62 557 -52 ct 568 -42 585 -37 607 -37 ct 
628 -37 648 -42 665 -51 ct 681 -61 694 -73 702 -90 ct 708 -102 711 -120 711 -145 ct 
711 -165 l  p ef
850 0 m  850 -455 l  906 -455 l  906 0 l  850 0 l  p ef
pom
gr
gs
0 0 m 18999 0 l 18999 27699 l 0 27699 l 0 0 l eoclip newpath
gr
gr
0 27700 t 
pom
count op_count sub {pop} repeat countdictstack dict_count sub {end} repeat b4_inc_state restore
%%PageTrailer
%%Trailer
%%EOF
```


----------



## Spacerat (23. Okt 2011)

Ok... Mit diesen Infos lassen sich mit meinem Proggi zumindest schon mal Buchstaben in EPS erstellen. Für ganze EPS-Dateien reicht es allerdings nicht. Werd' mich mal drum kümmern und das Ergebnis hier posten.


----------



## Spacerat (23. Okt 2011)

..und wieder ein DP... wieso kann man seine Beiträge eigentlich nach einer gewissen Zeit nicht mehr editieren? grrrr

```
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.image.BufferedImage;
import java.util.List;

import datatypes.collections.ArrayList;

public class FontOutlines2Text
{
	private static final long serialVersionUID = 3084369841425890033L;
	static final int DM = 8;
	static final String LINE_SEPARATOR = System.getProperty("line.separator");

	final List<SegmentType> segments = new ArrayList<SegmentType>();
	final char[] text;
	final float prec;
	final Font bigFont;

	private FontOutlines2Text(String text, float prec)
	{
		Graphics g = GraphicsEnvironment.getLocalGraphicsEnvironment().createGraphics(new BufferedImage(1,1,2));
		bigFont = g.getFont().deriveFont(600.f);
		this.text = text.toCharArray();
		this.prec = (prec % 1);
		AffineTransform at = bigFont.getTransform();
		FontRenderContext frc = new FontRenderContext(at, true, false);
		for(int n = 0; n < this.text.length; n++) {
			GlyphVector gv = bigFont.createGlyphVector(frc, String.valueOf(this.text[n]));
			GeneralPath gp = new GeneralPath(gv.getOutline());
			PathIterator pi = gp.getPathIterator(at);
			double dp[] = new double[6];
			int seg;
			// DAS WESENTLICHE
			do {
				seg = pi.currentSegment(dp);
				switch(seg) {
				case PathIterator.SEG_MOVETO:
					segments.add(new Move((int) dp[0], (int) dp[1]));
					break;
				case PathIterator.SEG_LINETO:
					segments.add(new Line((int) dp[0], (int) dp[1]));
					break;
				case PathIterator.SEG_CUBICTO:
					segments.add(new Cubic((int) dp[0], (int) dp[1], (int) dp[2], (int) dp[3]));
					break;
				case PathIterator.SEG_QUADTO:
					segments.add(new Quad((int) dp[0], (int) dp[1], (int) dp[2], (int) dp[3], (int) dp[4], (int) dp[5]));
					break;
				case PathIterator.SEG_CLOSE:
					segments.add(new Close());
					break;
				}
				pi.next();
			} while(!pi.isDone());
			segments.add(new EndOfLetter());
			// ENDE VON DAS WESENTLICHE
		}
	}

	@Override
	public String toString()
	{
		int cnt = 0;
		StringBuilder sb = new StringBuilder();
		for(SegmentType st : segments) {
			sb.append(st);
			sb.append((++cnt % 4 == 0 || st instanceof EndOfLetter)? LINE_SEPARATOR : " ");
		}
		return sb.toString();
	}

	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		if(args == null || args.length == 0 || args[0].equals("")) {
			args = new String[] {"Hal"};
		}
		System.out.println(new FontOutlines2Text(args[0], .1f));
	}

	private static abstract class SegmentType
	{
		final String output;

		SegmentType(int ... data)
		{
			output = format(data);
		}

		public final String toString()
		{
			return output;
		}

		abstract String format(int ... data);
	}

	private static class Move
	extends SegmentType
	{
		private Move(int ... data)
		{
			super(data);
			if(data == null || data.length < 2) {
				throw new IllegalArgumentException("invalid data");
			}
		}

		@Override
		String format(int ... data)
		{
			return data[0] + " " + data[1] + " m";
		}
	}

	private static class Line
	extends SegmentType
	{
		private Line(int ... data)
		{
			super(data);
			if(data == null || data.length < 2) {
				throw new IllegalArgumentException("invalid data");
			}
		}

		@Override
		String format(int ... data)
		{
			return data[0] + " " + data[1] + " l";
		}
	}

	private static class Quad
	extends SegmentType
	{
		private Quad(int ... data)
		{
			super(data);
			if(data == null || data.length < 4) {
				throw new IllegalArgumentException("invalid data");
			}
		}

		@Override
		String format(int ... data)
		{
			return data[2] + " " + data[3] + " " + data[0] + " " + data[1] + " ct";
		}
	}

	private static class Cubic
	extends SegmentType
	{
		private Cubic(int ... data)
		{
			super(data);
			if(data == null || data.length < 2) {
				throw new IllegalArgumentException("invalid data");
			}
		}

		@Override
		String format(int ... data)
		{
			// TODO qt may not be right.
			return data[4] + " " + data[5] + " " + data[0] + " " + data[1] + " " + data[2] + " " + data[3] + " qt";
		}
	}

	private static class Close
	extends SegmentType
	{
		private Close()
		{
			super(null);
		}

		@Override
		String format(int ... data)
		{
			return "p";
		}
	}

	private static class EndOfLetter
	extends SegmentType
	{
		private EndOfLetter()
		{
			super(null);
		}

		@Override
		String format(int ... data)
		{
			return "ef";
		}
	}
}
```
So die Ausgabe von diesem hier sieht schon mal mehr nach deiner Vorgabe aus. Mängel hat's noch bei der Verteilung der Elemente auf die Zeilen. Warum aber bei dir 6 statt 4 Werte für ct ausgegeben werden, ist mir nach wie vor ein Rätsel. Welchen Zeichensatz verwendest du? Vllt. liegt es ja daran.


----------



## malufi89 (23. Okt 2011)

Die Encoding Angaben sind in der EPS Datei mit angegeben: ISO Latin 1. Ich glaube Schriftart war Arial...
ich probiers mal direkt mit anderen Schriftarten und encodings...:

Ich habes jetzt mal mit Times und der Option LZW Kodierung probiert - eine andere option habe ich beim speichern der eps leider nicht. an der parameteranzahl oder der kodierung ändert das nichts... In Photoshop kann ich auswählen. Ascii85 produziert jedoch absolut ncihts schönes  ASCII selbst auch nicht. Kodiere ich binär, siehts noch wüster aus. Und ich kanns noch als JPEG kodieren (was ich so auch noch nich gesehen hab...) also nichts was uns wirklich helfen würde, möchte ich behaupten... oder hast du andere vorschläge was ich noch probieren könnte?


----------



## Spacerat (23. Okt 2011)

Ok... wenn ich mit die Definition von "ct" ansehe, fällt mit da diese "6 2" ins Auge. Daraus vllt. mal "4 2" machen, die Ausgabe meines Progs an entsprechender Stelle einfügen und anschliessend versuchen die Datei zu importieren. Andernfalls müsste aus dem Java-CUBIC_TO...
Au Weia... vllt. hab' ich ja auch was unheimlich verwechselt, nämlich CUBIC (3) und QUAD (2) 
...Entwarnung... hätt' mich gewundert, wenn es so gewesen wär.
Also Folgendes... Die einzige Verwechslung bei meinem letzten Code, war die Anzahl der übergebenen Werte an Quad und Cubic sowie deren "format"-Methode. Wie es aussieht vektorisiert dein Zeichenprogramm beim Speichern die Glyphen neu und zwar von Quad (Java-Ausgabe) in Cubic. Seitens Java ist wohl nun auch eine solche Wandlung erforderlich. Aber da solltest du vllt. mal im Mathe-Forum fragen, wie man aus einem Polynom 2. Grades ein kongruentes Pendant 3. Grades macht.


----------

