From f4d7d6f4070b24394a624c56461c1f4885e95c2a Mon Sep 17 00:00:00 2001 From: Ivan Mahonin Date: Mar 11 2020 07:34:21 +0000 Subject: freetype: advanced layout --- diff --git a/c++/freetype/src/SConstruct b/c++/freetype/src/SConstruct index 65b3dfd..6a259aa 100644 --- a/c++/freetype/src/SConstruct +++ b/c++/freetype/src/SConstruct @@ -19,7 +19,8 @@ target = 'freetype' sources = [ 'main.cpp', 'freetypeview.cpp', - 'labpangorenderer.cpp', + 'layout.cpp', + 'layoutdraw.cpp', 'matrix.cpp', 'surface.cpp', 'view.cpp' ] diff --git a/c++/freetype/src/common.h b/c++/freetype/src/common.h index b359255..ca0c87f 100644 --- a/c++/freetype/src/common.h +++ b/c++/freetype/src/common.h @@ -21,6 +21,7 @@ using Glib::RefPtr; const Real real_precision = 1e-10; const Real real_precision_sqr = real_precision * real_precision; +const Real real_precision_cub = real_precision_sqr * real_precision; class Shared { diff --git a/c++/freetype/src/freetypeview.cpp b/c++/freetype/src/freetypeview.cpp index db5c464..c5adaac 100644 --- a/c++/freetype/src/freetypeview.cpp +++ b/c++/freetype/src/freetypeview.cpp @@ -4,7 +4,8 @@ #include -#include "labpangorenderer.h" +#include "layout.h" +#include "layoutdraw.h" #include "surface.h" #include "log.h" @@ -48,7 +49,7 @@ FreeTypeView::FreeTypeView(): set_size_request(400, 400); - params.family = "DjVu"; + params.family = "Ani"; params.bold = false; params.italic = false; params.size = 12; @@ -57,7 +58,7 @@ FreeTypeView::FreeTypeView(): params.hinting = false; params.antialiasing = true; - params.invert = true; + params.invert = false; params.matrix = Matrix().translation(Vector2(15.5, 18.4)); params.color = Color(1, 1, 0, 1); @@ -100,155 +101,97 @@ void FreeTypeView::update_surface() { this->surface.clear(); + // prepare matrix 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; - + if (fabs(params.matrix.det()) <= real_precision_cub) + return; + + // calc bounds + const int width = get_allocated_width(); + const int height = get_allocated_height(); 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(); 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) )) + 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; - // prepare surface - DataSurface surface(width, height); - if (params.invert) - fill_rect(surface, int_bounds, params.color.get_mult_alpha()); - - const Vector2 spacing( - params.spacing.x > real_precision ? params.spacing.x : 1, - params.spacing.y > real_precision ? params.spacing.y : 1 ); - + // calc some measures const Vector2 origin( clamp(params.origin.x, 0, 1), clamp(params.origin.y, 0, 1) ); - - Matrix matrix = params.matrix; - Matrix2 glyph_matrix( - 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) + const Vector2 spacing( + params.spacing.x > real_precision ? params.spacing.x : 1, + params.spacing.y > real_precision ? params.spacing.y : 1 ); + const Matrix2 spacing_matrix( + Vector2(spacing.x, 0), + Vector2(0, spacing.y) ); + if (fabs(spacing_matrix.det()) <= real_precision_sqr) return; - - const Real size = params.size; - const Real wrap_width = params.wrap_width; // init - PangoFontMap *font_map = lab_pango_font_map_new(params.hinting, params.antialiasing); - PangoContext *context = pango_font_map_create_context(font_map); + Glib::RefPtr font_map = Layout::create_fontmap(params.hinting, params.antialiasing); + Glib::RefPtr context = font_map->create_context(); // load font - PangoFontDescription *font_desc = pango_font_description_new(); - pango_font_description_set_family(font_desc, params.family.c_str()); - 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 - PangoLayout *layout = pango_layout_new(context); - pango_layout_set_font_description(layout, font_desc); - pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); - pango_layout_set_width(layout, wrap_width > real_precision ? (int)round(wrap_width *PANGO_SCALE) : -1); - pango_layout_set_alignment(layout, - params.alignment < 0 ? PANGO_ALIGN_LEFT : - params.alignment > 0 ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_CENTER ); - pango_layout_set_justify(layout, params.justify); - pango_layout_set_text(layout, params.text.c_str(), (int)params.text.size()); - - // move origin - 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); + Pango::FontDescription font_desc; + font_desc.set_family(params.family); + font_desc.set_weight(params.bold ? Pango::WEIGHT_BOLD : Pango::WEIGHT_NORMAL); + font_desc.set_style(params.italic ? Pango::STYLE_ITALIC : Pango::STYLE_NORMAL); + font_desc.set_absolute_size(params.size * Pango::SCALE); + Glib::RefPtr font = context->load_font(font_desc); + + // create pango layout + Glib::RefPtr pango_layout = Pango::Layout::create(context); + pango_layout->set_font_description(font_desc); + pango_layout->set_wrap(Pango::WRAP_WORD_CHAR); + pango_layout->set_width( + params.wrap_width > real_precision ? (int)round(params.wrap_width*Pango::SCALE) : -1 ); + pango_layout->set_alignment( + params.alignment < 0 ? Pango::ALIGN_LEFT : + params.alignment > 0 ? Pango::ALIGN_RIGHT : Pango::ALIGN_CENTER ); + pango_layout->set_justify(params.justify); + pango_layout->set_text(params.text); + + // create lab layout + RefPtr layout(new Layout(pango_layout)); + const Pair2 &logical_bounds = layout->get_logical_bounds(); + const Vector2 layout_position( + logical_bounds.p1.x * origin.x, + logical_bounds.p1.y * origin.y ); + RefPtr transformed_layout( + new TransformedLayout(layout, spacing_matrix, Matrix2()) ); - 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)) ); - - if (!(bounds & dst_rect).empty()) { - // create renderer - 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; - rp.eraze = params.invert; - 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); - } - - // free pango - pango_font_description_free(font_desc); - g_object_unref(layout); - g_object_unref(font); - g_object_unref(context); - g_object_unref(font_map); + // prepare surface + DataSurface surface(width, height); + if (params.invert) + fill_rect(surface, int_bounds, params.color.get_mult_alpha()); + // draw + LayoutDraw::draw( + surface, + int_bounds, + transformed_layout, + params.matrix, + params.color, + params.invert, + -layout_position, + params.alignment_by_origin ? &origin.x : nullptr ); + + // to cairo this->surface = surface.to_cairo_surface(true); } diff --git a/c++/freetype/src/labpangorenderer.cpp b/c++/freetype/src/labpangorenderer.cpp deleted file mode 100644 index f5d3f83..0000000 --- a/c++/freetype/src/labpangorenderer.cpp +++ /dev/null @@ -1,218 +0,0 @@ - -#include - -#include -#include FT_BITMAP_H - -#include - - -#include "surface.h" -#include "matrix.h" - -#include "labpangorenderer.h" - - - -struct _LabPangoRenderer { - PangoRenderer parent_instance; - LabPangoRendererParams params; -}; - -struct _LabPangoRendererClass { - PangoRendererClass parent_class; -}; - -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; } - - -template -static void -put_ft_bitmap( - Surface &surface, - const IntPair2 &bounds, - const FT_Bitmap &bitmap, - const IntVector2 &offset, - const Color &color ) -{ - const IntPair2 b = bounds - & IntPair2( IntVector2(0, 0), IntVector2(surface.width(), surface.height()) ) - & IntPair2( offset, IntVector2(offset.x + bitmap.width, offset.y + bitmap.rows) ); - if (b.empty()) - return; - - const IntVector2 size = b.size(); - const IntVector2 sp0 = b.p0 - offset; - const int pitch = surface.pitch(); - - Color *dp = &surface[b.p0.y][b.p0.x]; - const int dstep = pitch - size.x; - - if (antialiasing) { - const unsigned char *sp = bitmap.buffer + sp0.y*bitmap.pitch + sp0.x; - const int sstep = bitmap.pitch - size.x; - 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); - if (eraze) *dp *= 1 - a; - else *dp = *dp*(1-a) + color*a; - } - } - } else { - 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 = 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 = eraze ? Color() : color; - } -} - - -static void -lab_pango_renderer_draw_glyph( - PangoRenderer *renderer, - PangoFont *font, - PangoGlyph glyph, - double x, - double y ) -{ - const LabPangoRendererParams ¶ms = ((LabPangoRenderer*)renderer)->params; - if (!params.surface) - return; - - const IntPair2 surface_bounds( - IntVector2(0, 0), - IntVector2(params.surface->width(), params.surface->height()) ); - - FT_Face face = pango_fc_font_lock_face((PangoFcFont*)font); - - // find glyph center for proper scaling - Vector2 origin; - FT_Set_Transform(face, nullptr, nullptr); - if (!FT_Load_Glyph(face, glyph, FT_LOAD_NO_HINTING)) { - const FT_Glyph_Metrics &metrics = face->glyph->metrics; - origin = Vector2(metrics.horiBearingX - metrics.vertBearingX, 0)/64; - } - - // prepare transformation - const Vector2 pos = (params.matrix*Vector3(x + origin.x, y + origin.y, 1)).vec2() - - params.glyph_matrix*origin; - const int fx = (int)round(pos.x*64); - const int fy = (int)round(pos.y*64); - int ix = fx / 64; - int iy = fy / 64; - FT_Vector vec = {}; - vec.x = fx % 64; - vec.y = fy % 64; - if (params.hinting) { - if (vec.x >= 32) ++ix; - if (vec.y >= 32) ++iy; - vec.x = 0; - vec.y = 0; - } - vec.y = -vec.y; - - FT_Matrix mat = {}; - mat.xx = (int)round( params.glyph_matrix.m00*65536); - mat.xy = (int)round(-params.glyph_matrix.m10*65536); - mat.yx = (int)round(-params.glyph_matrix.m01*65536); - mat.yy = (int)round( params.glyph_matrix.m11*65536); - - // draw - FT_Set_Transform(face, &mat, &vec); - if (!FT_Load_Glyph(face, glyph, params.hinting ? FT_LOAD_DEFAULT : FT_LOAD_NO_HINTING)) { - 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)) { - const IntVector2 offset( - face->glyph->bitmap_left + ix, - iy - face->glyph->bitmap_top ); - if (params.antialiasing) { - if (params.eraze) - put_ft_bitmap( - *params.surface, surface_bounds, face->glyph->bitmap, offset, params.color ); - else - put_ft_bitmap( - *params.surface, surface_bounds, face->glyph->bitmap, offset, params.color ); - } else { - if (params.eraze) - put_ft_bitmap( - *params.surface, surface_bounds, face->glyph->bitmap, offset, params.color ); - else - put_ft_bitmap( - *params.surface, surface_bounds, face->glyph->bitmap, offset, params.color ); - } - } - } - } - - pango_fc_font_unlock_face((PangoFcFont*)font); -} - - -static void -lab_pango_renderer_init(LabPangoRenderer *renderer) { - memset(&renderer->params, 0, sizeof(renderer->params)); - renderer->params = LabPangoRendererParams(); -} - -static void -lab_pango_renderer_class_init(LabPangoRendererClass *klass) -{ - PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS(klass); - renderer_class->draw_glyph = lab_pango_renderer_draw_glyph; -} - - -static void -lab_pango_fc_pattern_set_substitute(FcPattern *pattern, gpointer data) { - const int &args = *(const int*)data; - FcPatternDel(pattern, FC_HINTING); - FcPatternDel(pattern, FC_ANTIALIAS); - FcPatternAddBool(pattern, FC_HINTING, (args & 1) ? FcTrue : FcFalse); - FcPatternAddBool(pattern, FC_ANTIALIAS, (args & 2) ? FcTrue : FcFalse); -} - - -PangoRenderer* -lab_pango_renderer_new(LabPangoRendererParams *params) { - PangoRenderer *renderer = (PangoRenderer*)g_object_new(LAB_TYPE_PANGO_RENDERER, nullptr); - lab_pango_renderer_set_params((LabPangoRenderer*)renderer, params); - return renderer; -} - -void -lab_pango_renderer_set_params(LabPangoRenderer *renderer, LabPangoRendererParams *params) - { if (params) renderer->params = *params; } - -void -lab_pango_renderer_get_params(LabPangoRenderer *renderer, LabPangoRendererParams *params) - { if (params) *params = renderer->params; } - - -PangoFontMap* -lab_pango_font_map_new(bool hinting, bool antialiasing) { - static int args[4] { 0, 1, 2, 3 }; // to avoid encoding of int into pointer - - PangoFontMap *font_map = pango_ft2_font_map_new(); - pango_ft2_font_map_set_default_substitute( - (PangoFT2FontMap*)font_map, - lab_pango_fc_pattern_set_substitute, - args + (hinting ? 1 : 0) + (antialiasing ? 2 : 0), - nullptr ); - return font_map; -} - diff --git a/c++/freetype/src/labpangorenderer.h b/c++/freetype/src/labpangorenderer.h deleted file mode 100644 index b44637e..0000000 --- a/c++/freetype/src/labpangorenderer.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef LABPANGORENDERER_H -#define LABPANGORENDERER_H - - -#include - -#include "matrix.h" - -class Surface; -class Matrix3; -class Color; - - -G_BEGIN_DECLS - -#define LAB_TYPE_PANGO_RENDERER (lab_pango_renderer_get_type ()) - -typedef struct _LabPangoRenderer LabPangoRenderer; -typedef struct _LabPangoRendererClass LabPangoRendererClass; - -#define LAB_PANGO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), LAB_TYPE_PANGO_RENDERER, LabPangoRendererClass)) -#define LAB_IS_PANGO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), LAB_TYPE_PANGO_RENDERER)) -#define LAB_PANGO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), LAB_TYPE_PANGO_RENDERER, LabPangoRendererClass)) - -struct LabPangoRendererParams { - Surface *surface; - IntPair2 bounds; - Matrix3 matrix; - Matrix2 glyph_matrix; - Color color; - bool hinting; - bool antialiasing; - bool eraze; - LabPangoRendererParams(): - surface(), hinting(), antialiasing(), eraze() { } -}; - - -PangoRenderer* lab_pango_renderer_new(LabPangoRendererParams *params); - -void lab_pango_renderer_set_params(LabPangoRenderer *renderer, LabPangoRendererParams *params); -void lab_pango_renderer_get_params(LabPangoRenderer *renderer, LabPangoRendererParams *params); - -PangoFontMap* lab_pango_font_map_new(bool hinting, bool antialiasing); - -G_END_DECLS - - -#endif - diff --git a/c++/freetype/src/layout.cpp b/c++/freetype/src/layout.cpp new file mode 100644 index 0000000..2d50b02 --- /dev/null +++ b/c++/freetype/src/layout.cpp @@ -0,0 +1,231 @@ + +#include +#include FT_FREETYPE_H + +#include + +#include "layout.h" + + +// fake pango renderer - begin + +G_BEGIN_DECLS + +#define LAB_TYPE_FAKE_PANGO_RENDERER (lab_fake_pango_renderer_get_type ()) + +typedef struct _LabFakePangoRenderer LabFakePangoRenderer; +typedef struct _LabFakePangoRendererClass LabFakePangoRendererClass; + +#define LAB_FAKE_PANGO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), LAB_TYPE_FAKE_PANGO_RENDERER, LabFakePangoRendererClass)) +#define LAB_FAKE_IS_PANGO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), LAB_TYPE_FAKE_PANGO_RENDERER)) +#define LAB_FAKE_PANGO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), LAB_TYPE_FAKE_PANGO_RENDERER, LabFakePangoRendererClass)) + +PangoRenderer* lab_fake_pango_renderer_new(Layout::GlyphList *list); + +G_END_DECLS + +struct _LabFakePangoRenderer { + PangoRenderer parent_instance; + Layout::Line *line; +}; + +struct _LabFakePangoRendererClass { + PangoRendererClass parent_class; +}; + +G_DEFINE_TYPE(LabFakePangoRenderer, lab_fake_pango_renderer, PANGO_TYPE_RENDERER) + +static void +lab_fake_pango_renderer_init(LabFakePangoRenderer *renderer) + { renderer->line = nullptr; } + +static void +lab_fake_pango_renderer_draw_glyph( + PangoRenderer *renderer, + PangoFont *font, + PangoGlyph glyph, + double x, + double y ); + +static void +lab_fake_pango_renderer_class_init(LabFakePangoRendererClass *klass) { + PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS(klass); + renderer_class->draw_glyph = lab_fake_pango_renderer_draw_glyph; +} + +LabFakePangoRenderer* +lab_fake_pango_renderer_new() + { return (LabFakePangoRenderer*)g_object_new(LAB_TYPE_FAKE_PANGO_RENDERER, nullptr); } + +// end - fake pango renderer + + + +static void +lab_fake_pango_renderer_draw_glyph( + PangoRenderer *renderer, + PangoFont *font, + PangoGlyph glyph, + double x, + double y ) +{ + Layout::Line *line = ((LabFakePangoRenderer*)renderer)->line; + assert(line); + if (!line) + return; + + const Real k = 1/64.0; + + assert(PANGO_IS_FC_FONT(font)); + FT_Face face = pango_fc_font_lock_face((PangoFcFont*)font); + FT_Set_Transform(face, nullptr, nullptr); + if (!FT_Load_Glyph(face, glyph, FT_LOAD_NO_HINTING)) { + const FT_Glyph_Metrics &metrics = face->glyph->metrics; + IntVector2 origin(metrics.vertBearingX - metrics.horiBearingX, 0); + IntPair2 bounds( + IntVector2( metrics.horiBearingX, + -metrics.horiBearingY ) + origin, + IntVector2( metrics.horiBearingX + metrics.width, + -metrics.horiBearingY + metrics.height ) + origin ); + + Layout::RunList &runs = line->runs; + if (runs.empty() || !runs.back().font || runs.back().font->gobj() != font) { + runs.push_back(Layout::Run()); + runs.back().font = Glib::wrap(font, true); + } + Layout::Run &run = runs.back(); + run.glyphs.push_back(Layout::Glyph()); + Layout::Glyph &g = run.glyphs.back(); + + g.glyph_index = glyph; + g.origin = Vector2(origin.x, origin.y)*k; + g.position = Vector2(x, y) + g.origin; + g.bounds = Pair2( Vector2(bounds.p0.x, bounds.p0.y)*k, + Vector2(bounds.p1.x, bounds.p1.y)*k ); + } + pango_fc_font_unlock_face((PangoFcFont*)font); +} + +static void +lab_pango_fc_pattern_set_substitute(FcPattern *pattern, gpointer data) { + const int &args = *(const int*)data; + FcPatternDel(pattern, FC_HINTING); + FcPatternDel(pattern, FC_ANTIALIAS); + FcPatternAddBool(pattern, FC_HINTING, (args & 1) ? FcTrue : FcFalse); + FcPatternAddBool(pattern, FC_ANTIALIAS, (args & 2) ? FcTrue : FcFalse); +} + + +static GQuark +layout_data_quark() { + static GQuark quark = g_quark_from_static_string("lab_layout_data"); + return quark; +} + +Glib::RefPtr +Layout::create_fontmap(bool hinting, bool antialiasing) { + static int args[4] { 0, 1, 2, 3 }; // to avoid encoding of int into pointer + int* data = args + (hinting ? 1 : 0) + (antialiasing ? 2 : 0); + + PangoFontMap *font_map = pango_ft2_font_map_new(); + pango_ft2_font_map_set_default_substitute( + (PangoFT2FontMap*)font_map, + lab_pango_fc_pattern_set_substitute, + data, + nullptr ); + g_object_set_qdata((GObject*)font_map, layout_data_quark(), data); + return Glib::wrap(font_map); +} + + +Layout::Layout(const Glib::RefPtr &layout): + hinting(), + antialiasing() +{ + const int *data = (const int*)g_object_get_qdata( + (GObject*)pango_context_get_font_map( pango_layout_get_context(layout->gobj()) ), + layout_data_quark() ); + assert(data); + if (!data) + return; + hinting = (*data) & 1; + antialiasing = (*data) & 2; + + const Real k = 1/Real(PANGO_SCALE); + + PangoRectangle ink_rect, logical_rect; + pango_layout_get_extents(layout->gobj(), &ink_rect, &logical_rect); + bounds = Pair2( Vector2( ink_rect.x, ink_rect.y )*k, + Vector2( ink_rect.x + ink_rect.width, + ink_rect.y + ink_rect.height )*k ); + logical_bounds = Pair2( Vector2( logical_rect.x, logical_rect.y )*k, + Vector2( logical_rect.x + logical_rect.width, + logical_rect.y + logical_rect.height )*k ); + + LabFakePangoRenderer *renderer = lab_fake_pango_renderer_new(); + pango_renderer_activate((PangoRenderer*)renderer); + PangoLayoutIter *iter = pango_layout_get_iter(layout->gobj()); + do { + lines.push_back(Line()); + Line &line = lines.back(); + + pango_layout_iter_get_line_extents(iter, &ink_rect, &logical_rect); + int baseline = pango_layout_iter_get_baseline(iter); + + line.position = Vector2(logical_rect.x, baseline)*k; + line.bounds = Pair2( + Vector2( ink_rect.x - logical_rect.x, + ink_rect.y - baseline)*k, + Vector2( ink_rect.x - logical_rect.x + ink_rect.width, + ink_rect.y - baseline + ink_rect.height)*k ); + line.logical_bounds = Pair2( + Vector2( 0, logical_rect.y )*k, + Vector2( logical_rect.width, + logical_rect.y - baseline + logical_rect.height)*k ); + + renderer->line = &line; + PangoLayoutLine *l = pango_layout_iter_get_line_readonly(iter); + pango_renderer_draw_layout_line((PangoRenderer*)renderer, l, 0, 0); + } while(pango_layout_iter_next_line(iter)); + pango_layout_iter_free(iter); + pango_renderer_deactivate((PangoRenderer*)renderer); + g_object_unref(renderer); +} + + +TransformedLayout::TransformedLayout( + const RefPtr &layout, + const Matrix2 &spacing_matrix, + const Matrix2 &glyph_matrix +): + layout(layout), + spacing_matrix(spacing_matrix), + glyph_matrix(glyph_matrix) +{ + if (!layout) + return; + + const Pair2 &logical_bounds = layout->get_logical_bounds(); + const Layout::LineList &lines = layout->get_lines(); + line_bounds.reserve(lines.size()); + for(Layout::LineList::const_iterator li = lines.begin(); li != lines.end(); ++li) { + Pair2 lb; + for(Layout::RunList::const_iterator ri = li->runs.begin(); ri != li->runs.end(); ++ri) { + for(Layout::GlyphList::const_iterator gi = ri->glyphs.begin(); gi != ri->glyphs.end(); ++gi) { + const Vector2 position = spacing_matrix*gi->position; + Pair2 gb = glyph_matrix.transform_bounds(gi->bounds); + gb.p0 += position; + gb.p1 += position; + lb |= gb; + } + } + line_bounds.push_back(lb); + const Vector2 p0 = spacing_matrix*li->position; + const Vector2 p1 = spacing_matrix*Vector2(0, li->position.y); + const Vector2 p2 = spacing_matrix*Vector2(logical_bounds.p1.x - li->logical_bounds.p1.x, li->position.y); + bounds |= Pair2(lb.p0 + p0, lb.p1 + p0); + bounds |= Pair2(lb.p0 + p1, lb.p1 + p1); + bounds |= Pair2(lb.p0 + p2, lb.p1 + p2); + } +} + diff --git a/c++/freetype/src/layout.h b/c++/freetype/src/layout.h new file mode 100644 index 0000000..b5ba9a3 --- /dev/null +++ b/c++/freetype/src/layout.h @@ -0,0 +1,98 @@ +#ifndef LAYOUT_H +#define LAYOUT_H + + +#include + +#include + +#include "vector.h" +#include "matrix.h" + + +class Layout: public Shared { +public: + class Glyph { + public: + Vector2 position; + Vector2 origin; + Pair2 bounds; + Pango::Glyph glyph_index; + }; + typedef std::vector GlyphList; + + class Run { + public: + Glib::RefPtr font; + GlyphList glyphs; + }; + typedef std::vector RunList; + + class Line { + public: + Vector2 position; + Pair2 bounds; + Pair2 logical_bounds; + RunList runs; + }; + typedef std::vector LineList; + +private: + bool hinting; + bool antialiasing; + Pair2 bounds; + Pair2 logical_bounds; + LineList lines; + + Layout(const Layout&) = delete; + Layout& operator=(const Layout&) = delete; + +public: + explicit Layout(const Glib::RefPtr &layout); + + bool get_hinting() const + { return hinting; } + bool get_antialiasing() const + { return antialiasing; } + const Pair2& get_bounds() const + { return bounds; } + const Pair2& get_logical_bounds() const + { return logical_bounds; } + const LineList& get_lines() + { return lines; } + + static Glib::RefPtr create_fontmap(bool hinting, bool antialiasing); +}; + + +class TransformedLayout: public Shared { +public: + typedef std::vector LineBoundsList; + +private: + RefPtr layout; + Matrix2 spacing_matrix; + Matrix2 glyph_matrix; + Pair2 bounds; + LineBoundsList line_bounds; + +public: + TransformedLayout( + const RefPtr &layout, + const Matrix2 &spacing_matrix, + const Matrix2 &glyph_matrix ); + + const RefPtr& get_layout() const + { return layout; } + const Matrix2& get_spacing_matrix() const + { return spacing_matrix; } + const Matrix2& get_glyph_matrix() const + { return glyph_matrix; } + const Pair2& get_bounds() const + { return bounds; } + const LineBoundsList& get_line_bounds() const + { return line_bounds; } +}; + + +#endif diff --git a/c++/freetype/src/layoutdraw.cpp b/c++/freetype/src/layoutdraw.cpp new file mode 100644 index 0000000..bfd145a --- /dev/null +++ b/c++/freetype/src/layoutdraw.cpp @@ -0,0 +1,171 @@ + +#include +#include FT_BITMAP_H + +#include + +#include "layoutdraw.h" + + +template +static void +put_ft_bitmap( + Surface &surface, + const IntPair2 &/*bounds*/, + const FT_Bitmap &bitmap, + const IntVector2 &offset, + const Color &color ) +{ + const IntPair2 b = /*bounds + & */IntPair2( IntVector2(0, 0), IntVector2(surface.width(), surface.height()) ) + & IntPair2( offset, IntVector2(offset.x + bitmap.width, offset.y + bitmap.rows) ); + if (b.empty()) + return; + + const IntVector2 size = b.size(); + const IntVector2 sp0 = b.p0 - offset; + const int pitch = surface.pitch(); + + Color *dp = &surface[b.p0.y][b.p0.x]; + const int dstep = pitch - size.x; + + if (antialiasing) { + const unsigned char *sp = bitmap.buffer + sp0.y*bitmap.pitch + sp0.x; + const int sstep = bitmap.pitch - size.x; + 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); + if (eraze) *dp *= 1-a; + else *dp = *dp*(1-a) + color*a; + } + } + } else { + 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 = 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 = eraze ? Color() : color; + } +} + + +void +LayoutDraw::draw( + Surface &surface, + const IntPair2 &bounds, + const RefPtr &layout, + const Matrix &matrix, + const Color &color, + bool eraze, + const Vector2 &position, + const Real *realign ) +{ + if (!layout) + return; + + IntPair2 b = bounds & IntPair2(IntVector2(), IntVector2(surface.width(), surface.height())); + if (b.empty()) + return; + + const Matrix2 &spacing_matrix = layout->get_spacing_matrix(); + const Matrix2 &glyph_matrix = layout->get_glyph_matrix(); + + const Pair2 fb(Vector2(b.p0.x, b.p0.y), Vector2(b.p1.x, b.p1.y)); + const Pair2 flb = matrix.transform_bounds(layout->get_bounds() + spacing_matrix*position); + if ((fb & flb).empty()) + return; + + const Matrix3 full_spacing_matrix = matrix * Matrix3( + Vector3(spacing_matrix.row_x()), + Vector3(spacing_matrix.row_y()) ); + const Matrix2 full_glyph_matrix = Matrix2( + matrix.row_x().vec2(), + matrix.row_y().vec2() )*glyph_matrix; + + FT_Matrix mat = {}; + mat.xx = (int)round( full_glyph_matrix.m00*65536); + mat.xy = (int)round(-full_glyph_matrix.m10*65536); + mat.yx = (int)round(-full_glyph_matrix.m01*65536); + mat.yy = (int)round( full_glyph_matrix.m11*65536); + FT_Vector vec = {}; + + const bool hinting = layout->get_layout()->get_hinting(); + const bool antialiasing = layout->get_layout()->get_antialiasing(); + const FT_Int32 load_flags = hinting ? FT_LOAD_DEFAULT : FT_LOAD_NO_HINTING; + const FT_Render_Mode render_mode = antialiasing ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO; + + const Pair2 &logical_bounds = layout->get_layout()->get_logical_bounds(); + const Layout::LineList &lines = layout->get_layout()->get_lines(); + const TransformedLayout::LineBoundsList &line_bounds = layout->get_line_bounds(); + + PangoFont *font = nullptr; + + assert(lines.size() == line_bounds.size()); + TransformedLayout::LineBoundsList::const_iterator lbi = line_bounds.begin(); + for(Layout::LineList::const_iterator li = lines.begin(); li != lines.end(); ++li, ++lbi) { + Vector2 linepos = li->position; + if (realign) + linepos.x = (logical_bounds.p1.x - li->logical_bounds.p1.x)*clamp(*realign, 0, 1); + linepos += position; + + const Vector2 lb_offset = spacing_matrix*linepos; + const Pair2 lb = matrix.transform_bounds(*lbi + lb_offset); + if ((fb & lb).empty()) + continue; + + for(Layout::RunList::const_iterator ri = li->runs.begin(); ri != li->runs.end(); ++ri) { + assert(ri->font); + PangoFont *next_font = ri->font->gobj(); + if (!next_font) continue; + if (font != next_font) { + if (font) pango_fc_font_unlock_face((PangoFcFont*)font); + font = next_font; + } + + assert(PANGO_IS_FC_FONT(font)); + FT_Face face = pango_fc_font_lock_face((PangoFcFont*)font); + for(Layout::GlyphList::const_iterator gi = ri->glyphs.begin(); gi != ri->glyphs.end(); ++gi) { + const Vector2 gb_offset = (full_spacing_matrix*Vector3(linepos + gi->position, 1)).vec2(); + const Pair2 gb = full_glyph_matrix.transform_bounds(gi->bounds) + gb_offset; + if ((fb & gb).empty()) + continue; + + const Vector2 gp = gb_offset + full_glyph_matrix*gi->origin; + const int fx = (int)round(gp.x*64); + const int fy = (int)round(gp.y*64); + int ix = fx / 64; + int iy = fy / 64; + vec.x = fx % 64; + vec.y = fy % 64; + if (hinting) { + if (vec.x >= 32) ++ix; + if (vec.y >= 32) ++iy; + vec.x = 0; + vec.y = 0; + } + vec.y = -vec.y; + + FT_Set_Transform(face, &mat, &vec); + if ( !FT_Load_Glyph(face, gi->glyph_index, load_flags) + && !FT_Render_Glyph(face->glyph, render_mode) ) + { + const FT_Bitmap &bitmap = face->glyph->bitmap; + const IntVector2 offset( ix + face->glyph->bitmap_left, + iy - face->glyph->bitmap_top ); + if (antialiasing) { + if (eraze) put_ft_bitmap(surface, bounds, bitmap, offset, color); + else put_ft_bitmap(surface, bounds, bitmap, offset, color); + } else { + if (eraze) put_ft_bitmap(surface, bounds, bitmap, offset, color); + else put_ft_bitmap(surface, bounds, bitmap, offset, color); + } + } + } + } + } + + if (font) pango_fc_font_unlock_face((PangoFcFont*)font); +} + diff --git a/c++/freetype/src/layoutdraw.h b/c++/freetype/src/layoutdraw.h new file mode 100644 index 0000000..5a0b646 --- /dev/null +++ b/c++/freetype/src/layoutdraw.h @@ -0,0 +1,25 @@ +#ifndef LAYOUTDRAW_H +#define LAYOUTDRAW_H + + +#include "layout.h" +#include "surface.h" + + +class LayoutDraw { +public: + static void draw( + Surface &surface, + const IntPair2 &bounds, + const RefPtr &layout, + const Matrix &matrix, + const Color &color, + bool eraze = false, + const Vector2 &position = Vector2(), + const Real *realign = nullptr ); +}; + + +#endif + + diff --git a/c++/freetype/src/vector.h b/c++/freetype/src/vector.h index 1161516..9d70a39 100644 --- a/c++/freetype/src/vector.h +++ b/c++/freetype/src/vector.h @@ -318,6 +318,16 @@ public: inline PairT operator| (const PairT &other) const { return PairT(*this) |= other; } + inline PairT& operator+= (const Vector &v) + { p0 += v; p1 += v; return *this; } + inline PairT& operator-= (const Vector &v) + { p0 -= v; p1 -= v; return *this; } + + inline PairT operator+ (const Vector &other) const + { return PairT(*this) += other; } + inline PairT operator- (const Vector &other) const + { return PairT(*this) -= other; } + inline Vector distance() const { return p1 - p0; } inline Vector size() const