xref: /haiku/src/servers/app/drawing/Painter/AGGTextRenderer.cpp (revision adb0d19d561947362090081e81d90dde59142026)
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()) * PI / 180.0, 0.0);
80 	fEmbeddedTransformation.RotateBy(B_ORIGIN,
81 		-font.Rotation() * 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 //	fFontEngine.hinting(fEmbeddedTransformation.IsIdentity() && fHinted);
92 }
93 
94 
95 void
96 AGGTextRenderer::SetAntialiasing(bool antialiasing)
97 {
98 	if (fAntialias != antialiasing) {
99 		fAntialias = antialiasing;
100 		if (!fAntialias)
101 			fRasterizer.gamma(agg::gamma_threshold(0.5));
102 		else
103 			fRasterizer.gamma(agg::gamma_power(1.0));
104 	}
105 }
106 
107 
108 typedef agg::conv_transform<FontCacheEntry::CurveConverter, Transformable>
109 	conv_font_trans_type;
110 
111 typedef agg::conv_transform<FontCacheEntry::ContourConverter, Transformable>
112 	conv_font_contour_trans_type;
113 
114 
115 
116 class AGGTextRenderer::StringRenderer {
117 public:
118 	StringRenderer(const IntRect& clippingFrame, bool dryRun,
119 			FontCacheEntry::TransformedOutline& transformedGlyph,
120 			FontCacheEntry::TransformedContourOutline& transformedContour,
121 			const Transformable& transform,
122 			const BPoint& transformOffset,
123 			BPoint* nextCharPos,
124 			AGGTextRenderer& renderer)
125 		:
126 		fTransform(transform),
127 		fTransformOffset(transformOffset),
128 		fClippingFrame(clippingFrame),
129 		fDryRun(dryRun),
130 		fBounds(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN),
131 		fNextCharPos(nextCharPos),
132 		fVector(false),
133 
134 		fTransformedGlyph(transformedGlyph),
135 		fTransformedContour(transformedContour),
136 
137 		fRenderer(renderer)
138 	{
139 	}
140 
141 	void Start()
142 	{
143 		fRenderer.fRasterizer.reset();
144 		fRenderer.fSubpixRasterizer.reset();
145 	}
146 	void Finish(double x, double y)
147 	{
148 		if (fVector) {
149 			if (gSubpixelAntialiasing) {
150 				agg::render_scanlines(fRenderer.fSubpixRasterizer,
151 					fRenderer.fSubpixScanline, fRenderer.fSubpixRenderer);
152 			} else {
153 				agg::render_scanlines(fRenderer.fRasterizer,
154 					fRenderer.fScanline, fRenderer.fSolidRenderer);
155 			}
156 		}
157 
158 		if (fNextCharPos) {
159 			fNextCharPos->x = x;
160 			fNextCharPos->y = y;
161 			fTransform.Transform(fNextCharPos);
162 		}
163 	}
164 
165 	void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y)
166 	{
167 	}
168 
169 	bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph,
170 		FontCacheEntry* entry, double x, double y)
171 	{
172 		// "glyphBounds" is the bounds of the glyph transformed
173 		// by the x y location of the glyph along the base line,
174 		// it is therefor yet "untransformed" in case there is an
175 		// embedded transformation.
176 		const agg::rect_i& r = glyph->bounds;
177 		IntRect glyphBounds(int32(r.x1 + x), int32(r.y1 + y - 1),
178 			int32(r.x2 + x + 1), int32(r.y2 + y + 1));
179 			// NOTE: "-1"/"+1" converts the glyph bounding box from pixel
180 			// indices to pixel area coordinates
181 
182 		// track bounding box
183 		if (glyphBounds.IsValid())
184 			fBounds = fBounds | glyphBounds;
185 
186 		// render the glyph if this is not a dry run
187 		if (!fDryRun) {
188 			// init the fontmanager's embedded adaptors
189 			// NOTE: The initialization for the "location" of
190 			// the glyph is different depending on whether we
191 			// deal with non-(rotated/sheared) text, in which
192 			// case we have a native FT bitmap. For rotated or
193 			// sheared text, we use AGG vector outlines and
194 			// a transformation pipeline, which will be applied
195 			// _after_ we retrieve the outline, and that's why
196 			// we simply pass x and y, which are untransformed.
197 
198 			// "glyphBounds" is now transformed into screen coords
199 			// in order to stop drawing when we are already outside
200 			// of the clipping frame
201 			if (glyph->data_type != glyph_data_outline) {
202 				// we cannot use the transformation pipeline
203 				double transformedX = x + fTransformOffset.x;
204 				double transformedY = y + fTransformOffset.y;
205 				entry->InitAdaptors(glyph, transformedX, transformedY,
206 					fRenderer.fMonoAdaptor,
207 					fRenderer.fGray8Adaptor,
208 					fRenderer.fPathAdaptor);
209 
210 				glyphBounds.OffsetBy(fTransformOffset);
211 			} else {
212 				entry->InitAdaptors(glyph, x, y,
213 					fRenderer.fMonoAdaptor,
214 					fRenderer.fGray8Adaptor,
215 					fRenderer.fPathAdaptor);
216 
217 				int32 falseBoldWidth = (int32)fRenderer.fContour.width();
218 				if (falseBoldWidth != 0)
219 					glyphBounds.InsetBy(-falseBoldWidth, -falseBoldWidth);
220 				// TODO: not correct! this is later used for clipping,
221 				// but it doesn't get the rect right
222 				glyphBounds = fTransform.TransformBounds(glyphBounds);
223 			}
224 
225 			if (fClippingFrame.Intersects(glyphBounds)) {
226 				switch (glyph->data_type) {
227 					case glyph_data_mono:
228 						agg::render_scanlines(fRenderer.fMonoAdaptor,
229 							fRenderer.fMonoScanline, fRenderer.fBinRenderer);
230 						break;
231 
232 					case glyph_data_gray8:
233 						agg::render_scanlines(fRenderer.fGray8Adaptor,
234 							fRenderer.fGray8Scanline, fRenderer.fSolidRenderer);
235 						break;
236 
237 					case glyph_data_subpix:
238 						agg::render_scanlines(fRenderer.fGray8Adaptor,
239 							fRenderer.fGray8Scanline,
240 							fRenderer.fSubpixRenderer);
241 						break;
242 
243 					case glyph_data_outline: {
244 						fVector = true;
245 						if (gSubpixelAntialiasing) {
246 							if (fRenderer.fContour.width() == 0.0) {
247 								fRenderer.fSubpixRasterizer.add_path(
248 									fTransformedGlyph);
249 							} else {
250 								fRenderer.fSubpixRasterizer.add_path(
251 									fTransformedContour);
252 							}
253 						} else {
254 							if (fRenderer.fContour.width() == 0.0) {
255 								fRenderer.fRasterizer.add_path(
256 									fTransformedGlyph);
257 							} else {
258 								fRenderer.fRasterizer.add_path(
259 									fTransformedContour);
260 							}
261 						}
262 #if SHOW_GLYPH_BOUNDS
263 	agg::path_storage p;
264 	p.move_to(glyphBounds.left + 0.5, glyphBounds.top + 0.5);
265 	p.line_to(glyphBounds.right + 0.5, glyphBounds.top + 0.5);
266 	p.line_to(glyphBounds.right + 0.5, glyphBounds.bottom + 0.5);
267 	p.line_to(glyphBounds.left + 0.5, glyphBounds.bottom + 0.5);
268 	p.close_polygon();
269 	agg::conv_stroke<agg::path_storage> ps(p);
270 	ps.width(1.0);
271 	if (gSubpixelAntialiasing) {
272 		fRenderer.fSubpixRasterizer.add_path(ps);
273 	} else {
274 		fRenderer.fRasterizer.add_path(ps);
275 	}
276 #endif
277 
278 						break;
279 					}
280 					default:
281 						break;
282 				}
283 			}
284 		}
285 		return true;
286 	}
287 
288 	IntRect Bounds() const
289 	{
290 		return fBounds;
291 	}
292 
293 private:
294  	const Transformable& fTransform;
295 	const BPoint&		fTransformOffset;
296 	const IntRect&		fClippingFrame;
297 	bool				fDryRun;
298 	IntRect				fBounds;
299 	BPoint*				fNextCharPos;
300 	bool				fVector;
301 
302 	FontCacheEntry::TransformedOutline& fTransformedGlyph;
303 	FontCacheEntry::TransformedContourOutline& fTransformedContour;
304 	AGGTextRenderer&	fRenderer;
305 };
306 
307 // RenderString
308 BRect
309 AGGTextRenderer::RenderString(const char* string, uint32 length,
310 	const BPoint& baseLine, const BRect& clippingFrame, bool dryRun,
311 	BPoint* nextCharPos, const escapement_delta* delta,
312 	FontCacheReference* cacheReference)
313 {
314 //printf("RenderString(\"%s\", length: %ld, dry: %d)\n", string, length, dryRun);
315 
316 	Transformable transform(fEmbeddedTransformation);
317 	transform.TranslateBy(baseLine);
318 
319 	fCurves.approximation_scale(transform.scale());
320 
321 	// use a transformation behind the curves
322 	// (only if glyph->data_type == agg::glyph_data_outline)
323 	// in the pipeline for the rasterizer
324 	FontCacheEntry::TransformedOutline
325 		transformedOutline(fCurves, transform);
326 	FontCacheEntry::TransformedContourOutline
327 		transformedContourOutline(fContour, transform);
328 
329 	// for when we bypass the transformation pipeline
330 	BPoint transformOffset(0.0, 0.0);
331 	transform.Transform(&transformOffset);
332 
333 	StringRenderer renderer(clippingFrame, dryRun,
334 		transformedOutline, transformedContourOutline,
335 		transform, transformOffset, nextCharPos, *this);
336 
337 	GlyphLayoutEngine::LayoutGlyphs(renderer, fFont, string, length, delta,
338 		fKerning, B_BITMAP_SPACING, cacheReference);
339 
340 	return transform.TransformBounds(renderer.Bounds());
341 }
342