xmlgraphics-batik-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ruben malchow <ruben.malc...@googlemail.com>
Subject Re: fonts & kerning
Date Tue, 13 Nov 2012 23:51:34 GMT

hi,

i think you are right - i think the confusion was coming from the fact that inkscape can't
do SVG fonts, so if i open it there, everything goes bonkers. below the full code, for reference
of others, although i think something is wrong with the rectangles in draw - although the
context is dynamic, they're not drawn until i write the svg out, then transcode the output.


however, for the current task, one upstream component cannot handle kerning (yet) and we have
to be able to see it consistenly in different editors, so the path i chose now is to convert
the text to paths immediately, basically doing this:

	Node n = SvgUtil.createSvg();
	CharacterIterator ci = new StringCharacterIterator(text);
	Character c = null;
	double currX = 0;
	Element gMaster = (Element)SvgUtil.createSvgNode(n, SVG12Constants.SVG_G_TAG);
			
	FontGlyph lastGlyph = null;

	FontGlyph fg = getGlyph(ci.current());

	while((c = ci.current())!=CharacterIterator.DONE) {

		Element g = (Element)SvgUtil.createSvgNode(gMaster, SVG12Constants.SVG_G_TAG);
		g.setAttributeNS(null,SVG12Constants.SVG_TRANSFORM_ATTRIBUTE, 
				"translate("+currX+" 0) " +
				"scale("+getGlyphScale(point)+" "+(-1d*getGlyphScale(point))+")" +
				"");
		Element p = (Element)SvgUtil.createSvgNode(g, SVG12Constants.SVG_PATH_TAG);
		p.setAttributeNS(null, "d", fg.getNodePath());

		// standard h-advance
		double advance = fg.gethAdvanceX(point); 

		currX+=advance;
		ci.next();
		lastGlyph = fg;
		if(ci.current()==CharacterIterator.DONE) {
			break;
		}

		fg = getGlyph(ci.current());
		if(kern) {
			double k = getKerning(lastGlyph.getGlyphName(), fg.getGlyphName());
			log.info("== KERNING "+lastGlyph.getGlyphName()+" / "+fg.getGlyphName()+": "+k);
			currX = currX - (emToUserspace(k, point));
		}
	}



i probably will lose support for arabic, korean, thai and various other scripts, because this
cannot properly do combined glyphs (i guess), but it works quite nice and has the benefit
of looking exaxtly the same, no matter if it's batik, inkscape, illustrator or any other editor.


.rm


===== font / getExtentOfChar() ===== ===== ===== ===== ===== ===== ===== ===== ===== =====



import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.dom.svg.SVGOMTextElement;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.transcoder.SVGAbstractTranscoder;
import org.apache.batik.transcoder.Transcoder;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.batik.util.SVG12Constants;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGRect;

public class Font {

