diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
commit | c853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch) | |
tree | 7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/display/nr-style.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.tar.xz inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.zip |
Adding upstream version 1.3+ds.upstream/1.3+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/display/nr-style.cpp')
-rw-r--r-- | src/display/nr-style.cpp | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/src/display/nr-style.cpp b/src/display/nr-style.cpp new file mode 100644 index 0000000..a6a0ded --- /dev/null +++ b/src/display/nr-style.cpp @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Style information for rendering. + *//* + * Authors: + * Krzysztof KosiĆski <tweenk.pl@gmail.com> + * + * Copyright (C) 2010 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "display/nr-style.h" +#include "style.h" + +#include "display/drawing-context.h" +#include "display/drawing-pattern.h" +#include "display/drawing-surface.h" + +#include "object/sp-paint-server.h" + +namespace Inkscape { + +void NRStyleData::Paint::clear() +{ + server.reset(); + type = PaintType::NONE; +} + +void NRStyleData::Paint::set(SPColor const &c) +{ + clear(); + type = PaintType::COLOR; + color = c; +} + +void NRStyleData::Paint::set(SPPaintServer *ps) +{ + clear(); + if (ps) { + type = PaintType::SERVER; + server = ps->create_drawing_paintserver(); + } +} + +void NRStyleData::Paint::set(SPIPaint const *paint) +{ + if (paint->isPaintserver()) { + SPPaintServer* server = paint->value.href->getObject(); + if (server && server->isValid()) { + set(server); + } else if (paint->colorSet) { + set(paint->value.color); + } else { + clear(); + } + } else if (paint->isColor()) { + set(paint->value.color); + } else if (paint->isNone()) { + clear(); + } else if (paint->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL || + paint->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE) { + // A marker in the defs section will result in ending up here. + // std::cerr << "NRStyleData::Paint::set: Double" << std::endl; + } else { + g_assert_not_reached(); + } +} + +NRStyleData::NRStyleData() + : fill() + , stroke() + , stroke_width(0.0) + , hairline(false) + , miter_limit(0.0) + , n_dash(0) + , dash_offset(0.0) + , fill_rule(CAIRO_FILL_RULE_EVEN_ODD) + , line_cap(CAIRO_LINE_CAP_BUTT) + , line_join(CAIRO_LINE_JOIN_MITER) + , text_decoration_line(TEXT_DECORATION_LINE_CLEAR) + , text_decoration_style(TEXT_DECORATION_STYLE_CLEAR) + , text_decoration_fill() + , text_decoration_stroke() + , text_decoration_stroke_width(0.0) + , phase_length(0.0) + , tspan_line_start(false) + , tspan_line_end(false) + , tspan_width(0) + , ascender(0) + , descender(0) + , underline_thickness(0) + , underline_position(0) + , line_through_thickness(0) + , line_through_position(0) + , font_size(0) +{ + paint_order_layer[0] = PAINT_ORDER_NORMAL; +} + +bool NRStyleData::Paint::ditherable() const +{ + return type == PaintType::SERVER && server && server->ditherable(); +} + +NRStyleData::NRStyleData(SPStyle const *style, SPStyle const *context_style) +{ + // Handle 'context-fill' and 'context-stroke': Work in progress + const SPIPaint *style_fill = &style->fill; + if (style_fill->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL) { + if (context_style) { + style_fill = &context_style->fill; + } else { + // A marker in the defs section will result in ending up here. + //std::cerr << "NRStyleData::set: 'context-fill': 'context_style' is NULL" << std::endl; + } + } else if (style_fill->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE) { + if (context_style) { + style_fill = &context_style->stroke; + } else { + //std::cerr << "NRStyleData::set: 'context-stroke': 'context_style' is NULL" << std::endl; + } + } + + fill.set(style_fill); + fill.opacity = SP_SCALE24_TO_FLOAT(style->fill_opacity.value); + + switch (style->fill_rule.computed) { + case SP_WIND_RULE_EVENODD: + fill_rule = CAIRO_FILL_RULE_EVEN_ODD; + break; + case SP_WIND_RULE_NONZERO: + fill_rule = CAIRO_FILL_RULE_WINDING; + break; + default: + g_assert_not_reached(); + } + + const SPIPaint *style_stroke = &style->stroke; + if (style_stroke->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL) { + if (context_style) { + style_stroke = &context_style->fill; + } else { + //std::cerr << "NRStyleData::set: 'context-fill': 'context_style' is NULL" << std::endl; + } + } else if (style_stroke->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE) { + if (context_style) { + style_stroke = &context_style->stroke; + } else { + //std::cerr << "NRStyleData::set: 'context-stroke': 'context_style' is NULL" << std::endl; + } + } + + stroke.set(style_stroke); + stroke.opacity = SP_SCALE24_TO_FLOAT(style->stroke_opacity.value); + stroke_width = style->stroke_width.computed; + hairline = style->stroke_extensions.hairline; + switch (style->stroke_linecap.computed) { + case SP_STROKE_LINECAP_ROUND: + line_cap = CAIRO_LINE_CAP_ROUND; + break; + case SP_STROKE_LINECAP_SQUARE: + line_cap = CAIRO_LINE_CAP_SQUARE; + break; + case SP_STROKE_LINECAP_BUTT: + line_cap = CAIRO_LINE_CAP_BUTT; + break; + default: + g_assert_not_reached(); + } + switch (style->stroke_linejoin.computed) { + case SP_STROKE_LINEJOIN_ROUND: + line_join = CAIRO_LINE_JOIN_ROUND; + break; + case SP_STROKE_LINEJOIN_BEVEL: + line_join = CAIRO_LINE_JOIN_BEVEL; + break; + case SP_STROKE_LINEJOIN_MITER: + line_join = CAIRO_LINE_JOIN_MITER; + break; + default: + g_assert_not_reached(); + } + miter_limit = style->stroke_miterlimit.value; + + n_dash = style->stroke_dasharray.values.size(); + if (n_dash > 0 && style->stroke_dasharray.is_valid()) { + dash_offset = style->stroke_dashoffset.computed; + dash.resize(n_dash); + for (int i = 0; i < n_dash; ++i) { + dash[i] = style->stroke_dasharray.values[i].computed; + } + } else { + dash_offset = 0.0; + dash.clear(); + } + + for (int i = 0; i < PAINT_ORDER_LAYERS; ++i) { + switch (style->paint_order.layer[i]) { + case SP_CSS_PAINT_ORDER_NORMAL: + paint_order_layer[i]=PAINT_ORDER_NORMAL; + break; + case SP_CSS_PAINT_ORDER_FILL: + paint_order_layer[i]=PAINT_ORDER_FILL; + break; + case SP_CSS_PAINT_ORDER_STROKE: + paint_order_layer[i]=PAINT_ORDER_STROKE; + break; + case SP_CSS_PAINT_ORDER_MARKER: + paint_order_layer[i]=PAINT_ORDER_MARKER; + break; + } + } + + text_decoration_line = TEXT_DECORATION_LINE_CLEAR; + if (style->text_decoration_line.inherit ) { text_decoration_line |= TEXT_DECORATION_LINE_INHERIT; } + if (style->text_decoration_line.underline ) { text_decoration_line |= TEXT_DECORATION_LINE_UNDERLINE + TEXT_DECORATION_LINE_SET; } + if (style->text_decoration_line.overline ) { text_decoration_line |= TEXT_DECORATION_LINE_OVERLINE + TEXT_DECORATION_LINE_SET; } + if (style->text_decoration_line.line_through) { text_decoration_line |= TEXT_DECORATION_LINE_LINETHROUGH + TEXT_DECORATION_LINE_SET; } + if (style->text_decoration_line.blink ) { text_decoration_line |= TEXT_DECORATION_LINE_BLINK + TEXT_DECORATION_LINE_SET; } + + text_decoration_style = TEXT_DECORATION_STYLE_CLEAR; + if (style->text_decoration_style.inherit ) { text_decoration_style |= TEXT_DECORATION_STYLE_INHERIT; } + if (style->text_decoration_style.solid ) { text_decoration_style |= TEXT_DECORATION_STYLE_SOLID + TEXT_DECORATION_STYLE_SET; } + if (style->text_decoration_style.isdouble) { text_decoration_style |= TEXT_DECORATION_STYLE_ISDOUBLE + TEXT_DECORATION_STYLE_SET; } + if (style->text_decoration_style.dotted ) { text_decoration_style |= TEXT_DECORATION_STYLE_DOTTED + TEXT_DECORATION_STYLE_SET; } + if (style->text_decoration_style.dashed ) { text_decoration_style |= TEXT_DECORATION_STYLE_DASHED + TEXT_DECORATION_STYLE_SET; } + if (style->text_decoration_style.wavy ) { text_decoration_style |= TEXT_DECORATION_STYLE_WAVY + TEXT_DECORATION_STYLE_SET; } + + /* FIXME + The meaning of text-decoration-color in CSS3 for SVG is ambiguous (2014-05-06). Set + it for fill, for stroke, for both? Both would seem like the obvious choice but what happens + is that for text which is just fill (very common) it makes the lines fatter because it + enables stroke on the decorations when it wasn't present on the text. That contradicts the + usual behavior where the text and decorations by default have the same fill/stroke. + + The behavior here is that if color is defined it is applied to text_decoration_fill/stroke + ONLY if the corresponding fill/stroke is also present. + + Hopefully the standard will be clarified to resolve this issue. + */ + + // Unless explicitly set on an element, text decoration is inherited from + // closest ancestor where 'text-decoration' was set. That is, setting + // 'text-decoration' on an ancestor fixes the fill and stroke of the + // decoration to the fill and stroke values of that ancestor. + auto style_td = style; + if (style->text_decoration.style_td) style_td = style->text_decoration.style_td; + text_decoration_stroke.opacity = SP_SCALE24_TO_FLOAT(style_td->stroke_opacity.value); + text_decoration_stroke_width = style_td->stroke_width.computed; + + // Priority is given in order: + // * text_decoration_fill + // * text_decoration_color (only if fill set) + // * fill + if (style_td->text_decoration_fill.set) { + text_decoration_fill.set(&(style_td->text_decoration_fill)); + } else if (style_td->text_decoration_color.set) { + if(style->fill.isPaintserver() || style->fill.isColor()) { + // SVG sets color specifically + text_decoration_fill.set(style->text_decoration_color.value.color); + } else { + // No decoration fill because no text fill + text_decoration_fill.clear(); + } + } else { + // Pick color/pattern from text + text_decoration_fill.set(&style_td->fill); + } + + if (style_td->text_decoration_stroke.set) { + text_decoration_stroke.set(&style_td->text_decoration_stroke); + } else if (style_td->text_decoration_color.set) { + if(style->stroke.isPaintserver() || style->stroke.isColor()) { + // SVG sets color specifically + text_decoration_stroke.set(style->text_decoration_color.value.color); + } else { + // No decoration stroke because no text stroke + text_decoration_stroke.clear(); + } + } else { + // Pick color/pattern from text + text_decoration_stroke.set(&style_td->stroke); + } + + if (text_decoration_line != TEXT_DECORATION_LINE_CLEAR) { + phase_length = style->text_decoration_data.phase_length; + tspan_line_start = style->text_decoration_data.tspan_line_start; + tspan_line_end = style->text_decoration_data.tspan_line_end; + tspan_width = style->text_decoration_data.tspan_width; + ascender = style->text_decoration_data.ascender; + descender = style->text_decoration_data.descender; + underline_thickness = style->text_decoration_data.underline_thickness; + underline_position = style->text_decoration_data.underline_position; + line_through_thickness = style->text_decoration_data.line_through_thickness; + line_through_position = style->text_decoration_data.line_through_position; + font_size = style->font_size.computed; + } + + text_direction = style->direction.computed; +} + +auto NRStyle::preparePaint(Inkscape::DrawingContext &dc, Inkscape::RenderContext &rc, Geom::IntRect const &area, Geom::OptRect const &paintbox, Inkscape::DrawingPattern const *pattern, NRStyleData::Paint const &paint, CachedPattern const &cp) const -> CairoPatternUniqPtr +{ + if (paint.type == NRStyleData::PaintType::SERVER && pattern) { + // If a DrawingPattern, then always regenerate the pattern, because it may depend on 'area'. + // Even if not, regenerating the pattern is a no-op because DrawingPattern has a cache. + return CairoPatternUniqPtr(pattern->renderPattern(rc, area, paint.opacity, dc.surface()->device_scale())); + } + + // Otherwise, init or re-use cached pattern. + cp.inited.init([&, this] { + // Handle remaining non-DrawingPattern cases. + switch (paint.type) { + case NRStyleData::PaintType::SERVER: + if (paint.server) { + cp.pattern = CairoPatternUniqPtr(paint.server->create_pattern(dc.raw(), paintbox, paint.opacity)); + } else { + std::cerr << "Null pattern detected" << std::endl; + cp.pattern = CairoPatternUniqPtr(cairo_pattern_create_rgba(0, 0, 0, 0)); + } + break; + case NRStyleData::PaintType::COLOR: { + auto const &c = paint.color.v.c; + cp.pattern = CairoPatternUniqPtr(cairo_pattern_create_rgba(c[0], c[1], c[2], paint.opacity)); + break; + } + default: + cp.pattern.reset(); + break; + } + }); + + return copy(cp.pattern); +} + +void NRStyle::set(NRStyleData &&data_) +{ + data = std::move(data_); + invalidate(); +} + +auto NRStyle::prepareFill(Inkscape::DrawingContext &dc, Inkscape::RenderContext &rc, Geom::IntRect const &area, Geom::OptRect const &paintbox, Inkscape::DrawingPattern const *pattern) const -> CairoPatternUniqPtr +{ + return preparePaint(dc, rc, area, paintbox, pattern, data.fill, fill_pattern); +} + +auto NRStyle::prepareStroke(Inkscape::DrawingContext &dc, Inkscape::RenderContext &rc, Geom::IntRect const &area, Geom::OptRect const &paintbox, Inkscape::DrawingPattern const *pattern) const -> CairoPatternUniqPtr +{ + return preparePaint(dc, rc, area, paintbox, pattern, data.stroke, stroke_pattern); +} + +auto NRStyle::prepareTextDecorationFill(Inkscape::DrawingContext &dc, Inkscape::RenderContext &rc, Geom::IntRect const &area, Geom::OptRect const &paintbox, Inkscape::DrawingPattern const *pattern) const -> CairoPatternUniqPtr +{ + return preparePaint(dc, rc, area, paintbox, pattern, data.text_decoration_fill, text_decoration_fill_pattern); +} + +auto NRStyle::prepareTextDecorationStroke(Inkscape::DrawingContext &dc, Inkscape::RenderContext &rc, Geom::IntRect const &area, Geom::OptRect const &paintbox, Inkscape::DrawingPattern const *pattern) const -> CairoPatternUniqPtr +{ + return preparePaint(dc, rc, area, paintbox, pattern, data.text_decoration_stroke, text_decoration_stroke_pattern); +} + +void NRStyle::applyFill(Inkscape::DrawingContext &dc, CairoPatternUniqPtr const &cp) const +{ + dc.setSource(cp.get()); + dc.setFillRule(data.fill_rule); +} + +void NRStyle::applyTextDecorationFill(Inkscape::DrawingContext &dc, CairoPatternUniqPtr const &cp) const +{ + dc.setSource(cp.get()); + // Fill rule does not matter, no intersections. +} + +void NRStyle::applyStroke(Inkscape::DrawingContext &dc, CairoPatternUniqPtr const &cp) const +{ + dc.setSource(cp.get()); + if (data.hairline) { + dc.setHairline(); + } else { + dc.setLineWidth(data.stroke_width); + } + dc.setLineCap(data.line_cap); + dc.setLineJoin(data.line_join); + dc.setMiterLimit(data.miter_limit); + cairo_set_dash(dc.raw(), data.dash.empty() ? nullptr : data.dash.data(), data.dash.empty() ? 0 : data.n_dash, data.dash_offset); // fixme +} + +void NRStyle::applyTextDecorationStroke(Inkscape::DrawingContext &dc, CairoPatternUniqPtr const &cp) const +{ + dc.setSource(cp.get()); + if (data.hairline) { + dc.setHairline(); + } else { + dc.setLineWidth(data.text_decoration_stroke_width); + } + dc.setLineCap(CAIRO_LINE_CAP_BUTT); + dc.setLineJoin(CAIRO_LINE_JOIN_MITER); + dc.setMiterLimit(data.miter_limit); + cairo_set_dash(dc.raw(), nullptr, 0, 0.0); // fixme (no dash) +} + +void NRStyle::invalidate() +{ + // force pattern update + fill_pattern.reset(); + stroke_pattern.reset(); + text_decoration_fill_pattern.reset(); + text_decoration_stroke_pattern.reset(); +} + +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |