xref: /haiku/src/servers/app/drawing/Painter/AGGTextRenderer.cpp (revision 5b6fca06392320a9c32d542b519d8f4fe876aef2)
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 
32 AGGTextRenderer::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 
66 AGGTextRenderer::~AGGTextRenderer()
67 {
68 }
69 
70 
71 void
72 AGGTextRenderer::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 
87 void
88 AGGTextRenderer::SetHinting(bool hinting)
89 {
90 	fHinted = hinting;
91 }
92 
93 
94 void
95 AGGTextRenderer::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 
109 typedef agg::conv_transform<FontCacheEntry::CurveConverter, Transformable>
110 	conv_font_trans_type;
111 
112 typedef agg::conv_transform<FontCacheEntry::ContourConverter, Transformable>
113 	conv_font_contour_trans_type;
114 
115 
116 
117 class AGGTextRenderer::StringRenderer {
118 public:
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(LONG_MAX, LONG_MAX, LONG_MIN, LONG_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 	void Finish(double x, double y)
150 	{
151 		if (fVector) {
152 			if (fSubpixelAntiAliased) {
153 				agg::render_scanlines(fRenderer.fSubpixRasterizer,
154 					fRenderer.fSubpixScanline, fRenderer.fSubpixRenderer);
155 			} else {
156 				agg::render_scanlines(fRenderer.fRasterizer,
157 					fRenderer.fScanline, fRenderer.fSolidRenderer);
158 			}
159 		}
160 
161 		if (fNextCharPos) {
162 			fNextCharPos->x = x;
163 			fNextCharPos->y = y;
164 			fTransform.Transform(fNextCharPos);
165 		}
166 	}
167 
168 	void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y)
169 	{
170 	}
171 
172 	bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph,
173 		FontCacheEntry* entry, double x, double y)
174 	{
175 		// "glyphBounds" is the bounds of the glyph transformed
176 		// by the x y location of the glyph along the base line,
177 		// it is therefor yet "untransformed" in case there is an
178 		// embedded transformation.
179 		const agg::rect_i& r = glyph->bounds;
180 		IntRect glyphBounds(int32(r.x1 + x), int32(r.y1 + y - 1),
181 			int32(r.x2 + x + 1), int32(r.y2 + y + 1));
182 			// NOTE: "-1"/"+1" converts the glyph bounding box from pixel
183 			// indices to pixel area coordinates
184 
185 		// track bounding box
186 		if (glyphBounds.IsValid())
187 			fBounds = fBounds | glyphBounds;
188 
189 		// render the glyph if this is not a dry run
190 		if (!fDryRun) {
191 			// init the fontmanager's embedded adaptors
192 			// NOTE: The initialization for the "location" of
193 			// the glyph is different depending on whether we
194 			// deal with non-(rotated/sheared) text, in which
195 			// case we have a native FT bitmap. For rotated or
196 			// sheared text, we use AGG vector outlines and
197 			// a transformation pipeline, which will be applied
198 			// _after_ we retrieve the outline, and that's why
199 			// we simply pass x and y, which are untransformed.
200 
201 			// "glyphBounds" is now transformed into screen coords
202 			// in order to stop drawing when we are already outside
203 			// of the clipping frame
204 			if (glyph->data_type != glyph_data_outline) {
205 				// we cannot use the transformation pipeline
206 				double transformedX = x + fTransformOffset.x;
207 				double transformedY = y + fTransformOffset.y;
208 				entry->InitAdaptors(glyph, transformedX, transformedY,
209 					fRenderer.fMonoAdaptor,
210 					fRenderer.fGray8Adaptor,
211 					fRenderer.fPathAdaptor);
212 
213 				glyphBounds.OffsetBy(fTransformOffset);
214 			} else {
215 				entry->InitAdaptors(glyph, x, y,
216 					fRenderer.fMonoAdaptor,
217 					fRenderer.fGray8Adaptor,
218 					fRenderer.fPathAdaptor);
219 
220 				int32 falseBoldWidth = (int32)fRenderer.fContour.width();
221 				if (falseBoldWidth != 0)
222 					glyphBounds.InsetBy(-falseBoldWidth, -falseBoldWidth);
223 				// TODO: not correct! this is later used for clipping,
224 				// but it doesn't get the rect right
225 				glyphBounds = fTransform.TransformBounds(glyphBounds);
226 			}
227 
228 			if (fClippingFrame.Intersects(glyphBounds)) {
229 				switch (glyph->data_type) {
230 					case glyph_data_mono:
231 						agg::render_scanlines(fRenderer.fMonoAdaptor,
232 							fRenderer.fMonoScanline, fRenderer.fBinRenderer);
233 						break;
234 
235 					case glyph_data_gray8:
236 						agg::render_scanlines(fRenderer.fGray8Adaptor,
237 							fRenderer.fGray8Scanline, fRenderer.fSolidRenderer);
238 						break;
239 
240 					case glyph_data_subpix:
241 						agg::render_scanlines(fRenderer.fGray8Adaptor,
242 							fRenderer.fGray8Scanline,
243 							fRenderer.fSubpixRenderer);
244 						break;
245 
246 					case glyph_data_outline: {
247 						fVector = true;
248 						if (fSubpixelAntiAliased) {
249 							if (fRenderer.fContour.width() == 0.0) {
250 								fRenderer.fSubpixRasterizer.add_path(
251 									fTransformedGlyph);
252 							} else {
253 								fRenderer.fSubpixRasterizer.add_path(
254 									fTransformedContour);
255 							}
256 						} else {
257 							if (fRenderer.fContour.width() == 0.0) {
258 								fRenderer.fRasterizer.add_path(
259 									fTransformedGlyph);
260 							} else {
261 								fRenderer.fRasterizer.add_path(
262 									fTransformedContour);
263 							}
264 						}
265 #if SHOW_GLYPH_BOUNDS
266 	agg::path_storage p;
267 	p.move_to(glyphBounds.left + 0.5, glyphBounds.top + 0.5);
268 	p.line_to(glyphBounds.right + 0.5, glyphBounds.top + 0.5);
269 	p.line_to(glyphBounds.right + 0.5, glyphBounds.bottom + 0.5);
270 	p.line_to(glyphBounds.left + 0.5, glyphBounds.bottom + 0.5);
271 	p.close_polygon();
272 	agg::conv_stroke<agg::path_storage> ps(p);
273 	ps.width(1.0);
274 	if (fSubpixelAntiAliased) {
275 		fRenderer.fSubpixRasterizer.add_path(ps);
276 	} else {
277 		fRenderer.fRasterizer.add_path(ps);
278 	}
279 #endif
280 
281 						break;
282 					}
283 					default:
284 						break;
285 				}
286 			}
287 		}
288 		return true;
289 	}
290 
291 	IntRect Bounds() const
292 	{
293 		return fBounds;
294 	}
295 
296 private:
297  	const Transformable& fTransform;
298 	const BPoint&		fTransformOffset;
299 	const IntRect&		fClippingFrame;
300 	bool				fDryRun;
301 	bool				fSubpixelAntiAliased;
302 	bool				fVector;
303 	IntRect				fBounds;
304 	BPoint*				fNextCharPos;
305 
306 	FontCacheEntry::TransformedOutline& fTransformedGlyph;
307 	FontCacheEntry::TransformedContourOutline& fTransformedContour;
308 	AGGTextRenderer&	fRenderer;
309 };
310 
311 
312 BRect
313 AGGTextRenderer::RenderString(const char* string, uint32 length,
314 	const BPoint& baseLine, const BRect& clippingFrame, bool dryRun,
315 	BPoint* nextCharPos, const escapement_delta* delta,
316 	FontCacheReference* cacheReference)
317 {
318 //printf("RenderString(\"%s\", length: %ld, dry: %d)\n", string, length, dryRun);
319 
320 	Transformable transform(fEmbeddedTransformation);
321 	transform.TranslateBy(baseLine);
322 
323 	fCurves.approximation_scale(transform.scale());
324 
325 	// use a transformation behind the curves
326 	// (only if glyph->data_type == agg::glyph_data_outline)
327 	// in the pipeline for the rasterizer
328 	FontCacheEntry::TransformedOutline
329 		transformedOutline(fCurves, transform);
330 	FontCacheEntry::TransformedContourOutline
331 		transformedContourOutline(fContour, transform);
332 
333 	// for when we bypass the transformation pipeline
334 	BPoint transformOffset(0.0, 0.0);
335 	transform.Transform(&transformOffset);
336 
337 	StringRenderer renderer(clippingFrame, dryRun,
338 		gSubpixelAntialiasing && fAntialias,
339 		transformedOutline, transformedContourOutline,
340 		transform, transformOffset, nextCharPos, *this);
341 
342 	GlyphLayoutEngine::LayoutGlyphs(renderer, fFont, string, length, delta,
343 		fKerning, B_BITMAP_SPACING, NULL, cacheReference);
344 
345 	return transform.TransformBounds(renderer.Bounds());
346 }
347 
348 
349 BRect
350 AGGTextRenderer::RenderString(const char* string, uint32 length,
351 	const BPoint* offsets, const BRect& clippingFrame, bool dryRun,
352 	BPoint* nextCharPos, FontCacheReference* cacheReference)
353 {
354 //printf("RenderString(\"%s\", length: %ld, dry: %d)\n", string, length, dryRun);
355 
356 	Transformable transform(fEmbeddedTransformation);
357 
358 	fCurves.approximation_scale(transform.scale());
359 
360 	// use a transformation behind the curves
361 	// (only if glyph->data_type == agg::glyph_data_outline)
362 	// in the pipeline for the rasterizer
363 	FontCacheEntry::TransformedOutline
364 		transformedOutline(fCurves, transform);
365 	FontCacheEntry::TransformedContourOutline
366 		transformedContourOutline(fContour, transform);
367 
368 	// for when we bypass the transformation pipeline
369 	BPoint transformOffset(0.0, 0.0);
370 	transform.Transform(&transformOffset);
371 
372 	StringRenderer renderer(clippingFrame, dryRun,
373 		gSubpixelAntialiasing && fAntialias,
374 		transformedOutline, transformedContourOutline,
375 		transform, transformOffset, nextCharPos, *this);
376 
377 	GlyphLayoutEngine::LayoutGlyphs(renderer, fFont, string, length, NULL,
378 		fKerning, B_BITMAP_SPACING, offsets, cacheReference);
379 
380 	return transform.TransformBounds(renderer.Bounds());
381 }
382