	public static void text(String textContent, String outfilename) {
		try {

			// boot the document
			DOMImplementation domImpl = new SVGDOMImplementation();
			String svgNS = SVG12Constants.SVG_NAMESPACE_URI;
			String svgPrefix = SVG12Constants.SVG_SVG_TAG;
			SVGDocument svg = (SVGDocument) domImpl.createDocument(svgNS, svgPrefix, null);
			SVGGraphics2D graphics = new SVGGraphics2D(svg);
			UserAgent userAgent = new UserAgentAdapter();
			DocumentLoader loader = new DocumentLoader(userAgent);
			BridgeContext ctx = new BridgeContext(userAgent, loader);
			ctx.setDynamicState(BridgeContext.DYNAMIC);
			GVTBuilder builder = new GVTBuilder();
			GraphicsNode rootGN = builder.build(ctx, svg);

			svg.getDocumentElement().setAttributeNS(null, "version", "1.2");

			// add the font reference in the defs
			Element fontface = svg.createElementNS(svgNS, SVGConstants.SVG_FONT_FACE_TAG);
			fontface.setAttributeNS(null, "font-family", "test_font");

			Element fontfacesrc = svg.createElementNS(svgNS, SVGConstants.SVG_FONT_FACE_SRC_TAG);
			Element fontfaceuri = svg.createElementNS(svgNS, SVGConstants.SVG_FONT_FACE_URI_TAG);
			File f = new File("FONT.SVG");
			fontfaceuri.setAttributeNS(svgNS, "xlink:href", f.toURI()+"#font-2c95d8b23a8e51ae013a8e75ff18006b");

			Element fontfaceformat = svg.createElementNS(svgNS, SVGConstants.SVG_FONT_FACE_FORMAT_TAG);
			fontfaceformat.setAttributeNS(svgNS, "string", "svg");

			Element defs = svg.createElementNS(svgNS, SVGConstants.SVG_DEFS_TAG);

			fontfaceuri.appendChild(fontfaceformat);
			fontfacesrc.appendChild(fontfaceuri);
			fontface.appendChild(fontfacesrc);
			defs.appendChild(fontface);

			// create the text element
			Element text = svg.createElementNS(svgNS, SVG12Constants.SVG_TEXT_TAG);

			svg.getDocumentElement().appendChild(text);

			text.setAttributeNS(null, SVGConstants.SVG_STYLE_ATTRIBUTE, "font-size:12pt;" + "font-family:test_font;"
+ "font-style:normal;" + "font-variant:normal;" + "font-weight:normal;" + "fill:#000000;"
+ "stroke:none");
			text.setAttributeNS(null,"y", "200");

			Node fpText = text.getOwnerDocument().createTextNode(textContent);
			text.appendChild(fpText);

			SVGOMTextElement te = (SVGOMTextElement) text;

			// paint a border around each character
			for (int i = 0; i < textContent.length(); i++) {
				System.err.println(" getting extent of char: "+textContent.charAt(i));
				SVGRect rect = te.getExtentOfChar(i);
				Element e = svg.createElement("rect");
				e.setAttributeNS(null, "x", rect.getX() + "");
				e.setAttributeNS(null, "y", rect.getY() + "");
				e.setAttributeNS(null, "width", rect.getWidth() + "");
				e.setAttributeNS(null, "height", rect.getHeight() + "");
				e.setAttributeNS(null, "stroke", "#000000");
				e.setAttributeNS(null, "fill", "none ");
				e.setAttributeNS(null, "stroke-width", "1");
				//e.setAttributeNS(null, "style", "fill:none;fill-opacity:1;stroke:#0000ff;stroke-width:1;stroke-opacity:1;stroke-dasharray:9,9;stroke-dashoffset:0");
				svg.getDocumentElement().appendChild(e);
			}
			
			// output to svg
			{
				FileOutputStream fos = new FileOutputStream(outfilename+".svg");
				DOMSource domSource = new DOMSource(svg);
				StreamResult res = new StreamResult(fos);
				Transformer t = TransformerFactory.newInstance().newTransformer();
				t.transform(domSource, res);
				fos.flush();
			}
			
			// transcode to png
			{
				FileInputStream fis = new FileInputStream(outfilename+".svg");
				FileOutputStream fos = new FileOutputStream(outfilename+".png");
				TranscoderInput tip = new TranscoderInput(fis);
				TranscoderOutput top = new TranscoderOutput(fos);
				Transcoder t = new PNGTranscoder();
		        t.addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, 1600f);
				t.addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, 1600f);
				t.transcode(tip, top);
				fos.flush();
			}
			
			
			
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		text("HELLO AT, W, T, V, or other Kerning","with_spaces");
		text("HELLOAT,W,T,V,orotherKerning","without_spaces");
		
	}
}





On Nov 10, 2012, at 11:21 PM, DeWeese Thomas wrote:

> Hi Ruben,
> 
>    I'm fairly certain that getExtentOfChar works properly.  We have a fairly complete
test of it in "samples/tests/spec/scripting/text_content.svg" I added some hkern examples
to the SVG font that test includes and getExtentOfChar worked correctly in that context. 
In looking at that test one thing that occurred to me is the possibility that the coordinate
system you are adding the rectangles is slightly different from the texts coordinate system
(note that getExtentOfChar returns box in the text element not in the coordinate system of
the parent of the text which is likely where you are appending the rect).  You might want
to adopt the code that test uses to display it's rectangles for your rectangle display.
> 
>    BTW it's expected that the boxes don't overlap that doesn't indicate that the kerning
is being ignored.
> The extent boxes for horizontal text are adjusted to just touch each other so they can
be used for hit detection.  If text selection works correctly for that text then getExtentOfChar
should work.
> 
>    As for the issue with getting the extent of a single space char I suspect the issue
is that we are required to remove
> leading and trailing spaces from text elements in the normal case. I think you need to
set the "xml:space" attribute
> to "preserve".
> 
>    I would strong recommend that you try and get the 'getExtentOfChar' approach to work
for you as that is a much better solution than dropping support for all the nice text features
in SVG.  It also seems to me like if you can't getExtentOfChar to work properly then it likely
means you don't really understand your coordinate systems which will likely lead to similar
issues down the road with your own solution eventually.
> 
>    Thomas


Mime
View raw message