From 62a39fee6ea5741743d4c9fd0b36fcaaaedb6c63 Mon Sep 17 00:00:00 2001 From: Tavmjong Bah Date: Sun, 26 Jan 2025 15:27:14 +0100 Subject: [PATCH] Implement support for all color font formats. Requires recent Harfbuzz and Cairo. COLOR_FONT_STRATEGY environment variable: 0 (Default): Draw non-color, SVG fonts ourselves, use Cairo for other formats 1 Use Cairo for all. 2 Draw non-color, SVG fonts ourselves. Don't draw others (same as current 1.4.x) DRAW_TEXT_BOXES environment variable: If defined draw various bounding boxes for glyphs. Rendering fails for strategy 0 when changing zoom rapidly. Rendering fails for strategy 1 when zooming. --- CMakeScripts/DefineDependsandFlags.cmake | 1 + src/display/drawing-context.h | 11 + src/display/drawing-text.cpp | 234 ++++++++++++---- src/display/drawing-text.h | 5 + src/libnrtype/Layout-TNG-Compute.cpp | 10 +- src/libnrtype/OpenTypeUtil.cpp | 181 +++++++++++- src/libnrtype/OpenTypeUtil.h | 9 + src/libnrtype/font-glyph.h | 6 + src/libnrtype/font-instance.cpp | 333 +++++++++++++++++++---- src/libnrtype/font-instance.h | 26 +- 10 files changed, 698 insertions(+), 118 deletions(-) diff --git a/CMakeScripts/DefineDependsandFlags.cmake b/CMakeScripts/DefineDependsandFlags.cmake index 26650fb1ee..90931653b3 100644 --- a/CMakeScripts/DefineDependsandFlags.cmake +++ b/CMakeScripts/DefineDependsandFlags.cmake @@ -121,6 +121,7 @@ endif() find_package(PkgConfig REQUIRED) pkg_check_modules(INKSCAPE_DEP REQUIRED harfbuzz + harfbuzz-cairo pangocairo>=1.44 pangoft2 fontconfig diff --git a/src/display/drawing-context.h b/src/display/drawing-context.h index 35684924f7..39281df9cf 100644 --- a/src/display/drawing-context.h +++ b/src/display/drawing-context.h @@ -96,6 +96,17 @@ public: void strokePreserve() { cairo_stroke_preserve(_ct); } void clip() { cairo_clip(_ct); } + void setFontFace(cairo_font_face_t *font_face) { + cairo_set_font_face(_ct, font_face); + } + void showGlyphs(const cairo_glyph_t *glyphs, int num_glyphs) { + cairo_show_glyphs(_ct, glyphs, num_glyphs); // For color glyphs. + } + void setUnitFontMatrix() { + cairo_matrix_t unit_matrix = { 1, 0, 0, -1, 0, 0 }; + cairo_set_font_matrix(_ct, &unit_matrix); + } + void setLineWidth(double w) { cairo_set_line_width(_ct, w); } void setHairline(); void setLineCap(cairo_line_cap_t cap) { cairo_set_line_cap(_ct, cap); } diff --git a/src/display/drawing-text.cpp b/src/display/drawing-text.cpp index b3f2718742..b7a1194ea3 100644 --- a/src/display/drawing-text.cpp +++ b/src/display/drawing-text.cpp @@ -37,6 +37,8 @@ DrawingGlyphs::DrawingGlyphs(Drawing &drawing) void DrawingGlyphs::setGlyph(std::shared_ptr font, unsigned int glyph, Geom::Affine const &trans) { + assert(font); + defer([=, this, font = std::move(font)] { _markForRendering(); @@ -52,15 +54,49 @@ void DrawingGlyphs::setGlyph(std::shared_ptr font, unsigned int gl // Load pathvectors and pixbufs in advance, as must be done on main thread. if (font) { + cairo_font_face = font->CairoFontFace(); design_units = font->GetDesignUnits(); pathvec = font->PathVector(_glyph); bbox_exact = font->BBoxExact(_glyph); bbox_pick = font->BBoxPick( _glyph); bbox_draw = font->BBoxDraw( _glyph); if (font->FontHasSVG()) { + has_svg = true; pixbuf = font->PixBuf(_glyph); } - font_descr = pango_font_description_to_string(font->get_descr()); + has_png = font->FontHasPNG(); + has_layers = font->FontHasLayers(); + has_paint = font->FontHasPaint(); + + font_descr = pango_font_description_to_string(font->get_descr()); + + // This tests if we really need to check font for glyph type. Presumably Pango already has + // given us a matching font that contains the right glyph type. + if (has_svg != font->GlyphHasSVG(glyph)) { + std::cerr << "DrawingGlyphs::setGlyph: glyph missing in SVG font!!!! " << std::setw(6) << glyph + << " (" << std::setw(12) << font->UnicodeName(_glyph) << ")" + << " " << font_descr + << std::endl; + } + if (has_png != font->GlyphHasPNG(glyph)) { + std::cerr << "DrawingGlyphs::setGlyph: glyph missing in PNG font!!!! " << std::setw(6) << glyph + << " (" << std::setw(12) << font->UnicodeName(_glyph) << ")" + << " " << font_descr + << std::endl; + } + if (has_layers != font->GlyphHasLayers(glyph)) { + std::cerr << "DrawingGlyphs::setGlyph: glyph missing in COLRv0 font!!!! " << std::setw(6) << glyph + << " (" << std::setw(12) << font->UnicodeName(_glyph) << ")" + << " " << font_descr + << std::endl; + } + if (has_paint != font->GlyphHasPaint(glyph)) { + std::cerr << "DrawingGlyphs::setGlyph: glyph missing in COLRv1 font!!!! " << std::setw(6) << glyph + << " (" << std::setw(12) << font->UnicodeName(_glyph) << ")" + << " " << font_descr + << std::endl; + } + // std::cout << "DrawingGlyphs::setGlyph: " << std::setw(6) << glyph // << " design_units: " << design_units // << " bbox_exact: " << bbox_exact @@ -85,8 +121,9 @@ unsigned DrawingGlyphs::_updateItem(Geom::IntRect const &/*area*/, UpdateContext throw InvalidItemException(); } + // There is always a pathvec, it might be empty though. if (!pathvec) { - // Bitmap font + std::cerr << "DrawingGlyphs::_updateItem: no pathvec!" << std::endl; return STATE_ALL; } @@ -122,11 +159,11 @@ unsigned DrawingGlyphs::_updateItem(Geom::IntRect const &/*area*/, UpdateContext // drawing-item variable _bbox = bbox_draw_scaled; - // std::cout << "DrawingGlyphs::_updateItem: " - // << " glyph: " << std::setw(6) << _glyph - // << " bbox_pick_scaled: " << bbox_pick_scaled - // << " bbox_draw_scaled: " << bbox_draw_scaled - // << std::endl; + std::cout << "DrawingGlyphs::_updateItem: " + << " glyph: " << std::setw(6) << _glyph + << " bbox_pick_scaled: " << bbox_pick_scaled + << " bbox_draw_scaled: " << bbox_draw_scaled + << std::endl; return STATE_ALL; } @@ -411,6 +448,7 @@ unsigned DrawingText::_renderItem(DrawingContext &dc, RenderContext &rc, Geom::I { auto visible = area & _bbox; if (!visible) { + std::cout << "DrawingText::_renderItem: not visible" << std::endl; return RENDER_OK; } @@ -552,7 +590,19 @@ unsigned DrawingText::_renderItem(DrawingContext &dc, RenderContext &rc, Geom::I dc.newPath(); // Clear text-decoration path } - // Accumulate the path that represents the glyphs and/or draw SVG glyphs. + // TEMP + int strategy = 0; + const char* color_font_strategy = std::getenv("COLOR_FONT_STRATEGY"); + if (color_font_strategy) { + if (color_font_strategy[0] == '1') { + strategy = 1; + } else if (color_font_strategy[0] == '2') { + strategy = 2; + } + } + std::cout << "Drawing color fonts with strategy: " << strategy << std::endl; + + // Accumulate the path that represents the glyphs and/or draw color glyphs. for (auto &i : _children) { auto g = cast(&i); if (!g) throw InvalidItemException(); @@ -564,57 +614,136 @@ unsigned DrawingText::_renderItem(DrawingContext &dc, RenderContext &rc, Geom::I } dc.transform(g->_ctm); -#if 0 - // Draw various boxes for debugging - auto path_copy = cairo_copy_path(dc.raw()); // Cairo save/restore doesn't apply to path! - { - Inkscape::DrawingContext::Save save(dc); - dc.newPath(); - dc.rectangle(g->bbox_exact); - dc.setLineWidth(0.02); - dc.setSource(0.0, 0.0, 1.0, 1.0); // Blue - dc.stroke(); + const char* draw_text_boxes = std::getenv("DRAW_TEXT_BOXES"); + if (draw_text_boxes) { + // Draw various boxes for debugging + auto path_copy = cairo_copy_path(dc.raw()); // Cairo save/restore doesn't apply to path! + { + Inkscape::DrawingContext::Save save(dc); + dc.newPath(); + dc.rectangle(g->bbox_exact); + dc.setLineWidth(0.02); + dc.setSource(0x80ffff80); // Bluegreen + dc.stroke(); + } + { + Inkscape::DrawingContext::Save save(dc); + dc.newPath(); + dc.rectangle(g->bbox_pick); + dc.setLineWidth(0.02); + dc.setSource(0xff80ffff); // Purple + dc.stroke(); + } + { + Inkscape::DrawingContext::Save save(dc); + dc.newPath(); + dc.rectangle(g->bbox_draw); + dc.setLineWidth(0.02); + dc.setSource(0xffff8080); // Yellow + dc.stroke(); + } + cairo_append_path(dc.raw(), path_copy); + cairo_path_destroy(path_copy); + // End debug boxes. } - { + + std::cout << "DrawingText::_renderItem: " + << std::setw(6) << g->_glyph + << " " << std::setw(20) << (g->font_descr ? g->font_descr : " No font description!!") + << " Design units: " << g->design_units + << " svg: " << std::boolalpha << g->has_svg + << " png: " << std::boolalpha << g->has_png + << " layers: " << std::boolalpha << g->has_layers + << " paint: " << std::boolalpha << g->has_paint + << " pathvec: " << std::boolalpha << (bool)g->pathvec + << std::endl; + + // +++++++++++++++++++++++++++++++++ + + if (strategy == 0) { +#if HB_VERSION_ATLEAST(7,0,0) + if (g->has_svg && g->pixbuf) { Inkscape::DrawingContext::Save save(dc); - dc.newPath(); - dc.rectangle(g->bbox_pick); - dc.setLineWidth(0.02); - dc.setSource(1.0, 0.0, 0.0, 1.0); // Red - dc.stroke(); - } - { + + // pixbuf is in font design units, scale to embox. + double scale = g->design_units; + if (scale <= 0) scale = 1000; + dc.translate(0, 1); + dc.scale(1.0 / scale, -1.0 / scale); + dc.setSource(g->pixbuf->getSurfaceRaw(), 0, 0); + dc.paint(1); + } else if (g->has_png) { Inkscape::DrawingContext::Save save(dc); - dc.newPath(); - dc.rectangle(g->bbox_draw); - dc.setLineWidth(0.02); - dc.setSource(0.0, 1.0, 0.0, 1.0); // Green - dc.stroke(); + + cairo_glyph_t glyph = { 0, 0, 0 }; // {index, x, y} + glyph.index = g->_glyph; + + dc.setUnitFontMatrix(); // We do all scaling ourselves. + dc.setFontFace(g->cairo_font_face); + +// #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 18, 0) +// auto options = cairo_font_options_create(); +// cairo_font_options_set_color_mode(options, CAIRO_COLOR_MODE_COLOR); +// cairo_font_options_set_color_palette(options, CAIRO_COLOR_PALETTE_DEFAULT); +// cairo_set_font_options(dc.raw(), options); +// cairo_font_options_destroy(options); +// #endif + dc.showGlyphs(&glyph, 1); + } else if (g->has_layers || g->has_paint) { + Inkscape::DrawingContext::Save save(dc); + + cairo_glyph_t glyph = { 0, 0, 0 }; // {index, x, y} + glyph.index = g->_glyph; + + dc.setUnitFontMatrix(); // We do all scaling ourselves. + dc.setFontFace(g->cairo_font_face); + +// #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 18, 0) +// auto options = cairo_font_options_create(); +// cairo_font_options_set_color_mode(options, CAIRO_COLOR_MODE_COLOR); +// cairo_font_options_set_color_palette(options, CAIRO_COLOR_PALETTE_DEFAULT); +// cairo_set_font_options(dc.raw(), options); +// cairo_font_options_destroy(options); +// #endif + dc.showGlyphs(&glyph, 1); + } else if (g->pathvec) { + dc.path(*g->pathvec); + } else { + std::cerr << "DrawingText::_renderItem: No glyph data! " + << std::setw(6) << g->_glyph << std::endl; } - cairo_append_path(dc.raw(), path_copy); - cairo_path_destroy(path_copy); - // End debug boxes. +#else + std::cerr << "DrawingText::renderItem: Stategy 0 Color fonts require HarfBuzz >= 7.0.0" << std::endl; #endif + } // End Strategy 0 - if (g->pathvec) { + // +++++++++++++++++++++++++++++++++ - // Draw various boxes for debugging - // auto path_copy = cairo_copy_path(dc.raw()); // Cairo save/restore doesn't apply to path! - // { - // Geom::OptRect box = bounds_exact(*g->pathvec); - // if (box) { - // Inkscape::DrawingContext::Save save(dc); - // dc.newPath(); - // dc.rectangle(*box); - // dc.setLineWidth(0.02); - // dc.setSource(0xff000080); - // dc.stroke(); - // } - // } - // cairo_append_path(dc.raw(), path_copy); // Restore path. - // cairo_path_destroy(path_copy); - // End debug boxes. + // Use Cairo for font rendering. Requires HB 7.0.0 if we use cairo_font_face from hb_face. + // Uses outlines for SVGs + if (strategy == 1) { + { +#if HB_VERSION_ATLEAST(7,0,0) + Inkscape::DrawingContext::Save save(dc); + + cairo_glyph_t glyph = { 0, 0, 0 }; // {index, x, y} + glyph.index = g->_glyph; + dc.setUnitFontMatrix(); // We do all scaling ourselves. + dc.setFontFace(g->cairo_font_face); + + dc.showGlyphs(&glyph, 1); +#else + std::cerr << "DrawingText::renderItem: Stategy 1 Color fonts require HarfBuzz >= 7.0.0" << std::endl; +#endif + } + } // End Strategy 1 + + // +++++++++++++++++++++++++++++++++ + + if (strategy == 2) { + // 1.4.x + if (g->pathvec) { if (g->pixbuf) { { // pixbuf is in font design units, scale to embox. @@ -630,9 +759,10 @@ unsigned DrawingText::_renderItem(DrawingContext &dc, RenderContext &rc, Geom::I dc.path(*g->pathvec); } } + } // End Strategy 2 } - // Draw the glyphs (non-SVG glyphs). + // Draw the glyphs (non-color glyphs, draw as one path). { Inkscape::DrawingContext::Save save(dc); dc.transform(_ctm); diff --git a/src/display/drawing-text.h b/src/display/drawing-text.h index 3409fe4647..ece1ee1abd 100644 --- a/src/display/drawing-text.h +++ b/src/display/drawing-text.h @@ -49,6 +49,11 @@ protected: float _pl; // phase length double design_units; + cairo_font_face_t *cairo_font_face = nullptr; // Cairo font face for color glyphs. + bool has_svg = false; + bool has_png = false; + bool has_layers = false; // COLRv0 + bool has_paint = false; // COLRv1 HB 7.0.0 Geom::PathVector const *pathvec = nullptr; // pathvector of glyph. Inkscape::Pixbuf const *pixbuf = nullptr; // pixbuf, if SVG font Geom::Rect bbox_exact; // Exact bounding box of glyph. diff --git a/src/libnrtype/Layout-TNG-Compute.cpp b/src/libnrtype/Layout-TNG-Compute.cpp index 11d269cabe..ca37cc2d76 100644 --- a/src/libnrtype/Layout-TNG-Compute.cpp +++ b/src/libnrtype/Layout-TNG-Compute.cpp @@ -425,8 +425,6 @@ bool Layout::Calculator::_measureUnbrokenSpan(ParagraphInfo const ¶, // with upright orientation (pre 1.44.0). auto font = para.pango_items[span->end.iter_span->pango_item_index].font; double font_size = span->start.iter_span->font_size; - //double glyph_h_advance = font_size * font->Advance(info->glyph, false); - double glyph_v_advance = font_size * font->Advance(info->glyph, true ); if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) { // Vertical text @@ -446,6 +444,7 @@ bool Layout::Calculator::_measureUnbrokenSpan(ParagraphInfo const ¶, char_width += glyph_width; } else { // Pango < 1.44.0 glyph_width returned is horizontal width, not vertical. + double glyph_v_advance = font_size * font->Advance(info->glyph, true ); char_width += glyph_v_advance; } } @@ -701,10 +700,6 @@ void Layout::Calculator::_outputLine(ParagraphInfo const ¶, double x_in_span_last = 0.0; // set at the END when a new cluster starts double x_in_span = 0.0; // set from the preceding at the START when a new cluster starts. - // for (int i = 0; i < unbroken_span.glyph_string->num_glyphs; ++i) { - // std::cout << "Unbroken span: " << unbroken_span.glyph_string->glyphs[i].glyph << std::endl; - // } - if (it_span->start.char_byte == 0) { // Start of an unbroken span, we might have dx, dy or rotate still to process // (x and y are done per chunk) @@ -829,7 +824,6 @@ void Layout::Calculator::_outputLine(ParagraphInfo const ¶, // Advance does not include kerning but Pango <= 1.43 gives wrong advances for verical upright text. double glyph_h_advance = new_span.font_size * font->Advance(new_glyph.glyph, false); double glyph_v_advance = new_span.font_size * font->Advance(new_glyph.glyph, true ); - #ifdef DEBUG_GLYPH bool is_cluster_start = unbroken_span_glyph_info->attr.is_cluster_start; @@ -1617,6 +1611,8 @@ unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const ¶->pango_items[pango_item_index].item->analysis, new_span.glyph_string); + printf("Layout::Calculator::_buildSpansForPara: %*.*s\n", new_span.text_bytes, new_span.text_bytes, + para->text.data() + para_text_index); if (para->pango_items[pango_item_index].item->analysis.level & 1) { // Right to left text (Arabic, Hebrew, etc.) diff --git a/src/libnrtype/OpenTypeUtil.cpp b/src/libnrtype/OpenTypeUtil.cpp index 50326ce01d..1c757ac8c5 100644 --- a/src/libnrtype/OpenTypeUtil.cpp +++ b/src/libnrtype/OpenTypeUtil.cpp @@ -10,11 +10,21 @@ #include "OpenTypeUtil.h" - +#define DEBUG_OPENTYPEUTIL +#ifdef DEBUG_OPENTYPEUTIL #include // For debugging +#include +#include +#endif + #include #include +#include +#include +#include +#include // Glib::FileError + // FreeType #include FT_FREETYPE_H #include FT_MULTIPLE_MASTERS_H @@ -60,6 +70,35 @@ Glib::ustring extract_tag( guint32 *tag ) { return tag_name; } +// Get font name from hb_face +std::string font_name(hb_face_t* hb_face) { + const unsigned int MAX_NAME_CHAR = 100; + unsigned int max_name_char = MAX_NAME_CHAR; + char font_name_c[MAX_NAME_CHAR] = {}; + hb_ot_name_get_utf8 (hb_face, HB_OT_NAME_ID_FONT_FAMILY, hb_language_get_default(), &max_name_char, font_name_c); + std::string font_name = "unknown"; + if (font_name_c) { + font_name = font_name_c; + } + return font_name; +} + +// Create and return a directory to dump a file file's SVGs or PNGs. +std::string font_directory(hb_face_t* hb_face) { + + const std::string font_directory = "font_dumps"; + if (!std::filesystem::is_directory(font_directory) || !std::filesystem::exists(font_directory)) { + std::filesystem::create_directory(font_directory); + } + + std::string font_name_directory = font_directory + "/" + font_name(hb_face); + if (!std::filesystem::is_directory(font_name_directory) || !std::filesystem::exists(font_name_directory)) { + std::filesystem::create_directory(font_name_directory); + } + + return font_name_directory; +} + void readOpenTypeTableList(hb_font_t* hb_font, std::unordered_set& list) { hb_face_t* hb_face = hb_font_get_face (hb_font); @@ -76,6 +115,8 @@ void readOpenTypeTableList(hb_font_t* hb_font, std::unordered_set& } } +// There is now hb_face_collect_glyph_mappings() (since 7.0) that could be used. + // Later (see get_glyphs) we need to lookup the Unicode codepoint for a glyph // but there's no direct API for that. So, we need a way to iterate over all // glyph mappings and build a reverse map. @@ -384,7 +425,32 @@ void readOpenTypeSVGTable(hb_font_t* hb_font, hb_face_t* hb_face = hb_font_get_face (hb_font); +#if 0 + // Test use of hb_ot_color_glyph_reference_svg() + for (int i=0; i < hb_face_get_glyph_count(hb_face); ++i) { + hb_blob_t* svg_glyph_blob = hb_ot_color_glyph_reference_svg(hb_face, i); + if (svg_glyph_blob) { + // Note: The blob can contain glyphs after the desired glyph. One must use the 'length' argument to truncate the blob. + unsigned int length = 0; + const char* data = hb_blob_get_data(svg_glyph_blob, &length); + std::cout << "readOpenTypeSVGTable: " << font_name(hb_face) << " " << i << " length: " << length << std::endl; + if (data) { + std::ofstream output; + std::string filename = font_directory(hb_face) + "/glyph_x_" + std::to_string(i) + ".svg"; + output.open (filename); + for (unsigned int j = 0; j < length; j++) { + output << data[j]; + } + output.close(); + } + hb_blob_destroy(svg_glyph_blob); + } + } +#endif + // Harfbuzz has some support for SVG fonts but it is not exposed until version 2.1 (Oct 30, 2018). + // And, it turns out it is not very useful as it just returns the SVG that contains the glyph without + // processing picking the glyph out of the SVG which can contain hundreds or thousands of glyphs. // We do it the hard way! hb_blob_t *hb_blob = hb_face_reference_table (hb_face, HB_OT_TAG_SVG); @@ -396,10 +462,12 @@ void readOpenTypeSVGTable(hb_font_t* hb_font, unsigned int svg_length = hb_blob_get_length (hb_blob); if (svg_length == 0) { // No SVG glyphs in table! + hb_blob_destroy(hb_blob); return; } const char* data = hb_blob_get_data(hb_blob, &svg_length); + hb_blob_destroy(hb_blob); if (!data) { std::cerr << "readOpenTypeSVGTable: Failed to get data! " << std::endl; return; @@ -456,21 +524,116 @@ void readOpenTypeSVGTable(hb_font_t* hb_font, static auto regex = Glib::Regex::create("(id=\"\\s*glyph\\d+\\s*\")", Glib::RegexCompileFlags::REGEX_OPTIMIZE); svg = regex->replace(svg, 0, "\\1 visibility=\"hidden\"", static_cast(0)); +#ifdef DEBUG_OPENTYPEUTIL + std::ofstream output; + std::string filename = font_directory(hb_face) + "/glyph_" + std::to_string(startGlyphID); + if (startGlyphID != endGlyphID) { + filename += "_" + std::to_string(endGlyphID); + } + filename += ".svg"; + output.open (filename); + output << svg; + output.close(); + + std::cout << "Glyphs: " << startGlyphID << "-" << endGlyphID << " "; + auto length = svg.length(); + if (length < 500) { + std::cout << svg << std::endl; + } else { + std::cout << "svg length: " << length << std::endl; + } +#endif svgs[entry] = svg; for (unsigned int i = startGlyphID; i < endGlyphID+1; ++i) { glyphs[i].entry_index = entry; } - // for (auto const& glyph : glyphs) { - // std::cout << "Glyph: " << glyph.first << std::endl; - // auto length = svgs[glyph.second.entry_index].length(); - // if (length < 1000) { - // std::cout << svgs[glyph.second.entry_index] << std::endl; - // } else { - // std::cout << "glyph svg string length: " << length << std::endl; - // } + } +} + +// Get PNG glyphs out of an OpenType font. +void readOpenTypePNG(hb_font_t* hb_font, + std::vector>& pixbufs) { + + hb_face_t* hb_face = hb_font_get_face (hb_font); + + if (!hb_ot_color_has_png(hb_face)) { + std::cout << "No PNG in font face!" << std::endl; + return; + } + + auto glyph_count = hb_face_get_glyph_count(hb_face); + + hb_set_t* hb_unicode_set = hb_set_create(); + hb_face_collect_unicodes(hb_face, hb_unicode_set); + + hb_map_t* hb_unicode_to_glyph_map = hb_map_create(); + hb_face_collect_nominal_glyph_mapping(hb_face, hb_unicode_to_glyph_map, hb_unicode_set); + + std::map glyph_to_unicode_map; + hb_codepoint_t current_unicode = HB_SET_VALUE_INVALID; + while (hb_set_next(hb_unicode_set, ¤t_unicode)) { + glyph_to_unicode_map[hb_map_get(hb_unicode_to_glyph_map, current_unicode)] = current_unicode; + } + + std::cout << "readOpenTypePNG: glyph count: " << glyph_count << std::endl; + + for (unsigned int i = 0; i < glyph_count; ++i) { + hb_blob_t* png_data = hb_ot_color_glyph_reference_png(hb_font, i); + +// hb_codepoint_t unicode = hb_map_get(hb_glyph_map, i); + auto unicode = glyph_to_unicode_map[i]; + if (i < 20) { + std::cout << " glyph: " << i << " unicode: " << unicode << std::endl; + } + + + unsigned int length = 0; + auto data = hb_blob_get_data(png_data, &length); + hb_blob_destroy(png_data); + if (!data) { + std::cout << " No data! " << i << std::endl; + continue; + } + +#ifdef DEBUG_OPENTYPEUTIL + std::ofstream png_stream; + std::string filename = font_directory(hb_face) + "/glyph_" + std::to_string(unicode) + ".png"; + png_stream.open(filename); + if (!png_stream) { + std::cerr << "Failed to open PNG stream" << std::endl; + break; + } + for (unsigned int j = 0; j < length; ++j) { + png_stream << data[j]; + } + png_stream.close(); +#endif + + GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); + if (gdk_pixbuf_loader_write(loader, (guchar*)data, length, nullptr)) { + gdk_pixbuf_loader_close(loader, nullptr); + GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); + } + g_object_unref(loader); + + // auto loader2 = Gdk::PixbufLoader::create(); + // try { + // loader2->write((guint8*)data, length); // } + // catch (const Glib::FileError &e) + // { + // std::cout << "readOpenTypePNG: " << e.what() << std::endl; + // } + // catch (const Gdk::PixbufError &e) + // { + // std::cout << "readOpenTypePNG: " << e.what() << std::endl; + // } + // loader2->close(); + // auto pixbuf = loader2->get_pixbuf(); + + // pixbufs.emplace_back(pixbuf); } } diff --git a/src/libnrtype/OpenTypeUtil.h b/src/libnrtype/OpenTypeUtil.h index cdd46c0677..07a84fdecd 100644 --- a/src/libnrtype/OpenTypeUtil.h +++ b/src/libnrtype/OpenTypeUtil.h @@ -17,11 +17,17 @@ #include #include #include +#include #include #include FT_FREETYPE_H #include +#include + +namespace Gdk { +class Pixbuf; +} // Gdk /* * A set of utilities to extract data from OpenType fonts. @@ -132,6 +138,9 @@ void readOpenTypeSVGTable (hb_font_t* hb_font, std::map& glyphs, std::map& svgs); +void readOpenTypePNG (hb_font_t* hb_font, + std::vector>& pixbufs); + #endif /* !USE_PANGO_WIND32 */ #endif /* !SEEN_OPENTYPEUTIL_H */ diff --git a/src/libnrtype/font-glyph.h b/src/libnrtype/font-glyph.h index 3305cb2b9e..315c70d622 100644 --- a/src/libnrtype/font-glyph.h +++ b/src/libnrtype/font-glyph.h @@ -11,6 +11,7 @@ #define LIBNRTYPE_FONT_GLYPH_H #include +#include #include <2geom/pathvector.h> // The info for a glyph in a font. It's totally resolution- and fontsize-independent. @@ -22,6 +23,11 @@ struct FontGlyph Geom::Rect bbox_exact; // Exact bounding box, from font. Geom::Rect bbox_pick = {0.0, -0.2, 1.0, 0.8}; // Expanded bounding box (initialize to em box). (y point down.) Geom::Rect bbox_draw = {0.0, -0.2, 1.0, 0.8}; // Expanded bounding box (initialize to em box). + bool has_svg = false; // SVG (Mozzila/Adobe) + bool has_png = false; // CBLC (Google) or sbix (Apple) + bool has_layers = false; // COLRv0 (Microsoft) + bool has_paint = false; // COLRv1 + std::string unicode_name; }; #endif // LIBNRTYPE_FONT_GLYPH_H diff --git a/src/libnrtype/font-instance.cpp b/src/libnrtype/font-instance.cpp index c96ca66623..e45ce2a76b 100644 --- a/src/libnrtype/font-instance.cpp +++ b/src/libnrtype/font-instance.cpp @@ -31,10 +31,12 @@ #include #include +#include #include -#include +#include // Color fonts #include +#include #include <2geom/pathvector.h> #include <2geom/path-sink.h> @@ -47,6 +49,88 @@ * Outline extraction */ +#if HB_VERSION_ATLEAST(4,0,0) + +struct HBGeomData +{ + HBGeomData(Geom::PathBuilder &b, double s) + : builder(b) + , scale(s) + { + } + + Geom::PathBuilder &builder; + double scale; +}; + +static void hb_draw_move_to(hb_draw_funcs_t *dfuncs, // Unused + void *draw_data, + hb_draw_state_t *st, // Unused + float to_x, + float to_y, + void *user_data) // Unused +{ + HBGeomData *draw = (HBGeomData*)draw_data; + Geom::Point p(to_x, to_y); + draw->builder.moveTo(p * draw->scale); +} + +static void hb_draw_line_to(hb_draw_funcs_t *dfuncs, // Unused + void *draw_data, + hb_draw_state_t *st, // Unused + float to_x, + float to_y, + void *user_data) // Unused +{ + HBGeomData *draw = (HBGeomData*)draw_data; + Geom::Point p(to_x, to_y); + draw->builder.lineTo(p * draw->scale); +} + +static void hb_draw_quadratic_to(hb_draw_funcs_t *dfuncs, // Unused + void *draw_data, + hb_draw_state_t *st, // Unused + float control_x, + float control_y, + float to_x, + float to_y, + void *user_data) // Unused +{ + HBGeomData *draw = (HBGeomData*)draw_data; + Geom::Point p(to_x, to_y); + Geom::Point c(control_x, control_y); + draw->builder.quadTo(c * draw->scale, p * draw->scale); +} + +static void hb_draw_cubic_to(hb_draw_funcs_t *dfuncs, // Unused + void *draw_data, + hb_draw_state_t *st, // Unused + float control1_x, + float control1_y, + float control2_x, + float control2_y, + float to_x, + float to_y, + void *user_data) // Unused +{ + HBGeomData *draw = (HBGeomData*)draw_data; + Geom::Point p(to_x, to_y); + Geom::Point c1(control1_x, control1_y); + Geom::Point c2(control2_x, control2_y); + draw->builder.curveTo(c1 * draw->scale, c2 * draw->scale, p * draw->scale); +} + +static void hb_draw_close_path(hb_draw_funcs_t *dfuncs, // Unused + void *draw_data, + hb_draw_state_t *st, // Unused + void *uer_data) // Unused +{ + HBGeomData *draw = (HBGeomData*)draw_data; + draw->builder.closePath(); +} + +#else + struct FT2GeomData { FT2GeomData(Geom::PathBuilder &b, double s) @@ -105,12 +189,16 @@ static int ft2_cubic_to(FT_Vector const *control1, FT_Vector const *control2, FT return 0; } +#endif + /* * */ FontInstance::FontInstance(PangoFont *p_font, PangoFontDescription *descr) { + data = std::make_shared(); + acquire(p_font, descr); _ascent = _ascent_max = 0.8; @@ -149,23 +237,30 @@ void FontInstance::acquire(PangoFont *p_font_, PangoFontDescription *descr_) descr = descr_; hb_font_copy = nullptr; face = nullptr; - hb_face = nullptr; +#if HB_VERSION_ATLEAST(7,0,0) + data->cairo_font_face = nullptr; +#endif hb_font = pango_font_get_hb_font(p_font); // Pango owns hb_font. if (!hb_font) { release(); throw CtorException("Failed to get harfbuzz font"); } - hb_face = hb_font_get_face(hb_font); -#if HB_VERSION_ATLEAST(2,6,5) +#if HB_VERSION_ATLEAST(7,0,0) + // Used for rendering color fonts. + data->cairo_font_face = hb_cairo_font_face_create_for_font(hb_font); + if (!data->cairo_font_face) { + release(); + throw CtorException("Failed to get cairo font face"); + } +#endif + // hb_font is immutable, yet we need to act on it (with set_funcs) to extract the freetype face hb_font_copy = hb_font_create_sub_font(hb_font); hb_ft_font_set_funcs(hb_font_copy); + hb_face = hb_font_get_face(hb_font); face = hb_ft_font_lock_face(hb_font_copy); -#else - face = pango_fc_font_lock_face(PANGO_FC_FONT(p_font)); -#endif if (!face) { release(); @@ -176,16 +271,15 @@ void FontInstance::acquire(PangoFont *p_font_, PangoFontDescription *descr_) // Release the resources acquired by acquire(). void FontInstance::release() { -#if HB_VERSION_ATLEAST(2,6,5) if (hb_font_copy) { if (face) { hb_ft_font_unlock_face(hb_font_copy); } hb_font_destroy(hb_font_copy); } -#else - if (face) { - pango_fc_font_unlock_face(PANGO_FC_FONT(p_font)); +#if HB_VERSION_ATLEAST(7,0,0) + if (data->cairo_font_face) { + cairo_font_face_destroy(data->cairo_font_face); // TODO Verify this is necessary! } #endif pango_font_description_free(descr); @@ -194,17 +288,36 @@ void FontInstance::release() void FontInstance::init_face() { + std::cout << "FontInstance::init_face: " << pango_font_description_to_string(descr) << ":" << std::endl; auto hb_font = pango_font_get_hb_font(p_font); // Pango owns hb_font. assert(hb_font); // Guaranteed since already tested in acquire(). - has_svg = hb_ot_color_has_svg(hb_face); // SVG glyphs Since HB 2.1.0 + readOpenTypeTableList(hb_font, openTypeTableList); + std::cout << " OpenType Table list: "; + for (const auto& table : openTypeTableList) { + std::cout << table << ", "; + } + std::cout << std::endl; + + has_svg = hb_ot_color_has_svg(hb_face); // SVG glyphs HB 2.1.0 + has_png = hb_ot_color_has_png(hb_face); // Color png glyphs HB 2.1.0 + has_layers = hb_ot_color_has_layers(hb_face); // Has COLRv0 table. HB 2.1.0 +#if HB_VERSION_ATLEAST(7,0,0) + has_paint = hb_ot_color_has_paint(hb_face); // Has COLRv1 table. HB 7.0.0 +#endif + std::cout << " " << pango_font_description_to_string(descr) + << " Has SVG: " << std::setw(5) << std::boolalpha << has_svg + << " Has PNG: " << std::setw(5) << std::boolalpha << has_png + << " Has COLRv0: " << std::setw(5) << std::boolalpha << has_layers + << " Has COLRv1: " << std::setw(5) << std::boolalpha << has_paint + << std::endl; FT_Select_Charmap(face, ft_encoding_unicode); FT_Select_Charmap(face, ft_encoding_symbol); - data = std::make_shared(); - readOpenTypeTableList(hb_font, openTypeTableList); readOpenTypeSVGTable(hb_font, data->openTypeSVGGlyphs, data->openTypeSVGData); + std::vector> pixbufs; + readOpenTypePNG(hb_font, pixbufs); readOpenTypeFvarAxes(face, data->openTypeVarAxes); #if FREETYPE_MAJOR == 2 && FREETYPE_MINOR >= 8 // 2.8 does not seem to work even though it has some support. @@ -372,8 +485,9 @@ void FontInstance::find_font_metrics() // std::cout << "Hanging baseline: рдк: " << hanging << std::endl; FT_Done_Glyph(aglyph); } + } else { + _design_units = hb_face_get_upem(hb_face); } - // const gchar *family = pango_font_description_get_family(descr); // std::cout << "Font: " << (family?family:"null") << std::endl; // std::cout << " ascent: " << _ascent << std::endl; @@ -406,37 +520,51 @@ unsigned int FontInstance::MapUnicodeChar(gunichar c) const return res; } +// The purpose of this function is to extract and cache: +// * glyph metrics (for text layout and selection ), +// * glyph paths (for non-color fonts to allow for pattern fills, etc.). FontGlyph const *FontInstance::LoadGlyph(unsigned int glyph_id) { - if (!FT_IS_SCALABLE(face)) { - return nullptr; // bitmap font - } - if (auto it = data->glyphs.find(glyph_id); it != data->glyphs.end()) { return it->second.get(); // already loaded } - Geom::PathBuilder path_builder; + auto n_g = std::make_unique(); + + // For debugging + const unsigned int MAX_CHAR = 65; // Maximum length + 1 per OpenType spec. + char name[MAX_CHAR] = {}; + hb_font_get_glyph_name (hb_font, glyph_id, name, MAX_CHAR); + if (name) { + n_g->unicode_name = name; + } + + std::cout << "\nFontInstance::LoadGlyph: new: " << std::setw(6) << glyph_id + << " (" << std::setw(12) << name << ")" + << " " << pango_font_description_to_string(descr) + << std::endl; // Note: Bitmap only fonts (i.e. some color fonts) ignore FT_LOAD_NO_BITMAP. if (FT_Load_Glyph(face, glyph_id, FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP)) { + std::cout << "FontGlyph: Failed to load glyph: " << glyph_id + << " " << pango_font_description_to_string(descr) << std::endl; return nullptr; // error } + // Use harfbuzz value as FreeType doesn't return value for bitmap fonts. + // auto units_per_em = hb_face_get_upem(hb_face); + // Find scale, used by both metrics and paths. int x_scale = 0; int y_scale = 0; hb_font_get_scale(hb_font, &x_scale, &y_scale); + std::cout << " HB font scale " << x_scale << ", " << y_scale << std::endl; if (x_scale != y_scale) { std::cerr << "FontInstance::LoadGlyph: x scale not equal to y scale!" << std::endl; } - auto n_g = std::make_unique(); - // Find metrics ---------------------------------- - // Use harfbuzz as freetype doesn't return proper values for bitmap fonts. - n_g->h_advance = hb_font_get_glyph_h_advance (hb_font, glyph_id) / (double)x_scale; // Since HB 0.9.2 if (openTypeTableList.contains("vmtx")) { n_g->v_advance = -hb_font_get_glyph_v_advance (hb_font, glyph_id) / (double)y_scale; @@ -476,33 +604,96 @@ FontGlyph const *FontInstance::LoadGlyph(unsigned int glyph_id) // Find path vector ------------------------------ - if (face->glyph->format == ft_glyph_format_outline) { - FT_Outline_Funcs ft2_outline_funcs = { - ft2_move_to, - ft2_line_to, - ft2_conic_to, - ft2_cubic_to, - 0, 0 - }; - FT2GeomData user(path_builder, 1.0 / face->units_per_EM); - FT_Outline_Decompose(&face->glyph->outline, &ft2_outline_funcs, &user); + +#if HB_VERSION_ATLEAST(4,0,0) + if (true) { // Check if glyf table exists? + // Move this out of loop? + auto dfuncs = hb_draw_funcs_create(); + hb_draw_funcs_set_move_to_func (dfuncs, (hb_draw_move_to_func_t) hb_draw_move_to, NULL, NULL); + hb_draw_funcs_set_line_to_func (dfuncs, (hb_draw_line_to_func_t) hb_draw_line_to, NULL, NULL); + hb_draw_funcs_set_quadratic_to_func(dfuncs, (hb_draw_quadratic_to_func_t)hb_draw_quadratic_to, NULL, NULL); + hb_draw_funcs_set_cubic_to_func (dfuncs, (hb_draw_cubic_to_func_t) hb_draw_cubic_to, NULL, NULL); + hb_draw_funcs_set_close_path_func (dfuncs, (hb_draw_close_path_func_t) hb_draw_close_path, NULL, NULL); + hb_draw_funcs_make_immutable (dfuncs); + + Geom::PathBuilder path_builder_hb; + HBGeomData draw_data(path_builder_hb, 1.0/x_scale); + hb_font_draw_glyph(hb_font, glyph_id, dfuncs, &draw_data); + hb_draw_funcs_destroy(dfuncs); + + path_builder_hb.flush(); + Geom::PathVector pv = path_builder_hb.peek(); + // std::cout << "HB Path: " << pv << std::endl; + + if (!pv.empty()) { + n_g->pathvector = std::move(pv); + } } - path_builder.flush(); +#else - Geom::PathVector pv = path_builder.peek(); + if (FT_IS_SCALABLE(face)) { + // Vector + if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + FT_Outline_Funcs ft2_outline_funcs = { + ft2_move_to, + ft2_line_to, + ft2_conic_to, + ft2_cubic_to, + 0, 0 + }; + Geom::PathBuilder path_builder_ft; + FT2GeomData user(path_builder_ft, 1.0 / face->units_per_EM); + FT_Outline_Decompose(&face->glyph->outline, &ft2_outline_funcs, &user); + + path_builder.flush(); + + Geom::PathVector pv = path_builder_ft.peek(); + + // close all paths + for (auto &i : pv) { + i.close(); + } + // std::cout << "FT Path: " << pv << std::endl; - // close all paths - for (auto &i : pv) { - i.close(); + if (!pv.empty()) { + n_g->pathvector = std::move(pv); + } + } } +#endif - if (!pv.empty()) { - n_g->pathvector = std::move(pv); - } + // From pango shape.c + if (hb_ot_color_has_svg(hb_face)) { + auto blob = hb_ot_color_glyph_reference_svg (hb_face, glyph_id); // Note face! + if (blob) { + auto length = hb_blob_get_length(blob); + hb_blob_destroy(blob); + if (length > 0) { + n_g->has_svg = true; + } + } + }; + + if (hb_ot_color_has_png(hb_face)) { + auto blob = hb_ot_color_glyph_reference_png (hb_font, glyph_id); // Note face! + if (blob) { + auto length = hb_blob_get_length(blob); + hb_blob_destroy(blob); + if (length > 0) { + n_g->has_png = true; + } + } + }; + + n_g->has_layers = hb_ot_color_glyph_get_layers(hb_face, glyph_id, 0, NULL, NULL) > 0; +#if HB_VERSION_ATLEAST(7,0,0) + n_g->has_paint = hb_ot_color_glyph_has_paint(hb_face, glyph_id); +#endif auto ret = data->glyphs.emplace(glyph_id, std::move(n_g)); + std::cout << "FontInstance::LoadGlyph: exit\n" << std::endl; return ret.first->second.get(); } @@ -577,6 +768,56 @@ Geom::Rect FontInstance::BBoxDraw(unsigned int glyph_id) return g->bbox_draw; } +bool FontInstance::GlyphHasSVG(unsigned int glyph_id) +{ + auto g = LoadGlyph(glyph_id); + if (!g) { + return false; + } + + return g->has_svg; +} + +bool FontInstance::GlyphHasPNG(unsigned int glyph_id) +{ + auto g = LoadGlyph(glyph_id); + if (!g) { + return false; + } + + return g->has_png; +} + +bool FontInstance::GlyphHasLayers(unsigned int glyph_id) +{ + auto g = LoadGlyph(glyph_id); + if (!g) { + return false; + } + + return g->has_layers; +} + +bool FontInstance::GlyphHasPaint(unsigned int glyph_id) +{ + auto g = LoadGlyph(glyph_id); + if (!g) { + return false; + } + + return g->has_paint; +} + +std::string FontInstance::UnicodeName(unsigned int glyph_id) +{ + auto g = LoadGlyph(glyph_id); + if (!g) { + return std::string("Glyph missing"); + } + + return g->unicode_name; +} + Geom::PathVector const *FontInstance::PathVector(unsigned int glyph_id) { auto g = LoadGlyph(glyph_id); @@ -606,12 +847,12 @@ Inkscape::Pixbuf const *FontInstance::PixBuf(unsigned int glyph_id) Glib::ustring svg = data->openTypeSVGData[glyph_iter->second.entry_index]; // Create new viewbox which determines pixbuf size. - Glib::ustring viewbox("viewBox=\"0 "); - viewbox += std::to_string(-_design_units); + Glib::ustring viewbox("viewBox=\"0 "); // min-x + viewbox += std::to_string(-_design_units); // min-y viewbox += " "; - viewbox += std::to_string(_design_units*2); // Noto emoji leaks outside of em-box. + viewbox += std::to_string(_design_units*2); // width // Noto Emoji leaks outside of em-box. viewbox += " "; - viewbox += std::to_string(_design_units*2); + viewbox += std::to_string(_design_units*2); // height viewbox += "\""; // Search for existing viewbox diff --git a/src/libnrtype/font-instance.h b/src/libnrtype/font-instance.h index 95298c57ef..5afbe434c8 100644 --- a/src/libnrtype/font-instance.h +++ b/src/libnrtype/font-instance.h @@ -68,11 +68,23 @@ public: // Various bounding boxes. Color fonts complicate this as some types don't have paths. Geom::Rect BBoxExact(unsigned int glyph_id); // Bounding box of glyph. From Harfbuzz. - Geom::Rect BBoxPick( unsigned int glyph_id); // For picking. (Height: embox, Width: glyph advance.) - Geom::Rect BBoxDraw( unsigned int glyph_id); // Contains all inked areas including possible text decorations. + Geom::Rect BBoxPick(unsigned int glyph_id); // For picking. (Height: embox, Width: glyph advance.) + Geom::Rect BBoxDraw(unsigned int glyph_id); // Contains all inked areas including possible text decorations. +#if HB_VERSION_ATLEAST(7,0,0) + // For drawing bitmap color fonts. + cairo_font_face_t *CairoFontFace() { return data->cairo_font_face; } +#endif // Return if font has various tables. - bool FontHasSVG() const { return has_svg; }; + bool FontHasSVG() const { return has_svg; }; + bool FontHasPNG() const { return has_png; } + bool FontHasLayers() const { return has_layers; } // COLORv0 + bool FontHasPaint() const { return has_paint; } // COLORv1 HB 7.0.0 + bool GlyphHasSVG( unsigned int glyph_id); + bool GlyphHasPNG( unsigned int glyph_id); + bool GlyphHasLayers(unsigned int glyph_id); // COLORv0 + bool GlyphHasPaint( unsigned int glyph_id); // COLORv1 HB 7.0.0 + std::string UnicodeName(unsigned int glyph_id); auto const &get_opentype_varaxes() const { return data->openTypeVarAxes; } @@ -142,7 +154,10 @@ private: // as long as p_font is valid, face is too. FT_Face face; - bool has_svg = false; // SVG glyphs + bool has_svg = false; // SVG glyphs + bool has_png = false; // Color png glyphs (either CBDT or sbix tables) + bool has_layers = false; // Color vector glyphs (COLRv0 table) + bool has_paint = false; // Color vector glyphs (COLRv1 table) (HB 7.0.0) /* * Metrics @@ -168,6 +183,9 @@ private: struct Data { + // Used for color glyphs. + cairo_font_face_t *cairo_font_face; + /* * Tables */ -- GitLab