From cca66b9ec4e494c1d919bff0f71a820d8afab1fa Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:24:48 +0200 Subject: Adding upstream version 1.2.2. Signed-off-by: Daniel Baumann --- src/object/viewbox.cpp | 328 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 src/object/viewbox.cpp (limited to 'src/object/viewbox.cpp') diff --git a/src/object/viewbox.cpp b/src/object/viewbox.cpp new file mode 100644 index 0000000..eee0fc5 --- /dev/null +++ b/src/object/viewbox.cpp @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * viewBox helper class, common code used by root, symbol, marker, pattern, image, view + * + * Authors: + * Lauris Kaplinski (code extracted from symbol.cpp) + * Tavmjong Bah + * Johan Engelen + * + * Copyright (C) 2013-2014 Tavmjong Bah, authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + * + */ + +#include <2geom/transforms.h> + +#include "viewbox.h" +#include "enums.h" +#include "sp-item.h" +#include "svg/stringstream.h" + +#include + +namespace { +std::map const ASPECT_ALIGN_STRINGS{ + {SP_ASPECT_NONE, "none"}, // + {SP_ASPECT_XMIN_YMIN, "xMinYMin"}, // + {SP_ASPECT_XMID_YMIN, "xMidYMin"}, // + {SP_ASPECT_XMAX_YMIN, "xMaxYMin"}, // + {SP_ASPECT_XMIN_YMID, "xMinYMid"}, // + {SP_ASPECT_XMID_YMID, "xMidYMid"}, // + {SP_ASPECT_XMAX_YMID, "xMaxYMid"}, // + {SP_ASPECT_XMIN_YMAX, "xMinYMax"}, // + {SP_ASPECT_XMID_YMAX, "xMidYMax"}, // + {SP_ASPECT_XMAX_YMAX, "xMaxYMax"}, // +}; +} + +SPViewBox::SPViewBox() + : viewBox_set(false) + , viewBox() + , aspect_set(false) + , aspect_align(SP_ASPECT_XMID_YMID) // Default per spec + , aspect_clip(SP_ASPECT_MEET) + , c2p(Geom::identity()) +{ +} + +void SPViewBox::set_viewBox(const gchar* value) { + + if (value) { + gchar *eptr = const_cast(value); // const-cast necessary because of const-incorrect interface definition of g_ascii_strtod + + double x = g_ascii_strtod (eptr, &eptr); + + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) { + eptr++; + } + + double y = g_ascii_strtod (eptr, &eptr); + + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) { + eptr++; + } + + double width = g_ascii_strtod (eptr, &eptr); + + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) { + eptr++; + } + + double height = g_ascii_strtod (eptr, &eptr); + + while (*eptr && ((*eptr == ',') || (*eptr == ' '))) { + eptr++; + } + + if ((width > 0) && (height > 0)) { + /* Set viewbox */ + this->viewBox = Geom::Rect::from_xywh(x, y, width, height); + this->viewBox_set = true; + } else { + this->viewBox_set = false; + } + } else { + this->viewBox_set = false; + } + + // The C++ way? -- not necessarily using iostreams + // std::string sv( value ); + // std::replace( sv.begin(), sv.end(), ',', ' '); + // std::stringstream ss( sv ); + // double x, y, width, height; + // ss >> x >> y >> width >> height; +} + +void SPViewBox::set_preserveAspectRatio(const gchar* value) { + + /* Do setup before, so we can use break to escape */ + this->aspect_set = false; + this->aspect_align = SP_ASPECT_XMID_YMID; // Default per spec + this->aspect_clip = SP_ASPECT_MEET; + + if (value) { + const gchar *p = value; + + while (*p && (*p == 32)) { + p += 1; + } + + if (!*p) { + return; + } + + const gchar *e = p; + + while (*e && (*e != 32)) { + e += 1; + } + + int len = e - p; + + if (len > 8) { // Can't have buffer overflow as 8 < 256 + return; + } + + gchar c[256]; + memcpy (c, value, len); + + c[len] = 0; + + /* Now the actual part */ + unsigned int align = SP_ASPECT_NONE; + if (!strcmp (c, "none")) { + align = SP_ASPECT_NONE; + } else if (!strcmp (c, "xMinYMin")) { + align = SP_ASPECT_XMIN_YMIN; + } else if (!strcmp (c, "xMidYMin")) { + align = SP_ASPECT_XMID_YMIN; + } else if (!strcmp (c, "xMaxYMin")) { + align = SP_ASPECT_XMAX_YMIN; + } else if (!strcmp (c, "xMinYMid")) { + align = SP_ASPECT_XMIN_YMID; + } else if (!strcmp (c, "xMidYMid")) { + align = SP_ASPECT_XMID_YMID; + } else if (!strcmp (c, "xMaxYMid")) { + align = SP_ASPECT_XMAX_YMID; + } else if (!strcmp (c, "xMinYMax")) { + align = SP_ASPECT_XMIN_YMAX; + } else if (!strcmp (c, "xMidYMax")) { + align = SP_ASPECT_XMID_YMAX; + } else if (!strcmp (c, "xMaxYMax")) { + align = SP_ASPECT_XMAX_YMAX; + } else { + return; + } + + unsigned int clip = SP_ASPECT_MEET; + + while (*e && (*e == 32)) { + e += 1; + } + + if (*e) { + if (!strcmp (e, "meet")) { + clip = SP_ASPECT_MEET; + } else if (!strcmp (e, "slice")) { + clip = SP_ASPECT_SLICE; + } else { + return; + } + } + + this->aspect_set = true; + this->aspect_align = align; + this->aspect_clip = clip; + } +} + +// Apply scaling from viewbox +void SPViewBox::apply_viewbox(const Geom::Rect& in, double scale_none) { + + /* Determine actual viewbox in viewport coordinates */ + // scale_none is the scale that would apply if the viewbox and page size are same size + // it is passed here because it is a double-precision variable, while 'in' is originally float + double x = 0.0; + double y = 0.0; + double scale_x = in.width() / this->viewBox.width(); + double scale_y = in.height() / this->viewBox.height(); + double scale_uniform = 1.0; // used only if scaling is uniform + + if (Geom::are_near(scale_x / scale_y, 1.0, Geom::EPSILON)) { + // scaling is already uniform, reduce numerical error + scale_uniform = (scale_x + scale_y)/2.0; + if (Geom::are_near(scale_uniform / scale_none, 1.0, Geom::EPSILON)) + scale_uniform = scale_none; // objects are same size, reduce numerical error + scale_x = scale_uniform; + scale_y = scale_uniform; + } else if (this->aspect_align != SP_ASPECT_NONE) { + // scaling is not uniform, but force it to be + scale_uniform = (this->aspect_clip == SP_ASPECT_MEET) ? MIN (scale_x, scale_y) : MAX (scale_x, scale_y); + scale_x = scale_uniform; + scale_y = scale_uniform; + double width = this->viewBox.width() * scale_uniform; + double height = this->viewBox.height() * scale_uniform; + + /* Now place viewbox to requested position */ + switch (this->aspect_align) { + case SP_ASPECT_XMIN_YMIN: + break; + case SP_ASPECT_XMID_YMIN: + x = 0.5 * (in.width() - width); + break; + case SP_ASPECT_XMAX_YMIN: + x = 1.0 * (in.width() - width); + break; + case SP_ASPECT_XMIN_YMID: + y = 0.5 * (in.height() - height); + break; + case SP_ASPECT_XMID_YMID: + x = 0.5 * (in.width() - width); + y = 0.5 * (in.height() - height); + break; + case SP_ASPECT_XMAX_YMID: + x = 1.0 * (in.width() - width); + y = 0.5 * (in.height() - height); + break; + case SP_ASPECT_XMIN_YMAX: + y = 1.0 * (in.height() - height); + break; + case SP_ASPECT_XMID_YMAX: + x = 0.5 * (in.width() - width); + y = 1.0 * (in.height() - height); + break; + case SP_ASPECT_XMAX_YMAX: + x = 1.0 * (in.width() - width); + y = 1.0 * (in.height() - height); + break; + default: + break; + } + } + + /* Viewbox transform from scale and position */ + Geom::Affine q; + q[0] = scale_x; + q[1] = 0.0; + q[2] = 0.0; + q[3] = scale_y; + q[4] = x - scale_x * this->viewBox.left(); + q[5] = y - scale_y * this->viewBox.top(); + + // std::cout << " q\n" << q << std::endl; + + /* Append viewbox transformation */ + this->c2p = q * this->c2p; +} + +SPItemCtx SPViewBox::get_rctx(const SPItemCtx* ictx, double scale_none) { + + /* Create copy of item context */ + SPItemCtx rctx = *ictx; + + /* Calculate child to parent transformation */ + /* Apply parent translation (set up as viewport) */ + this->c2p = Geom::Translate(rctx.viewport.min()); + + if (this->viewBox_set) { + // Adjusts c2p for viewbox + apply_viewbox( rctx.viewport, scale_none ); + } + + rctx.i2doc = this->c2p * rctx.i2doc; + + /* If viewBox is set initialize child viewport */ + /* Otherwise it is already correct */ + if (this->viewBox_set) { + rctx.viewport = this->viewBox; + rctx.i2vp = Geom::identity(); + } + + return rctx; +} + +/** + * Write viewBox attribute to XML, if set. + */ +void SPViewBox::write_viewBox(Inkscape::XML::Node *repr) const +{ + if (viewBox_set) { + Inkscape::SVGOStringStream os; + os << viewBox.left() << " " // + << viewBox.top() << " " // + << viewBox.width() << " " // + << viewBox.height(); + + repr->setAttribute("viewBox", os.str()); + } +} + +/** + * Write preserveAspectRatio attribute to XML, if set. + */ +void SPViewBox::write_preserveAspectRatio(Inkscape::XML::Node *repr) const +{ + if (aspect_set) { + std::string aspect = ASPECT_ALIGN_STRINGS.at(aspect_align); + + if (aspect_clip == SP_ASPECT_SLICE) { + aspect += " slice"; + } + + repr->setAttribute("preserveAspectRatio", aspect); + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-basic-offset:2 + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=2:tabstop=8:softtabstop=2:fileencoding=utf-8:textwidth=99 : -- cgit v1.2.3