diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 32fecef5c4d94d09d61aaec6a2a5d17e29ec526e..83bf9a251a98ad553fce3739bde6b3dd82b1ce59 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -214,7 +214,7 @@ inkscape:macos: MSYSTEM: UCRT64 CCACHE_BASEDIR: $CI_PROJECT_DIR/src # ICW = inkscape-ci-windows, https://gitlab.com/inkscape/infra/inkscape-ci-windows - ICW_VERSION: 157 + ICW_VERSION: 163 ICW_NAME: ink$ICW_VERSION ICW_RELEASE_URL: $CI_API_V4_URL/projects/46863172/packages/generic/windows/r$ICW_VERSION/${ICW_NAME}_$MSYSTEM.wim before_script: diff --git a/CMakeScripts/DefineDependsandFlags.cmake b/CMakeScripts/DefineDependsandFlags.cmake index f3531237e563e7cc49cf9c88f96822df730587a1..dddbde268a726bdbefd3b4927ef30f016eb435fe 100644 --- a/CMakeScripts/DefineDependsandFlags.cmake +++ b/CMakeScripts/DefineDependsandFlags.cmake @@ -128,7 +128,8 @@ endif() find_package(PkgConfig REQUIRED) pkg_check_modules(INKSCAPE_DEP REQUIRED - harfbuzz>=2.6.5 + harfbuzz>=7.0.0 + harfbuzz-cairo pangocairo>=1.44 pangoft2 fontconfig diff --git a/CMakeScripts/InstallMSYS2.cmake b/CMakeScripts/InstallMSYS2.cmake index 7694e4ed2506264c69c946e225c234507217e1c1..9da74da1d9085bf7086dacb24767711ddf4a8052 100644 --- a/CMakeScripts/InstallMSYS2.cmake +++ b/CMakeScripts/InstallMSYS2.cmake @@ -65,6 +65,7 @@ if(WIN32) ${MINGW_BIN}/libspelling-1-[0-9]*.dll ${MINGW_BIN}/libgtksourceview-5-[0-9]*.dll ${MINGW_BIN}/libharfbuzz-[0-9]*.dll + ${MINGW_BIN}/libharfbuzz-cairo-[0-9]*.dll ${MINGW_BIN}/libharfbuzz-subset-[0-9]*.dll ${MINGW_BIN}/libheif.dll ${MINGW_BIN}/libiconv-[0-9]*.dll diff --git a/buildtools/msys2installdeps.sh b/buildtools/msys2installdeps.sh index 14a504f19d9d74234e95063b0c973a18e8489781..db78e4d295b53b73334c69b0ce8c8232fe6333f9 100644 --- a/buildtools/msys2installdeps.sh +++ b/buildtools/msys2installdeps.sh @@ -65,6 +65,7 @@ $ARCH-gsl \ $ARCH-gtk4 \ $ARCH-gtk-doc \ $ARCH-gtkmm-4.0 \ +$ARCH-harfbuzz-cairo \ $ARCH-libxslt # install packaging tools (required for dist-win-* targets) diff --git a/src/display/drawing-context.h b/src/display/drawing-context.h index ad84fdaf68f12540413f7484608e3cd6131874b3..355eabd772bd1d8a13f949f9d0594aacbbc418bc 100644 --- a/src/display/drawing-context.h +++ b/src/display/drawing-context.h @@ -100,6 +100,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 c1dbabc445a451132c662911b9f69a228dd9c89f..bb7917ce604560c3080c70390d0203d89ef86591 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,52 @@ 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. + // + // Spaces will trigger these error messages. + // + // 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 +124,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,12 +162,6 @@ 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; - return STATE_ALL; } @@ -411,6 +445,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; } @@ -551,7 +586,9 @@ 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. + const char* color_font_debug = std::getenv("COLOR_FONT_DEBUG"); + + // Accumulate the path that represents the glyphs and/or draw color glyphs. for (auto &i : _children) { auto g = cast(&i); if (!g) throw InvalidItemException(); @@ -563,75 +600,93 @@ 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(); - } - { - 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(); + 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(0.0, 0.0, 1.0, 1.0); // Blue + dc.stroke(); + } + { + 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(); + } + { + 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_append_path(dc.raw(), path_copy); + cairo_path_destroy(path_copy); + // End debug boxes. } - { - 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(); + + if (color_font_debug) { + 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; } - cairo_append_path(dc.raw(), path_copy); - cairo_path_destroy(path_copy); - // End debug boxes. -#endif - 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. + if (g->has_svg && g->pixbuf) { + // Cairo does not support SVG color fonts. + Inkscape::DrawingContext::Save save(dc); + + // pixbuf is in font design units, scale to embox. + double scale = g->design_units; + if (scale <= 0) scale = 1000; + dc.translate(g->bbox_draw.corner(3)); + dc.scale(1.0 / scale, -1.0 / scale); + dc.setSource(g->pixbuf->getSurfaceRaw(), 0, 0); + dc.paint(1); + } else if (g->has_png || g->has_layers || g->has_paint) { + // Other color fonts. + Inkscape::DrawingContext::Save save(dc); - if (g->pixbuf) { - { - // pixbuf is in font design units, scale to embox. - double scale = g->design_units; - if (scale <= 0) scale = 1000; - Inkscape::DrawingContext::Save save(dc); - dc.translate(g->bbox_draw.corner(3)); - dc.scale(1.0 / scale, -1.0 / scale); - dc.setSource(g->pixbuf->getSurfaceRaw(), 0, 0); - dc.paint(1); + 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 (has_fill) { + // Set foreground color if fill is solid. + if (cairo_pattern_get_type(has_fill.get()) == CAIRO_PATTERN_TYPE_SOLID) { + double r, g, b, a; + cairo_pattern_get_rgba(has_fill.get(), &r, &g, &b, &a); + dc.setSource(has_fill.get()); // using cairo_font_options_set_custom_palette_color() works for everything but foreground (0xffff). } - } else { - dc.path(*g->pathvec); } + dc.showGlyphs(&glyph, 1); + } else if (g->pathvec) { + // Non-color fonts, we fill and stroke ourselves. + dc.path(*g->pathvec); + } else { + std::cerr << "DrawingText::_renderItem: No glyph data! " + << std::setw(6) << g->_glyph << std::endl; } } - // 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 3409fe464706fa4d822beb292a41600cab68ca4c..ece1ee1abd997766f337af3fdcd3fcfa670d1e1f 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 be77c7f1885fd590a1406bb710495cfc195b717c..399ed54d5219011b7f41d94a7e54407086800a1a 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; @@ -1618,6 +1612,11 @@ unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const ¶->pango_items[pango_item_index].item->analysis, new_span.glyph_string); + const char* color_font_debug = std::getenv("COLOR_FONT_DEBUG"); + if (color_font_debug) { + 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 b80968fbe88c206e60659e8509cc8c573fcc0230..700063acde7f9ba4b736ba115d1672b67c7ba142 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 @@ -58,6 +68,37 @@ 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; +} + +#ifdef DEBUG_OPENTYPEUTIL +// 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; +} +#endif + void readOpenTypeTableList(hb_font_t* hb_font, std::unordered_set& list) { hb_face_t* hb_face = hb_font_get_face (hb_font); @@ -74,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. @@ -382,7 +425,32 @@ void readOpenTypeSVGTable(hb_font_t* hb_font, hb_face_t* hb_face = hb_font_get_face (hb_font); +#ifdef DEBUG_OPENTYPEUTIL + // 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); @@ -394,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; @@ -454,21 +524,125 @@ void readOpenTypeSVGTable(hb_font_t* hb_font, static auto regex = Glib::Regex::create("(id=\"\\s*glyph\\d+\\s*\")", Glib::Regex::CompileFlags::OPTIMIZE); svg = regex->replace(Glib::UStringView(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)) { + const unsigned int buffer_size = 64; + char font_family_c[buffer_size]; + unsigned int buffer_size_io = buffer_size; + hb_language_t lang = hb_language_from_string("en", -1); + std::string font_family; + auto length = hb_ot_name_get_utf8(hb_face, HB_OT_NAME_ID_FONT_FAMILY, lang, &buffer_size_io, font_family_c); + if (length > 0) { + font_family = font_family_c; + } + std::cerr << "No PNG in font face! " << font_family << 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 7d7189604cb83c75d95b95b26c817d24c66783a0..a1d41e2175eea0eb85bb1f5fbbc8249189072ffa 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. @@ -107,6 +113,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 3305cb2b9e303e6e4b19529fe09acb955cb1cd2b..315c70d6223bdeb9a3f9771afd0c897468fc04c4 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 31566a855dbf8212930d053773d7d49adb0372d9..b86643caba85e7f57abb6f90f492ff054379c624 100644 --- a/src/libnrtype/font-instance.cpp +++ b/src/libnrtype/font-instance.cpp @@ -30,14 +30,18 @@ #include FT_GLYPH_H #include FT_MULTIPLE_MASTERS_H +#include +#include +#include +#include +#include // Color fonts + #include #include -#include -#include -#include -#include -#include <2geom/path-sink.h> +#include + #include <2geom/pathvector.h> +#include <2geom/path-sink.h> #include "display/cairo-utils.h" // Inkscape::Pixbuf #include "libnrtype/font-glyph.h" @@ -47,70 +51,88 @@ * Outline extraction */ -struct FT2GeomData +struct HBGeomData { - FT2GeomData(Geom::PathBuilder &b, double s) + HBGeomData(Geom::PathBuilder &b, double s) : builder(b) - , last(0, 0) , scale(s) { } Geom::PathBuilder &builder; - Geom::Point last; double scale; }; -// outline as returned by freetype -static int ft2_move_to(FT_Vector const *to, void * i_user) +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 { - FT2GeomData *user = (FT2GeomData*)i_user; - Geom::Point p(to->x, to->y); - // printf("m t=%f %f\n",p[0],p[1]); - user->builder.moveTo(p * user->scale); - user->last = p; - return 0; + HBGeomData *draw = (HBGeomData*)draw_data; + Geom::Point p(to_x, to_y); + draw->builder.moveTo(p * draw->scale); } -static int ft2_line_to(FT_Vector const *to, void *i_user) +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 { - FT2GeomData *user = (FT2GeomData*)i_user; - Geom::Point p(to->x, to->y); - // printf("l t=%f %f\n",p[0],p[1]); - user->builder.lineTo(p * user->scale); - user->last = p; - return 0; + HBGeomData *draw = (HBGeomData*)draw_data; + Geom::Point p(to_x, to_y); + draw->builder.lineTo(p * draw->scale); } -static int ft2_conic_to(FT_Vector const *control, FT_Vector const *to, void *i_user) +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 { - FT2GeomData *user = (FT2GeomData*)i_user; - Geom::Point p(to->x, to->y), c(control->x, control->y); - user->builder.quadTo(c * user->scale, p * user->scale); - // printf("b c=%f %f t=%f %f\n",c[0],c[1],p[0],p[1]); - user->last = p; - return 0; + 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 int ft2_cubic_to(FT_Vector const *control1, FT_Vector const *control2, FT_Vector const *to, void *i_user) +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 { - FT2GeomData *user = (FT2GeomData*)i_user; - Geom::Point p(to->x, to->y); - Geom::Point c1(control1->x, control1->y); - Geom::Point c2(control2->x, control2->y); - // printf("c c1=%f %f c2=%f %f t=%f %f\n",c1[0],c1[1],c2[0],c2[1],p[0],p[1]); - //user->theP->CubicTo(p,3*(c1-user->last),3*(p-c2)); - user->builder.curveTo(c1 * user->scale, c2 * user->scale, p * user->scale); - user->last = p; - return 0; + 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(); +} FontInstance::FontInstance(PangoFont *p_font, PangoFontDescription *descr) { + data = std::make_shared(); + acquire(p_font, descr); _ascent = _ascent_max = 0.8; @@ -149,20 +171,27 @@ void FontInstance::acquire(PangoFont *p_font_, PangoFontDescription *descr_) descr = descr_; hb_font_copy = nullptr; face = nullptr; - hb_face = nullptr; + data->cairo_font_face = nullptr; 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); + + // 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"); + } // 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); - face = hb_ft_font_lock_face(hb_font_copy); + hb_face = hb_font_get_face(hb_font); + face = hb_ft_font_lock_face(hb_font_copy); if (!face) { release(); throw CtorException("Failed to get freetype face"); @@ -179,23 +208,60 @@ void FontInstance::release() hb_font_destroy(hb_font_copy); } + if (data->cairo_font_face) { + cairo_font_face_destroy(data->cairo_font_face); // TODO Verify this is necessary! + } + pango_font_description_free(descr); g_object_unref(p_font); } void FontInstance::init_face() { + const char* color_font_debug = std::getenv("COLOR_FONT_DEBUG"); + if (color_font_debug) { + 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 + if (color_font_debug) { + 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 + has_paint = hb_ot_color_has_paint(hb_face); // Has COLRv1 table. HB 7.0.0 + + if (color_font_debug) { + 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); + if (has_svg) { + readOpenTypeSVGTable(hb_font, data->openTypeSVGGlyphs, data->openTypeSVGData); + } + + if (color_font_debug) { + std::vector> pixbufs; + if (has_png) { + 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. @@ -363,8 +429,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; @@ -413,23 +480,50 @@ 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 (glyph_id == 0xfffffff) { + // Pango value for zero-width empty glyph that we can ignore (e.g. 0xFE0F, Emoji variant selector). + return nullptr; } 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(); + + const char* color_font_debug = std::getenv("COLOR_FONT_DEBUG"); + const char* font_paths_debug = std::getenv("FONT_PATHS_DEBUG"); + + if (color_font_debug) { + // 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::cerr << "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; @@ -438,12 +532,8 @@ FontGlyph const *FontInstance::LoadGlyph(unsigned int glyph_id) 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; @@ -483,30 +573,57 @@ 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); - } - - path_builder.flush(); - - Geom::PathVector pv = path_builder.peek(); + 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(); + if (font_paths_debug) { + std::cout << "HB Path: " << pv << std::endl; + } - // close all paths - for (auto &i : pv) { - i.close(); + if (!pv.empty()) { + n_g->pathvector = std::move(pv); + } } - 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; + n_g->has_paint = hb_ot_color_glyph_has_paint(hb_face, glyph_id); auto ret = data->glyphs.emplace(glyph_id, std::move(n_g)); @@ -607,6 +724,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); diff --git a/src/libnrtype/font-instance.h b/src/libnrtype/font-instance.h index 3cd8f45ead3e9c9bbbb2c3dda121d1bb55ce0df9..30dc3314ebca0061bcfc7effff7dff1f3697d3cc 100644 --- a/src/libnrtype/font-instance.h +++ b/src/libnrtype/font-instance.h @@ -68,11 +68,22 @@ 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. + + // For drawing bitmap color fonts. + cairo_font_face_t *CairoFontFace() { return data->cairo_font_face; } // 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; } @@ -154,7 +165,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 @@ -180,6 +194,9 @@ private: struct Data { + // Used for color glyphs. + cairo_font_face_t *cairo_font_face; + /* * Tables */ diff --git a/testfiles/rendering_tests/CMakeLists.txt b/testfiles/rendering_tests/CMakeLists.txt index 942d009e77571f86b4b770f926e96dc83f461cf0..6a84c48f8c93caa43657c1211d07e760187cfadd 100644 --- a/testfiles/rendering_tests/CMakeLists.txt +++ b/testfiles/rendering_tests/CMakeLists.txt @@ -75,5 +75,8 @@ if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) # .otf font with compressed SVG glyphs add_rendering_test(text-gzipped-svg-glyph FUZZ 0.03) endif() + # custom .otf font with SVG glyphs for testing various edge cases -add_rendering_test(text-svg-glyph-custom FUZZ 0.3) \ No newline at end of file +add_rendering_test(text-svg-glyph-custom FUZZ 0.3) + +add_rendering_test(color-font-test FUZZ 0.1) diff --git a/testfiles/rendering_tests/README b/testfiles/rendering_tests/README index 6ebcb4ca004f1d715eb63b940886560e1f128fa0..a693fd639803229c187229b04b919b674a4628d9 100644 --- a/testfiles/rendering_tests/README +++ b/testfiles/rendering_tests/README @@ -2,12 +2,10 @@ HOWTO # Add a rendering test: - create the svg file - - 0.92: - - inkscape .svg -d 96 -e expected_rendering/.png - - inkscape .svg -d 384 -e expected_rendering/-large.png - - 1.0: - - inkscape -d 96 --export-filename=expected_rendering/.png .svg - - inkscape -d 384 --export-filename=expected_rendering/-large.png .svg + + - inkscape -d 96 --export-png-use-dithering false --export-filename=expected_rendering/.png .svg + - inkscape -d 384 --export-png-use-dithering false --export-filename=expected_rendering/-large.png .svg + - add the test in CMakeLists.txt - use stable if possible to generate the reference png files - git add .svg expected_rendering/-large.png expected_rendering/.png diff --git a/testfiles/rendering_tests/color-font-test.svg b/testfiles/rendering_tests/color-font-test.svg new file mode 100644 index 0000000000000000000000000000000000000000..95f47be1a978a458415fc116de0cce2758566f41 --- /dev/null +++ b/testfiles/rendering_tests/color-font-test.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + No Color + SVG + COLRv0 + COLRv1 + CBDT - Disabled + + + ab cd + ab cd + ab cd + ab cd + + + diff --git a/testfiles/rendering_tests/expected_rendering/color-font-test-large.png b/testfiles/rendering_tests/expected_rendering/color-font-test-large.png new file mode 100644 index 0000000000000000000000000000000000000000..04abe787066fcf3654854738c12cf29bbe129572 Binary files /dev/null and b/testfiles/rendering_tests/expected_rendering/color-font-test-large.png differ diff --git a/testfiles/rendering_tests/expected_rendering/color-font-test.png b/testfiles/rendering_tests/expected_rendering/color-font-test.png new file mode 100644 index 0000000000000000000000000000000000000000..a4938e38bc93e2dc6a1617164c0829b3d163bbb7 Binary files /dev/null and b/testfiles/rendering_tests/expected_rendering/color-font-test.png differ diff --git a/testfiles/rendering_tests/expected_rendering/test-peppercarrot-text.png b/testfiles/rendering_tests/expected_rendering/test-peppercarrot-text.png index 99a4c4675e3f155bf13f379a57e89167958eaefd..6f41c7357d72b7692beefdd0c6e2b8ac1ed5fbb5 100644 Binary files a/testfiles/rendering_tests/expected_rendering/test-peppercarrot-text.png and b/testfiles/rendering_tests/expected_rendering/test-peppercarrot-text.png differ diff --git a/testfiles/rendering_tests/fonts/ColorTest.ttf b/testfiles/rendering_tests/fonts/ColorTest.ttf new file mode 100644 index 0000000000000000000000000000000000000000..24e9ca6ba3193653ff49b0d8821c59b516d4183d Binary files /dev/null and b/testfiles/rendering_tests/fonts/ColorTest.ttf differ diff --git a/testfiles/rendering_tests/fonts/ColorTest_CBDT.ttf b/testfiles/rendering_tests/fonts/ColorTest_CBDT.ttf new file mode 100644 index 0000000000000000000000000000000000000000..39565129332b986f53adb34b86238ad238c70b7e Binary files /dev/null and b/testfiles/rendering_tests/fonts/ColorTest_CBDT.ttf differ diff --git a/testfiles/rendering_tests/fonts/ColorTest_COLRv0.ttf b/testfiles/rendering_tests/fonts/ColorTest_COLRv0.ttf new file mode 100644 index 0000000000000000000000000000000000000000..28d4bbbd214902f3b1599139afd26d509ecb31cc Binary files /dev/null and b/testfiles/rendering_tests/fonts/ColorTest_COLRv0.ttf differ diff --git a/testfiles/rendering_tests/fonts/ColorTest_COLRv1.ttf b/testfiles/rendering_tests/fonts/ColorTest_COLRv1.ttf new file mode 100644 index 0000000000000000000000000000000000000000..327be40b9bd102934d7dd2a568d4aa157c910d37 Binary files /dev/null and b/testfiles/rendering_tests/fonts/ColorTest_COLRv1.ttf differ diff --git a/testfiles/rendering_tests/fonts/ColorTest_SVG.ttf b/testfiles/rendering_tests/fonts/ColorTest_SVG.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e62c77be3444b5ed8d9062482c368a5b24576d1a Binary files /dev/null and b/testfiles/rendering_tests/fonts/ColorTest_SVG.ttf differ