/* * Copyright 2005-2007, Stephan Aßmus . All rights reserved. * Distributed under the terms of the MIT License. */ #include "AGGTextRenderer.h" #include #include #include #include #include #include #include #include #include #define SHOW_GLYPH_BOUNDS 0 #if SHOW_GLYPH_BOUNDS # include # include #endif #include "GlyphLayoutEngine.h" #include "IntRect.h" // constructor AGGTextRenderer::AGGTextRenderer(renderer_type& solidRenderer, renderer_bin_type& binRenderer, scanline_unpacked_type& scanline) : fPathAdaptor() , fGray8Adaptor() , fGray8Scanline() , fMonoAdaptor() , fMonoScanline() , fCurves(fPathAdaptor) , fContour(fCurves) , fSolidRenderer(solidRenderer) , fBinRenderer(binRenderer) , fScanline(scanline) , fRasterizer() , fHinted(true) , fAntialias(true) , fKerning(true) , fEmbeddedTransformation() { fCurves.approximation_scale(2.0); fContour.auto_detect_orientation(false); } // destructor AGGTextRenderer::~AGGTextRenderer() { } // SetFont void AGGTextRenderer::SetFont(const ServerFont &font) { fFont = font; // construct an embedded transformation (rotate & shear) fEmbeddedTransformation.Reset(); fEmbeddedTransformation.ShearBy(B_ORIGIN, (90.0 - font.Shear()) * PI / 180.0, 0.0); fEmbeddedTransformation.RotateBy(B_ORIGIN, -font.Rotation() * PI / 180.0); fContour.width(font.FalseBoldWidth() * 2.0); } // SetHinting void AGGTextRenderer::SetHinting(bool hinting) { fHinted = hinting; // fFontEngine.hinting(fEmbeddedTransformation.IsIdentity() && fHinted); } // SetAntialiasing void AGGTextRenderer::SetAntialiasing(bool antialiasing) { if (fAntialias != antialiasing) { fAntialias = antialiasing; if (!fAntialias) fRasterizer.gamma(agg::gamma_threshold(0.5)); else fRasterizer.gamma(agg::gamma_power(1.0)); } } typedef agg::conv_transform conv_font_trans_type; typedef agg::conv_transform conv_font_contour_trans_type; class AGGTextRenderer::StringRenderer { public: StringRenderer(const IntRect& clippingFrame, bool dryRun, FontCacheEntry::TransformedOutline& transformedGlyph, FontCacheEntry::TransformedContourOutline& transformedContour, const Transformable& transform, const BPoint& transformOffset, BPoint* nextCharPos, AGGTextRenderer& renderer) : fTransform(transform) , fTransformOffset(transformOffset) , fClippingFrame(clippingFrame) , fDryRun(dryRun) , fBounds(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN) , fNextCharPos(nextCharPos) , fVector(false) , fTransformedGlyph(transformedGlyph) , fTransformedContour(transformedContour) , fRenderer(renderer) { } void Start() { fRenderer.fRasterizer.reset(); } void Finish(double x, double y) { if (fVector) { agg::render_scanlines(fRenderer.fRasterizer, fRenderer.fScanline, fRenderer.fSolidRenderer); } if (fNextCharPos) { fNextCharPos->x = x; fNextCharPos->y = y; fTransform.Transform(fNextCharPos); } } void ConsumeEmptyGlyph(int32 index, uint32 charCode, double x, double y) { } bool ConsumeGlyph(int32 index, uint32 charCode, const GlyphCache* glyph, FontCacheEntry* entry, double x, double y) { // "glyphBounds" is the bounds of the glyph transformed // by the x y location of the glyph along the base line, // it is therefor yet "untransformed" in case there is an // embedded transformation. const agg::rect_i& r = glyph->bounds; IntRect glyphBounds(r.x1 + x, r.y1 + y - 1, r.x2 + x + 1, r.y2 + y + 1); // NOTE: "-1"/"+1" converts the glyph bounding box from pixel // indices to pixel area coordinates // track bounding box if (glyphBounds.IsValid()) fBounds = fBounds | glyphBounds; // render the glyph if this is not a dry run if (!fDryRun) { // init the fontmanager's embedded adaptors // NOTE: The initialization for the "location" of // the glyph is different depending on wether we // deal with non-(rotated/sheared) text, in which // case we have a native FT bitmap. For rotated or // sheared text, we use AGG vector outlines and // a transformation pipeline, which will be applied // _after_ we retrieve the outline, and that's why // we simply pass x and y, which are untransformed. // "glyphBounds" is now transformed into screen coords // in order to stop drawing when we are already outside // of the clipping frame if (glyph->data_type != glyph_data_outline) { // we cannot use the transformation pipeline double transformedX = x + fTransformOffset.x; double transformedY = y + fTransformOffset.y; entry->InitAdaptors(glyph, transformedX, transformedY, fRenderer.fMonoAdaptor, fRenderer.fGray8Adaptor, fRenderer.fPathAdaptor); glyphBounds.OffsetBy(fTransformOffset); } else { entry->InitAdaptors(glyph, x, y, fRenderer.fMonoAdaptor, fRenderer.fGray8Adaptor, fRenderer.fPathAdaptor); float falseBoldWidth = fRenderer.fContour.width(); if (falseBoldWidth != 0.0) glyphBounds.InsetBy(-falseBoldWidth, -falseBoldWidth); // TODO: not correct! this is later used for clipping, // but it doesn't get the rect right glyphBounds = fTransform.TransformBounds(glyphBounds); } if (fClippingFrame.Intersects(glyphBounds)) { switch (glyph->data_type) { case glyph_data_mono: agg::render_scanlines(fRenderer.fMonoAdaptor, fRenderer.fMonoScanline, fRenderer.fBinRenderer); break; case glyph_data_gray8: agg::render_scanlines(fRenderer.fGray8Adaptor, fRenderer.fGray8Scanline, fRenderer.fSolidRenderer); break; case glyph_data_outline: { fVector = true; if (fRenderer.fContour.width() == 0.0) { fRenderer.fRasterizer.add_path(fTransformedGlyph); } else { fRenderer.fRasterizer.add_path(fTransformedContour); } #if SHOW_GLYPH_BOUNDS agg::path_storage p; p.move_to(glyphBounds.left + 0.5, glyphBounds.top + 0.5); p.line_to(glyphBounds.right + 0.5, glyphBounds.top + 0.5); p.line_to(glyphBounds.right + 0.5, glyphBounds.bottom + 0.5); p.line_to(glyphBounds.left + 0.5, glyphBounds.bottom + 0.5); p.close_polygon(); agg::conv_stroke ps(p); ps.width(1.0); fRenderer.fRasterizer.add_path(ps); #endif break; } default: break; } } } return true; } IntRect Bounds() const { return fBounds; } private: const Transformable& fTransform; const BPoint& fTransformOffset; const IntRect& fClippingFrame; bool fDryRun; IntRect fBounds; BPoint* fNextCharPos; bool fVector; FontCacheEntry::TransformedOutline& fTransformedGlyph; FontCacheEntry::TransformedContourOutline& fTransformedContour; AGGTextRenderer& fRenderer; }; // RenderString BRect AGGTextRenderer::RenderString(const char* string, uint32 length, const BPoint& baseLine, const BRect& clippingFrame, bool dryRun, BPoint* nextCharPos, const escapement_delta* delta, FontCacheReference* cacheReference) { //printf("RenderString(\"%s\", length: %ld, dry: %d)\n", string, length, dryRun); Transformable transform(fEmbeddedTransformation); transform.TranslateBy(baseLine); fCurves.approximation_scale(transform.scale()); // use a transformation behind the curves // (only if glyph->data_type == agg::glyph_data_outline) // in the pipeline for the rasterizer FontCacheEntry::TransformedOutline transformedOutline(fCurves, transform); FontCacheEntry::TransformedContourOutline transformedContourOutline(fContour, transform); // for when we bypass the transformation pipeline BPoint transformOffset(0.0, 0.0); transform.Transform(&transformOffset); StringRenderer renderer(clippingFrame, dryRun, transformedOutline, transformedContourOutline, transform, transformOffset, nextCharPos, *this); GlyphLayoutEngine::LayoutGlyphs(renderer, fFont, string, length, delta, fKerning, B_BITMAP_SPACING, cacheReference); return transform.TransformBounds(renderer.Bounds()); }