summaryrefslogtreecommitdiffstats
path: root/src/display/nr-style.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
commitc853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch)
tree7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/display/nr-style.cpp
parentInitial commit. (diff)
downloadinkscape-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.cpp423
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 :