1/*
2 * Copyright 2005-2009, Stephan Aßmus <superstippi@gmx.de>.
3 * Copyright 2008, Andrej Spielmann <andrej.spielmann@seh.ox.ac.uk>.
4 * All rights reserved. Distributed under the terms of the MIT License.
5 */
6
7#include "AGGTextRenderer.h"
8
9#include <agg_basics.h>
10#include <agg_bounding_rect.h>
11#include <agg_conv_segmentator.h>
12#include <agg_conv_transform.h>
13#include <agg_trans_affine.h>
14
15#include <math.h>
16#include <malloc.h>
17#include <stdio.h>
18#include <string.h>
19
20#define SHOW_GLYPH_BOUNDS 0
21
22#if SHOW_GLYPH_BOUNDS
23#	include <agg_conv_stroke.h>
24#	include <agg_path_storage.h>
25#endif
26
27#include "GlobalSubpixelSettings.h"
28#include "GlyphLayoutEngine.h"
29#include "IntRect.h"
30
31
32AGGTextRenderer::AGGTextRenderer(renderer_subpix_type& subpixRenderer,
33		renderer_type& solidRenderer, renderer_bin_type& binRenderer,
34		scanline_unpacked_type& scanline,
35		scanline_unpacked_subpix_type& subpixScanline,
36		rasterizer_subpix_type& subpixRasterizer)
37	:
38	fPathAdaptor(),
39	fGray8Adaptor(),
40	fGray8Scanline(),
41	fMonoAdaptor(),
42	fMonoScanline(),
43	fSubpixAdaptor(),
44
45	fCurves(fPathAdaptor),
46	fContour(fCurves),
47
48	fSolidRenderer(solidRenderer),
49	fBinRenderer(binRenderer),
50	fSubpixRenderer(subpixRenderer),
51	fScanline(scanline),
52	fSubpixScanline(subpixScanline),
53	fSubpixRasterizer(subpixRasterizer),
54	fRasterizer(),
55
56	fHinted(true),
57	fAntialias(true),
58	fKerning(true),
59	fEmbeddedTransformation()
60{
61	fCurves.approximation_scale(2.0);
62	fContour.auto_detect_orientation(false);
63}
64
65
66AGGTextRenderer::~AGGTextRenderer()
67{
68}
69
70
71void
72AGGTextRenderer::SetFont(const ServerFont& font)
73{
74	fFont = font;
75
76	// construct an embedded transformation (rotate & shear)
77	fEmbeddedTransformation.Reset();
78	fEmbeddedTransformation.ShearBy(B_ORIGIN,
79		(90.0 - font.Shear()) * M_PI / 180.0, 0.0);
80	fEmbeddedTransformation.RotateBy(B_ORIGIN,
81		-font.Rotation() * M_PI / 180.0);
82
83	fContour.width(font.FalseBoldWidth() * 2.0);
84}
85
86
87void
88AGGTextRenderer::SetHinting(bool hinting)
89{
90	fHinted = hinting;
91}
92
93
94void
95AGGTextRenderer::SetAntialiasing(bool antialiasing)
96{
97	if (fAntialias != antialiasing) {
98		fAntialias = antialiasing;
99		// NOTE: The fSubpixRasterizer is not used when anti-aliasing is
100		// disbaled.
101		if (!fAntialias)
102			fRasterizer.gamma(agg::gamma_threshold(0.5));
103		else
104			fRasterizer.gamma(agg::gamma_power(1.0));
105	}
106}
107
108
109typedef agg::conv_transform<FontCacheEntry::CurveConverter, Transformable>
110	conv_font_trans_type;
111
112typedef agg::conv_transform<FontCacheEntry::ContourConverter, Transformable>
113	conv_font_contour_trans_type;
114
115
116
117class AGGTextRenderer::StringRenderer {
118public:
119	StringRenderer(const IntRect& clippingFrame, bool dryRun,
120			bool subpixelAntiAliased,
121			FontCacheEntry::TransformedOutline& transformedGlyph,
122			FontCacheEntry::TransformedContourOutline& transformedContour,
123			const Transformable& transform,
124			const BPoint& transformOffset,
125			BPoint* nextCharPos,
126			AGGTextRenderer& renderer)
127		:
128		fTransform(transform),
129		fTransformOffset(transformOffset),
130		fClippingFrame(clippingFrame),
131		fDryRun(dryRun),
132		fSubpixelAntiAliased(subpixelAntiAliased),
133		fVector(false),
134		fBounds(INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN),
135		fNextCharPos(nextCharPos),
136
137		fTransformedGlyph(transformedGlyph),
138		fTransformedContour(transformedContour),
139
140		fRenderer(renderer)
141	{
142	}
143
144	void Start()
145	{
146		fRenderer.fRasterizer.reset();
147		fRenderer.fSubpixRasterizer.reset();
148	}
149
150	void Finish(double x, double y)
151	{
152		if (fVector) {
153			if (fSubpixelAntiAliased) {
154				agg::render_scanlines(fRenderer.fSubpixRasterizer,
155					fRenderer.fSubpixScanline, fRenderer.fSubpixRenderer);
156			} else {
157				agg::render_scanlines(fRenderer.fRasterizer,
158					fRenderer.fScanline, fRenderer.fSolidRenderer);
159			}
160		}
161
162		if (fNextCharPos) {
163			fNextCharPos->x = x;
164			fNextCharPos->y = y;
165			fTransform.Transform(fNextCharPos);
166		}
167	}
168
169	void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y)
170	{
171	}
172
173	bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph,
174		FontCacheEntry* entry, double x, double y)
175	{
176		// "glyphBounds" is the bounds of the glyph transformed
177		// by the x y location of the glyph along the base line,
178		// it is therefor yet "untransformed" in case there is an
179		// embedded transformation.
180		const agg::rect_i& r = glyph->bounds;
181		IntRect glyphBounds(int32(r.x1 + x), int32(r.y1 + y - 1),
182			int32(r.x2 + x + 1), int32(r.y2 + y + 1));
183			// NOTE: "-1"/"+1" converts the glyph bounding box from pixel
184			// indices to pixel area coordinates
185
186		// track bounding box
187		if (glyphBounds.IsValid())
188			fBounds = fBounds | glyphBounds;
189
190		// render the glyph if this is not a dry run
191		if (!fDryRun) {
192			// init the fontmanager's embedded adaptors
193			// NOTE: The initialization for the "location" of
194			// the glyph is different depending on whether we
195			// deal with non-(rotated/sheared) text, in which
196			// case we have a native FT bitmap. For rotated or
197			// sheared text, we use AGG vector outlines and
198			// a transformation pipeline, which will be applied
199			// _after_ we retrieve the outline, and that's why
200			// we simply pass x and y, which are untransformed.
201
202			// "glyphBounds" is now transformed into screen coords
203			// in order to stop drawing when we are already outside
204			// of the clipping frame
205			if (glyph->data_type != glyph_data_outline) {
206				// we cannot use the transformation pipeline
207				double transformedX = x + fTransformOffset.x;
208				double transformedY = y + fTransformOffset.y;
209				entry->InitAdaptors(glyph, transformedX, transformedY,
210					fRenderer.fMonoAdaptor,
211					fRenderer.fGray8Adaptor,
212					fRenderer.fPathAdaptor);
213
214				glyphBounds.OffsetBy(fTransformOffset);
215			} else {
216				entry->InitAdaptors(glyph, x, y,
217					fRenderer.fMonoAdaptor,
218					fRenderer.fGray8Adaptor,
219					fRenderer.fPathAdaptor);
220
221				int32 falseBoldWidth = (int32)fRenderer.fContour.width();
222				if (falseBoldWidth != 0)
223					glyphBounds.InsetBy(-falseBoldWidth, -falseBoldWidth);
224				// TODO: not correct! this is later used for clipping,
225				// but it doesn't get the rect right
226				glyphBounds = fTransform.TransformBounds(glyphBounds);
227			}
228
229			if (fClippingFrame.Intersects(glyphBounds)) {
230				switch (glyph->data_type) {
231					case glyph_data_mono:
232						agg::render_scanlines(fRenderer.fMonoAdaptor,
233							fRenderer.fMonoScanline, fRenderer.fBinRenderer);
234						break;
235
236					case glyph_data_gray8:
237						agg::render_scanlines(fRenderer.fGray8Adaptor,
238							fRenderer.fGray8Scanline, fRenderer.fSolidRenderer);
239						break;
240
241					case glyph_data_subpix:
242						agg::render_scanlines(fRenderer.fGray8Adaptor,
243							fRenderer.fGray8Scanline,
244							fRenderer.fSubpixRenderer);
245						break;
246
247					case glyph_data_outline: {
248						fVector = true;
249						if (fSubpixelAntiAliased) {
250							if (fRenderer.fContour.width() == 0.0) {
251								fRenderer.fSubpixRasterizer.add_path(
252									fTransformedGlyph);
253							} else {
254								fRenderer.fSubpixRasterizer.add_path(
255									fTransformedContour);
256							}
257						} else {
258							if (fRenderer.fContour.width() == 0.0) {
259								fRenderer.fRasterizer.add_path(
260									fTransformedGlyph);
261							} else {
262								fRenderer.fRasterizer.add_path(
263									fTransformedContour);
264							}
265						}
266#if SHOW_GLYPH_BOUNDS
267	agg::path_storage p;
268	p.move_to(glyphBounds.left + 0.5, glyphBounds.top + 0.5);
269	p.line_to(glyphBounds.right + 0.5, glyphBounds.top + 0.5);
270	p.line_to(glyphBounds.right + 0.5, glyphBounds.bottom + 0.5);
271	p.line_to(glyphBounds.left + 0.5, glyphBounds.bottom + 0.5);
272	p.close_polygon();
273	agg::conv_stroke<agg::path_storage> ps(p);
274	ps.width(1.0);
275	if (fSubpixelAntiAliased) {
276		fRenderer.fSubpixRasterizer.add_path(ps);
277	} else {
278		fRenderer.fRasterizer.add_path(ps);
279	}
280#endif
281
282						break;
283					}
284					default:
285						break;
286				}
287			}
288		}
289		return true;
290	}
291
292	IntRect Bounds() const
293	{
294		return fBounds;
295	}
296
297private:
298 	const Transformable& fTransform;
299	const BPoint&		fTransformOffset;
300	const IntRect&		fClippingFrame;
301	bool				fDryRun;
302	bool				fSubpixelAntiAliased;
303	bool				fVector;
304	IntRect				fBounds;
305	BPoint*				fNextCharPos;
306
307	FontCacheEntry::TransformedOutline& fTransformedGlyph;
308	FontCacheEntry::TransformedContourOutline& fTransformedContour;
309	AGGTextRenderer&	fRenderer;
310};
311
312
313BRect
314AGGTextRenderer::RenderString(const char* string, uint32 length,
315	const BPoint& baseLine, const BRect& clippingFrame, bool dryRun,
316	BPoint* nextCharPos, const escapement_delta* delta,
317	FontCacheReference* cacheReference)
318{
319//printf("RenderString(\"%s\", length: %ld, dry: %d)\n", string, length, dryRun);
320
321	Transformable transform(fEmbeddedTransformation);
322	transform.TranslateBy(baseLine);
323
324	fCurves.approximation_scale(transform.scale());
325
326	// use a transformation behind the curves
327	// (only if glyph->data_type == agg::glyph_data_outline)
328	// in the pipeline for the rasterizer
329	FontCacheEntry::TransformedOutline
330		transformedOutline(fCurves, transform);
331	FontCacheEntry::TransformedContourOutline
332		transformedContourOutline(fContour, transform);
333
334	// for when we bypass the transformation pipeline
335	BPoint transformOffset(0.0, 0.0);
336	transform.Transform(&transformOffset);
337
338	StringRenderer renderer(clippingFrame, dryRun,
339		gSubpixelAntialiasing && fAntialias,
340		transformedOutline, transformedContourOutline,
341		transform, transformOffset, nextCharPos, *this);
342
343	GlyphLayoutEngine::LayoutGlyphs(renderer, fFont, string, length, delta,
344		fKerning, B_BITMAP_SPACING, NULL, cacheReference);
345
346	return transform.TransformBounds(renderer.Bounds());
347}
348
349
350BRect
351AGGTextRenderer::RenderString(const char* string, uint32 length,
352	const BPoint* offsets, const BRect& clippingFrame, bool dryRun,
353	BPoint* nextCharPos, FontCacheReference* cacheReference)
354{
355//printf("RenderString(\"%s\", length: %ld, dry: %d)\n", string, length, dryRun);
356
357	Transformable transform(fEmbeddedTransformation);
358
359	fCurves.approximation_scale(transform.scale());
360
361	// use a transformation behind the curves
362	// (only if glyph->data_type == agg::glyph_data_outline)
363	// in the pipeline for the rasterizer
364	FontCacheEntry::TransformedOutline
365		transformedOutline(fCurves, transform);
366	FontCacheEntry::TransformedContourOutline
367		transformedContourOutline(fContour, transform);
368
369	// for when we bypass the transformation pipeline
370	BPoint transformOffset(0.0, 0.0);
371	transform.Transform(&transformOffset);
372
373	StringRenderer renderer(clippingFrame, dryRun,
374		gSubpixelAntialiasing && fAntialias,
375		transformedOutline, transformedContourOutline,
376		transform, transformOffset, nextCharPos, *this);
377
378	GlyphLayoutEngine::LayoutGlyphs(renderer, fFont, string, length, NULL,
379		fKerning, B_BITMAP_SPACING, offsets, cacheReference);
380
381	return transform.TransformBounds(renderer.Bounds());
382}
383