diff --git a/c++/freetype/src/freetypeview.cpp b/c++/freetype/src/freetypeview.cpp index a21067b..bf4941a 100644 --- a/c++/freetype/src/freetypeview.cpp +++ b/c++/freetype/src/freetypeview.cpp @@ -13,15 +13,21 @@ FreeTypeView::FreeTypeView(): - p0(new View::Point( Vector2(0, 0))), - px(new View::Point( Vector2(1, 0))), - py(new View::Point( Vector2(0, 1))) + p0(new View::Point( Vector2(0, 0) )), + px(new View::Point( Vector2(1, 0) )), + py(new View::Point( Vector2(0, 1) )), + bounds_p0(new View::Point( Vector2(-2, -2) )), + bounds_p1(new View::Point( Vector2( 2, 2) )) { prev_p0 = p0->position; transform *= Matrix().scaling(Vector2(50, 50)); points.push_back(p0); points.push_back(px); points.push_back(py); + points.push_back(bounds_p0); + points.push_back(bounds_p1); + + set_size_request(400, 400); params.family = "Ani"; params.bold = false; @@ -74,15 +80,31 @@ void FreeTypeView::update_surface() { this->surface.clear(); + Matrix to_pixels = transform_to_pixels(); + Matrix in_matrix( Vector3(px->position - p0->position)/50, Vector3(py->position - p0->position)/50, Vector3(p0->position, 1) ); params.matrix = transform_to_pixels() * in_matrix; + Pair2 bounds = to_pixels.transform_bounds( + Pair2(bounds_p0->position) + .expand(bounds_p1->position) ); + const int width = get_allocated_width(); const int height = get_allocated_height(); - if (width <= 0 || height <= 0) + bounds &= Pair2(Vector2(), Vector2(width, height)); + if (bounds.empty()) + return; + + IntPair2 int_bounds = IntPair2( IntVector2( (int)floor(bounds.p0.x + real_precision), + (int)floor(bounds.p0.y + real_precision) ), + IntVector2( (int)ceil(bounds.p1.x - real_precision), + (int)ceil(bounds.p1.y - real_precision) )) + & IntPair2( IntVector2(), + IntVector2(width, height) ); + if (int_bounds.empty()) return; const Vector2 spacing( @@ -98,11 +120,15 @@ FreeTypeView::update_surface() { matrix.row_x().vec2(), matrix.row_y().vec2() ); matrix *= Matrix().scaling(spacing); + Matrix bbox_check_matrix = + params.matrix + * Matrix().scaling( + Vector2( std::max(Real(1), spacing.x), + std::max(Real(1), spacing.y) )); if (fabs(glyph_matrix.det()) <= real_precision_sqr) return; - const Real size = params.size; const Real wrap_width = params.wrap_width; @@ -116,7 +142,6 @@ FreeTypeView::update_surface() { pango_font_description_set_weight(font_desc, params.bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL); pango_font_description_set_style(font_desc, params.italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL); pango_font_description_set_absolute_size(font_desc, size * PANGO_SCALE); - PangoFont *font = pango_context_load_font(context, font_desc); // create layout @@ -131,51 +156,75 @@ FreeTypeView::update_surface() { pango_layout_set_text(layout, params.text.c_str(), (int)params.text.size()); // move origin - PangoRectangle rect; - pango_layout_get_extents(layout, nullptr, &rect); + PangoRectangle ink_rect, rect; + pango_layout_get_extents(layout, &ink_rect, &rect); const Vector2 offset_to_origin( (rect.x + rect.width)/Real(PANGO_SCALE) * origin.x, (rect.y + rect.height)/Real(PANGO_SCALE) * origin.y ); matrix *= Matrix().translation(-offset_to_origin); + + bbox_check_matrix *= Matrix().translation(-offset_to_origin) + * Matrix().scaling(Vector2(Real(1)/PANGO_SCALE, Real(1)/PANGO_SCALE)); + const Pair2 dst_rect = bbox_check_matrix.transform_bounds( + Pair2( Vector2( ink_rect.x, ink_rect.y), + Vector2( ink_rect.x + ink_rect.width, + ink_rect.y + ink_rect.height) ).inflate(Vector2(2, 2)) ); - // create renderer - DataSurface surface(width, height); - LabPangoRendererParams rp; - rp.surface = &surface; - rp.matrix = matrix; - rp.glyph_matrix = glyph_matrix; - rp.color = params.color; - rp.hinting = params.hinting; - rp.antialiasing = params.antialiasing; - PangoRenderer *renderer = lab_pango_renderer_new(&rp); - - // render line by line - pango_renderer_activate(renderer); - PangoLayoutIter *iter = pango_layout_get_iter(layout); - do { - PangoRectangle line_rect; - pango_layout_iter_get_line_extents(iter, nullptr, &line_rect); - PangoLayoutLine *line = pango_layout_iter_get_line_readonly(iter); - int baseline = pango_layout_iter_get_baseline(iter); - int x = params.alignment_by_origin - ? (int)round((rect.x + rect.width - line_rect.width)*origin.x) - : line_rect.x; - pango_renderer_draw_layout_line(renderer, line, x, baseline); - } while(pango_layout_iter_next_line(iter)); - pango_layout_iter_free(iter); - pango_renderer_deactivate(renderer); + if (!(bounds & dst_rect).empty()) { + // create renderer + DataSurface surface(width, height); + LabPangoRendererParams rp; + rp.surface = &surface; + rp.bounds = IntPair2( IntVector2( (int)floor(bounds.p0.x + real_precision), + (int)floor(bounds.p0.y + real_precision) ), + IntVector2( (int)ceil(bounds.p1.x - real_precision), + (int)ceil(bounds.p1.y - real_precision) )) + & IntPair2( IntVector2(), + IntVector2(width, height) ); + rp.matrix = matrix; + rp.glyph_matrix = glyph_matrix; + rp.color = params.color; + rp.hinting = params.hinting; + rp.antialiasing = params.antialiasing; + PangoRenderer *renderer = lab_pango_renderer_new(&rp); + + // render line by line + pango_renderer_activate(renderer); + PangoLayoutIter *iter = pango_layout_get_iter(layout); + do { + PangoRectangle line_ink_rect, line_rect; + pango_layout_iter_get_line_extents(iter, &line_ink_rect, &line_rect); + int x = params.alignment_by_origin + ? (int)round((rect.x + rect.width - line_rect.width)*origin.x) + : line_rect.x; + int dx = x - line_rect.x; + + const Pair2 dst_line_rect = bbox_check_matrix.transform_bounds( + Pair2( Vector2( line_ink_rect.x + dx, + line_ink_rect.y ), + Vector2( line_ink_rect.x + dx + line_ink_rect.width, + line_ink_rect.y + line_ink_rect.height) ).inflate(Vector2(2, 2)) ); + + if (!(bounds & dst_line_rect).empty()) { + int baseline = pango_layout_iter_get_baseline(iter); + PangoLayoutLine *line = pango_layout_iter_get_line_readonly(iter); + pango_renderer_draw_layout_line(renderer, line, x, baseline); + } + } while(pango_layout_iter_next_line(iter)); + pango_layout_iter_free(iter); + pango_renderer_deactivate(renderer); + + g_object_unref(renderer); + + this->surface = surface.to_cairo_surface(true); + } // free pango - g_object_unref(renderer); pango_font_description_free(font_desc); g_object_unref(layout); g_object_unref(font); g_object_unref(context); g_object_unref(font_map); - - this->surface = surface.to_cairo_surface(true); - - queue_draw(); } void @@ -185,22 +234,34 @@ FreeTypeView::on_point_motion(const View::PointPtr &point) { py->position += p0->position - prev_p0; prev_p0 = p0->position; } - update_surface(); + queue_draw(); } void FreeTypeView::on_draw_view(const Cairo::RefPtr &context) { + update_surface(); + const Real ps = get_pixel_size(); context->save(); context->set_line_width(ps); + // draw transform context->set_source_rgba(0, 0, 1, 1); context->move_to(px->position.x, px->position.y); context->line_to(p0->position.x, p0->position.y); context->line_to(py->position.x, py->position.y); context->stroke(); + // draw bounds + context->set_source_rgba(0, 0, 1, 1); + context->move_to(bounds_p0->position.x, bounds_p0->position.y); + context->line_to(bounds_p1->position.x, bounds_p0->position.y); + context->line_to(bounds_p1->position.x, bounds_p1->position.y); + context->line_to(bounds_p0->position.x, bounds_p1->position.y); + context->close_path(); + context->stroke(); + if (surface) { context->save(); context->transform(transform_from_pixels().to_cairo()); diff --git a/c++/freetype/src/freetypeview.h b/c++/freetype/src/freetypeview.h index 9caac6e..6530e9e 100644 --- a/c++/freetype/src/freetypeview.h +++ b/c++/freetype/src/freetypeview.h @@ -41,7 +41,12 @@ public: { } }; - View::PointPtr p0, px, py; + View::PointPtr + p0, + px, + py, + bounds_p0, + bounds_p1; Vector2 prev_p0; Params params; diff --git a/c++/freetype/src/labpangorenderer.cpp b/c++/freetype/src/labpangorenderer.cpp index 09f9fdd..36e901f 100644 --- a/c++/freetype/src/labpangorenderer.cpp +++ b/c++/freetype/src/labpangorenderer.cpp @@ -26,62 +26,74 @@ struct _LabPangoRendererClass { G_DEFINE_TYPE(LabPangoRenderer, lab_pango_renderer, PANGO_TYPE_RENDERER) +static inline int floor_ft_int(int x) { return (x & -64)/64; } +static inline int ceil_ft_int(int x) { return ((x-1) & -64)/64 + 1; } + static void -put_ft_bitmap(Surface &surface, FT_Bitmap &bitmap, int x, int y, const Color &color) { - if (x >= surface.width() || y >= surface.height()) - return; - if (x + (int)bitmap.width <= 0 || y + (int)bitmap.rows <= 0) +put_ft_bitmap( + Surface &surface, + const IntPair2 &bounds, + const FT_Bitmap &bitmap, + int x, + int y, + const Color &color ) +{ + const IntPair2 b = bounds + & IntPair2( IntVector2(0, 0), IntVector2(surface.width(), surface.height()) ) + & IntPair2( IntVector2(x, y), IntVector2(x + bitmap.width, y + bitmap.rows) ); + if (b.empty()) return; - const int bx = std::max(0, -x); - const int by = std::max(0, -y); - x = std::max(0, x); - y = std::max(0, y); - const int w = std::min(surface.width() - x, (int)bitmap.width - bx); - const int h = std::min(surface.height() - y, (int)bitmap.rows - by); + const IntVector2 size = b.size(); + const IntVector2 sp0 = b.p0 - IntVector2(x, y); const int pitch = surface.pitch(); - Color *dst_pixel = &surface[y][x]; - const int dst_row_step = pitch - w; + Color *dp = &surface[b.p0.y][b.p0.x]; + const int dstep = pitch - size.x; - const unsigned char *src_pixel = bitmap.buffer + by*bitmap.pitch + bx; - const int src_row_step = bitmap.pitch - w; + const unsigned char *sp = bitmap.buffer + sp0.y*bitmap.pitch + sp0.x; + const int sstep = bitmap.pitch - size.x; - for(const Color *end = dst_pixel + pitch*h; dst_pixel != end; dst_pixel += dst_row_step, src_pixel += src_row_step) { - for(const Color *row_end = dst_pixel + w; dst_pixel != row_end; ++dst_pixel, ++src_pixel) { - Real a = *src_pixel * (1/255.0); - *dst_pixel = *dst_pixel*(1-a) + color*a; + for(const Color *end = dp + pitch*size.y; dp != end; dp += dstep, sp += sstep) { + for(const Color *row_end = dp + size.x; dp < row_end; ++dp, ++sp) { + const Real a = *sp * (1/255.0); + *dp = *dp*(1-a) + color*a; } } } static void -put_ft_bitmap_mono(Surface &surface, FT_Bitmap &bitmap, int x, int y, const Color &color) { - if (x >= surface.width() || y >= surface.height()) - return; - if (x + (int)bitmap.width <= 0 || y + (int)bitmap.rows <= 0) +put_ft_bitmap_mono( + Surface &surface, + const IntPair2 &bounds, + const FT_Bitmap &bitmap, + int x, + int y, + const Color &color ) +{ + const IntPair2 b = bounds + & IntPair2( IntVector2(0, 0), IntVector2(surface.width(), surface.height()) ) + & IntPair2( IntVector2(x, y), IntVector2(bitmap.width, bitmap.rows) ); + if (b.empty()) return; - const int bx = std::max(0, -x); - const int by = std::max(0, -y); - x = std::max(0, x); - y = std::max(0, y); - const int w = std::min(surface.width() - x, (int)bitmap.width - bx); - const int h = std::min(surface.height() - y, (int)bitmap.rows - by); + const IntVector2 size = b.size(); + const IntVector2 sp0 = b.p0 - IntVector2(x, y); const int pitch = surface.pitch(); - Color *dst_pixel = &surface[y][x]; - const int dst_row_step = pitch - w; + Color *dp = &surface[b.p0.y][b.p0.x]; + const int dstep = pitch - size.x; - const unsigned char *src_row = bitmap.buffer + by*bitmap.pitch; - const int src_row_end = bx + w; + const unsigned char *sp = bitmap.buffer + sp0.y*bitmap.pitch; + const int sstep = bitmap.pitch; + const int sp1x = sp0.x + size.x; - for(const Color *end = dst_pixel + pitch*h; dst_pixel != end; dst_pixel += dst_row_step, src_row += bitmap.pitch) - for(int src_x = bx; src_x < src_row_end; ++dst_pixel, ++src_x) - if (src_row[src_x/8] & (128 >> (src_x%8))) - *dst_pixel = color; + for(const Color *end = dp + pitch*size.y; dp != end; dp += dstep, sp += sstep) + for(int bit = sp0.x; bit < sp1x; ++dp, ++bit) + if (sp[bit/8] & (128 >> (bit%8))) + *dp = color; } @@ -96,8 +108,11 @@ lab_pango_renderer_draw_glyph( const LabPangoRendererParams ¶ms = ((LabPangoRenderer*)renderer)->params; if (!params.surface) return; - - Vector3 pos = params.matrix*Vector3(x, y, 1); + + const IntPair2 surface_bounds( + IntVector2(0, 0), + IntVector2(params.surface->width(), params.surface->height()) ); + const Vector3 pos = params.matrix*Vector3(x, y, 1); const int fx = (int)round(pos.x*64); const int fy = (int)round(pos.y*64); @@ -125,21 +140,33 @@ lab_pango_renderer_draw_glyph( FT_Face face = pango_fc_font_lock_face((PangoFcFont*)font); FT_Set_Transform(face, &mat, &vec); if (!FT_Load_Glyph(face, glyph, params.hinting ? FT_LOAD_DEFAULT : FT_LOAD_NO_HINTING)) { - if (!FT_Render_Glyph(face->glyph, params.antialiasing ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO)) { - if (params.antialiasing) { - put_ft_bitmap( - *params.surface, - face->glyph->bitmap, - face->glyph->bitmap_left + ix, - iy - face->glyph->bitmap_top, - params.color ); - } else { - put_ft_bitmap_mono( - *params.surface, - face->glyph->bitmap, - face->glyph->bitmap_left + ix, - iy - face->glyph->bitmap_top, - params.color ); + const FT_Glyph_Metrics &metrics = face->glyph->metrics; + IntPair2 glyph_bounds( + IntVector2( + floor_ft_int( metrics.horiBearingX) + ix, + floor_ft_int(-metrics.horiBearingY) + iy ), + IntVector2( + ceil_ft_int( metrics.horiBearingX + metrics.width ) + ix, + ceil_ft_int(-metrics.horiBearingY + metrics.height) + iy )); + if (!(params.bounds & glyph_bounds).empty()) { + if (!FT_Render_Glyph(face->glyph, params.antialiasing ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO)) { + if (params.antialiasing) { + put_ft_bitmap( + *params.surface, + surface_bounds, + face->glyph->bitmap, + face->glyph->bitmap_left + ix, + iy - face->glyph->bitmap_top, + params.color ); + } else { + put_ft_bitmap_mono( + *params.surface, + surface_bounds, + face->glyph->bitmap, + face->glyph->bitmap_left + ix, + iy - face->glyph->bitmap_top, + params.color ); + } } } } diff --git a/c++/freetype/src/labpangorenderer.h b/c++/freetype/src/labpangorenderer.h index 5887de7..7f5b40c 100644 --- a/c++/freetype/src/labpangorenderer.h +++ b/c++/freetype/src/labpangorenderer.h @@ -24,6 +24,7 @@ typedef struct _LabPangoRendererClass LabPangoRendererClass; struct LabPangoRendererParams { Surface *surface; + IntPair2 bounds; Matrix3 matrix; Matrix2 glyph_matrix; Color color; diff --git a/c++/freetype/src/matrix.h b/c++/freetype/src/matrix.h index 942b0b3..1b6ceb1 100644 --- a/c++/freetype/src/matrix.h +++ b/c++/freetype/src/matrix.h @@ -27,8 +27,34 @@ public: m10(y.x), m11(y.y) { } + inline Vector2& operator[] (int index) { return Vector2::cast(m[index]); } + inline const Vector2& operator[] (int index) const { return Vector2::cast(m[index]); } + + inline Vector2& row_x() { return (*this)[0]; } + inline Vector2& row_y() { return (*this)[1]; } + + inline const Vector2& row_x() const { return (*this)[0]; } + inline const Vector2& row_y() const { return (*this)[1]; } + inline Real det() const { return m00*m11 - m01*m10; } + + inline Vector2 operator* (const Vector2 &v) const + { return row_x()*v.x + row_y()*v.y; } + inline Matrix2 operator* (const Matrix2 &other) const { + return Matrix2( *this * other.row_x(), + *this * other.row_y() ); + } + inline Matrix2& operator*= (const Matrix2 &other) + { return *this = *this * other; } + + Pair2 transform_bounds(const Pair2 &bounds) const { + return bounds.empty() ? Pair2() + : Pair2( *this*Vector2(bounds.p0.x, bounds.p0.y) ) + .expand( *this*Vector2(bounds.p0.x, bounds.p1.y) ) + .expand( *this*Vector2(bounds.p1.x, bounds.p0.y) ) + .expand( *this*Vector2(bounds.p1.x, bounds.p1.y) ); + } }; @@ -111,6 +137,14 @@ public: m00, m10, m01, m11, m20, m21 ); } + + Pair2 transform_bounds(const Pair2 &bounds) const { + return bounds.empty() ? Pair2() + : Pair2( (*this*Vector3(bounds.p0.x, bounds.p0.y, 1)).vec2() ) + .expand( (*this*Vector3(bounds.p0.x, bounds.p1.y, 1)).vec2() ) + .expand( (*this*Vector3(bounds.p1.x, bounds.p0.y, 1)).vec2() ) + .expand( (*this*Vector3(bounds.p1.x, bounds.p1.y, 1)).vec2() ); + } }; typedef Matrix3 Matrix; diff --git a/c++/freetype/src/vector.h b/c++/freetype/src/vector.h index 16e70b0..1161516 100644 --- a/c++/freetype/src/vector.h +++ b/c++/freetype/src/vector.h @@ -292,6 +292,31 @@ public: inline bool operator!= (const PairT &other) const { return *this < other || other < *this; } + inline PairT& operator&= (const PairT &other) { + if (empty() || other.empty()) + return *this = PairT(); + for(int i = 0; i < Vector::Count; ++i) { + if (p0[i] < other.p0[i]) p0[i] = other.p0[i]; + if (other.p1[i] < p1[i]) p1[i] = other.p1[i]; + } + return *this; + } + inline PairT& operator|= (const PairT &other) { + if (other.empty()) + return *this; + if (empty()) + return *this = other; + for(int i = 0; i < Vector::Count; ++i) { + if (other.p0[i] < p0[i]) p0[i] = other.p0[i]; + if (p1[i] < other.p1[i]) p1[i] = other.p1[i]; + } + return *this; + } + + inline PairT operator& (const PairT &other) const + { return PairT(*this) &= other; } + inline PairT operator| (const PairT &other) const + { return PairT(*this) |= other; } inline Vector distance() const { return p1 - p0; }