Skip to content

Commit

Permalink
Fix Matrix transformations, tex, image and stroke
Browse files Browse the repository at this point in the history
 - missing: text
  • Loading branch information
Febbe committed Apr 1, 2024
1 parent f8f706b commit f32fac7
Show file tree
Hide file tree
Showing 24 changed files with 232 additions and 189 deletions.
3 changes: 2 additions & 1 deletion src/core/control/LatexController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,8 @@ auto LatexController::loadRendered(string renderedTex) -> std::unique_ptr<TexIma
}

img->setText(std::move(renderedTex));
img->move(posx, posy);
img->setX(posx);
img->setY(posy);

if (imgheight > 1024 * std::numeric_limits<double>::epsilon()) {
double ratio = img->getElementWidth() / img->getElementHeight();
Expand Down
2 changes: 1 addition & 1 deletion src/core/control/tools/EditSelection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ EditSelection::EditSelection(Control* ctrl, InsertionOrder elts, const PageRef&
for (auto&& e: contents->getElements()) {
this->preserveAspectRatio = this->preserveAspectRatio || e->rescaleOnlyAspectRatio();
this->supportMirroring = this->supportMirroring && e->rescaleWithMirror();
this->supportRotation = this->supportRotation && e->getType() == ELEMENT_STROKE;
this->supportRotation = true /* this->supportRotation && e->getType() == ELEMENT_STROKE */;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/core/control/tools/EraseHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ void EraseHandler::eraseStroke(Layer* l, Stroke* s, double x, double y, Range& r
ErasableStroke* erasable = s->getErasable();
if (!erasable) {
if (this->handler->getEraserType() == ERASER_TYPE_DELETE_STROKE) {
if (!s->intersects(x, y, halfEraserSize)) {
if (!s->intersects({x, y}, halfEraserSize)) {
// The stroke does not intersect the eraser square
return;
}
Expand Down
4 changes: 2 additions & 2 deletions src/core/gui/PageViewFindObjectHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ class SelectObject: public BaseSelectObject {
if (e->getType() == ELEMENT_STROKE) {
Stroke* s = (Stroke*)e;
double tmpGap = 0;
if ((s->intersects(x, y, 5, &tmpGap)) && (gap > tmpGap)) {
if ((s->intersects({x, y}, 5, &tmpGap)) && (gap > tmpGap)) {
gap = tmpGap;
strokeMatch = s;
matchIndex = pos;
Expand Down Expand Up @@ -200,7 +200,7 @@ class PlayObject: public BaseSelectObject {

AudioElement* s = (AudioElement*)e;
double tmpGap = 0;
if ((s->intersects(x, y, 15, &tmpGap))) {
if ((s->intersects({x, y}, 15, &tmpGap))) {
size_t ts = s->getTimestamp();

if (auto fn = s->getAudioFilename(); !fn.empty()) {
Expand Down
7 changes: 4 additions & 3 deletions src/core/model/AudioElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#pragma once

#include <cstddef> // for size_t
#include "util/Point.h"

#include "Element.h" // for Element, ElementType
#include "filesystem.h" // for path
Expand All @@ -28,13 +29,13 @@ class AudioElement: public Element {
~AudioElement() override;

void setTimestamp(size_t timestamp);
size_t getTimestamp() const;
auto getTimestamp() const -> size_t;

void setAudioFilename(fs::path fn);
auto getAudioFilename() const -> fs::path const&;

virtual bool intersects(double x, double y, double halfSize) const = 0;
virtual bool intersects(double x, double y, double halfSize, double* gap) const = 0;
virtual auto intersects(xoj::util::Point<double> pos, double halfSize) const -> bool = 0;
virtual auto intersects(xoj::util::Point<double> pos, double halfSize, double* gap) const -> bool = 0;

protected:
void serialize(ObjectOutputStream& out) const override;
Expand Down
37 changes: 10 additions & 27 deletions src/core/model/Element.cpp
Original file line number Diff line number Diff line change
@@ -1,55 +1,42 @@
#include "Element.h"

#include <algorithm> // for max, min
#include <cmath> // for ceil, floor, NAN
#include <cstdint> // for uint32_t
#include <iostream>
#include <system_error>

#include <glib.h> // for gint

#include "util/Matrix.h"
#include "util/Point.h"
#include "util/Rectangle.h"
#include "util/safe_casts.h" // for as_unsigned
#include "util/serializing/ObjectInputStream.h" // for ObjectInputStream
#include "util/serializing/ObjectOutputStream.h" // for ObjectOutputStream

using xoj::util::Rectangle;

Element::Element(ElementType type): type(type) {}

void Element::move(double dx, double dy) {
this->transformation = this->transformation.translate(dx, dy);
}
void Element::move(double dx, double dy) { this->transformation = this->transformation.translate(dx, dy); }

void Element::scale(xoj::util::Point<double> base, double fx, double fy, bool restoreLineWidth) {
auto scale = xoj::util::Matrix();
scale = scale.translate(-base.x, -base.y);
scale = scale.scale(fx, fy);
scale = scale.translate(base.x, base.y);
this->transformation = scale * this->transformation;
// this->updateBounds();
this->transformation = this->transformation.translate(-base.x, -base.y).scale(fx, fy).translate(base.x, base.y);
}

void Element::rotate(xoj::util::Point<double> base, double th) {
auto rotate = xoj::util::Matrix();
rotate = rotate.translate(-base.x, -base.y);
rotate = rotate.rotate(th);
rotate = rotate.translate(base.x, base.y);
this->transformation = rotate * this->transformation;
// this->updateBounds();
this->transformation = this->transformation.translate(-base.x, -base.y).rotate(th).translate(base.x, base.y);
}


void Element::setX(double x) {
auto x_orig = boundingRect().x;
auto tx = x - x_orig;
this->transformation.tx = tx;
this->transformation.tx += tx;
}

void Element::setY(double y) {
auto y_orig = boundingRect().y;
auto ty = y - y_orig;
this->transformation.ty = ty;
this->transformation.ty += ty;
}

void Element::setWidth(double width) {
Expand All @@ -70,13 +57,9 @@ auto Element::getElementWidth() const -> double { return boundingRect().width; }

auto Element::getElementHeight() const -> double { return boundingRect().height; }

auto Element::boundingRect() const -> Rectangle<double> {
return internalUpdateBounds().first;
}
auto Element::boundingRect() const -> Rectangle<double> { return updateBounds().first; }

auto Element::getSnappedBounds() const -> Rectangle<double> {
return internalUpdateBounds().second;
}
auto Element::getSnappedBounds() const -> Rectangle<double> { return updateBounds().second; }

void Element::setColor(Color color) { this->color = color; }

Expand Down Expand Up @@ -146,4 +129,4 @@ auto refElementContainer(const std::vector<ElementPtr>& elements) -> std::vector

auto Element::getTransformation() const -> xoj::util::Matrix { return this->transformation; }

void Element::setTransformation(xoj::util::Matrix const& mtx) {this->transformation = mtx; }
void Element::setTransformation(xoj::util::Matrix const& mtx) { this->transformation = mtx; }
2 changes: 1 addition & 1 deletion src/core/model/Element.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class Element: public Serializable {

void move(double dx, double dy);
virtual void scale(xoj::util::Point<double> base, double fx, double fy, bool restoreLineWidth);
virtual void rotate(xoj::util::Point<double> base, double th);
void rotate(xoj::util::Point<double> base, double th);

void setColor(Color color);
auto getColor() const -> Color;
Expand Down
55 changes: 26 additions & 29 deletions src/core/model/Stroke.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "util/Interval.h" // for Interval
#include "util/PairView.h" // for PairView<>::BaseIte...
#include "util/PlaceholderString.h" // for PlaceholderString
#include "util/Point.h"
#include "util/Range.h"
#include "util/Rectangle.h" // for Rectangle
#include "util/SmallVector.h" // IWYU pragma: keep for SmallVector
Expand Down Expand Up @@ -248,10 +249,8 @@ auto Stroke::rescaleWithMirror() -> bool { return true; }

auto Stroke::isInSelection(ShapeContainer* container) const -> bool {
for (auto&& p: this->points) {
double px = p.x;
double py = p.y;

if (!container->contains(px, py)) {
auto pt = getTransformation() * p;
if (!container->contains(pt.x, pt.y)) {
return false;
}
}
Expand All @@ -261,7 +260,7 @@ auto Stroke::isInSelection(ShapeContainer* container) const -> bool {

void Stroke::addPoint(const Point& p) {
this->points.emplace_back(p);
if(this->points.size() == 1) /* [[unlikely]] */ {
if (this->points.size() == 1) /* [[unlikely]] */ {
this->bounds = {p.x, p.y, 0, 0};
this->snappedBounds = {p.x, p.y, 0, 0};
}
Expand All @@ -271,7 +270,8 @@ void Stroke::addPoint(const Point& p) {

auto Stroke::getPointCount() const -> size_t { return this->points.size(); }

// auto Stroke::getPointVectorFail() const -> std::pair<xoj::util::Matrix, std::vector<Point> const&> { return {{}, points}; }
// auto Stroke::getPointVectorFail() const -> std::pair<xoj::util::Matrix, std::vector<Point> const&> { return {{},
// points}; }
auto Stroke::getPointVector() const -> std::vector<Point> const& { return points; }

void Stroke::deletePointsFrom(size_t index) {
Expand Down Expand Up @@ -405,50 +405,48 @@ void Stroke::setPressure(const std::vector<double>& pressure) {
/**
* checks if the stroke is intersected by the eraser rectangle
*/
auto Stroke::intersects(double x, double y, double halfEraserSize) const -> bool {
return intersects(x, y, halfEraserSize, nullptr);
auto Stroke::intersects(xoj::util::Point<double> pos, double halfEraserSize) const -> bool {
return intersects(pos, halfEraserSize, nullptr);
}

/**
* checks if the stroke is intersected by the eraser rectangle
*/
auto Stroke::intersects(double x, double y, double halfEraserSize, double* gap) const -> bool {
auto Stroke::intersects(xoj::util::Point<double> pos, double halfEraserSize, double* gap) const -> bool {
if (this->points.empty()) {
return false;
}
auto tpos = getTransformation().inverse() * pos;

double x1 = x - halfEraserSize;
double x2 = x + halfEraserSize;
double y1 = y - halfEraserSize;
double y2 = y + halfEraserSize;
double x1 = tpos.x - halfEraserSize;
double x2 = tpos.x + halfEraserSize;
double y1 = tpos.y - halfEraserSize;
double y2 = tpos.y + halfEraserSize;

double lastX = points[0].x;
double lastY = points[0].y;
xoj::util::Point<double> last = {points[0].x, points[0].y};
for (auto&& point: points) {
double px = point.x;
double py = point.y;
xoj::util::Point<double> curr = {point.x, point.y};

if (px >= x1 && py >= y1 && px <= x2 && py <= y2) {
if (curr.x >= x1 && curr.y >= y1 && curr.x <= x2 && curr.y <= y2) {
if (gap) {
*gap = 0;
}
return true;
}

double len = hypot(px - lastX, py - lastY);
double len = curr.distance(last);
if (len >= halfEraserSize) {
/**
* The distance of the center of the eraser box to the line passing through (lastx, lasty) and (px, py)
*/
double p = std::abs((x - lastX) * (lastY - py) + (y - lastY) * (px - lastX)) / len;
double p = std::abs((tpos.x - last.x) * (last.y - curr.y) + (tpos.y - last.y) * (curr.x - last.x)) / len;

// If the distance p of the center of the eraser box to the (full) line is in the range,
// we check whether the eraser box is not too far from the line segment through the two points.

if (p <= halfEraserSize) {
double centerX = (lastX + px) / 2;
double centerY = (lastY + py) / 2;
double distance = hypot(x - centerX, y - centerY);
auto center = (last + curr) / 2.0;
double distance = center.distance(tpos);

// For the above check we imagine a circle whose center is the mid point of the two points of the stroke
// and whose radius is half the length of the line segment plus half the diameter of the eraser box
Expand All @@ -468,8 +466,7 @@ auto Stroke::intersects(double x, double y, double halfEraserSize, double* gap)
}
}

lastX = px;
lastY = py;
last = curr;
}

return false;
Expand All @@ -485,8 +482,8 @@ auto Stroke::intersects(double x, double y, double halfEraserSize, double* gap)
* The line enters the rectangle at the point res.min * p + (1 - res.min) * q
* The line leaves the rectangle at the point res.max * p + (1 - res.max) * q
*/
static std::optional<Interval<double>> intersectLineWithRectangle(const Point& p, const Point& q,
const Rectangle<double>& rectangle) {
static auto intersectLineWithRectangle(const Point& p, const Point& q, const Rectangle<double>& rectangle)
-> std::optional<Interval<double>> {
auto intersectLineWithStrip = [](double a1, double a2, double stripAMin, double stripWidth) {
// a1, a2 are coordinates along an axis orthogonal to the strip
double norm = 1.0 / (a2 - a1);
Expand Down Expand Up @@ -526,8 +523,8 @@ static std::optional<Interval<double>> intersectLineWithRectangle(const Point& p
* Same as intersectLineWithRectangle but only returns parameters between 0 and 1
* (corresponding to points between p and q)
*/
static TinyVector<double, 2> intersectLineSegmentWithRectangle(const Point& p, const Point& q,
const Rectangle<double>& rectangle) {
static auto intersectLineSegmentWithRectangle(const Point& p, const Point& q, const Rectangle<double>& rectangle)
-> TinyVector<double, 2> {
std::optional<Interval<double>> intersections = intersectLineWithRectangle(p, q, rectangle);

if (intersections) {
Expand Down
5 changes: 3 additions & 2 deletions src/core/model/Stroke.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "model/Element.h"
#include "util/Matrix.h"
#include "util/Point.h"

#include "AudioElement.h" // for AudioElement
#include "LineStyle.h" // for LineStyle
Expand Down Expand Up @@ -145,8 +146,8 @@ class Stroke final: public AudioElement {
auto getLineStyle() const -> const LineStyle&;
void setLineStyle(const LineStyle& style);

auto intersects(double x, double y, double halfEraserSize) const -> bool override;
auto intersects(double x, double y, double halfEraserSize, double* gap) const -> bool override;
auto intersects(xoj::util::Point<double> pos, double halfEraserSize) const -> bool override;
auto intersects(xoj::util::Point<double> pos, double halfEraserSize, double* gap) const -> bool override;

/**
* @brief Find the parameters within a certain interval corresponding to the points where the stroke crosses in
Expand Down
38 changes: 18 additions & 20 deletions src/core/model/TexImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,26 @@

using xoj::util::Rectangle;

static auto retrievePdfBounds(PopplerDocument* pdf) -> xoj::util::Rectangle<double> {
[[nodiscard]] static auto retrievePdfBounds(PopplerDocument* pdf) -> xoj::util::Rectangle<double> {
double width = 0;
double height = 0;
auto page = xoj::util::GObjectSPtr<PopplerPage>(poppler_document_get_page(pdf, 0), xoj::util::adopt);
poppler_page_get_size (page.get(), &width, &height);
poppler_page_get_size(page.get(), &width, &height);
return {0, 0, width, height};
}

TexImage::TexImage(): Element(ELEMENT_TEXIMAGE) {};
TexImage::TexImage(): Element(ELEMENT_TEXIMAGE){};
TexImage::~TexImage() = default;

void TexImage::freeImageAndPdf() {
this->pdf.reset();
}
void TexImage::freeImageAndPdf() { this->pdf.reset(); }

auto TexImage::cloneTexImage() const -> std::unique_ptr<TexImage> {

auto img = std::make_unique<TexImage>();
img->setColor(this->getColor());
img->text = this->text;
img->bounds = this->bounds;

// Load a copy of our data (must be called after
// giving the clone a copy of our PDF -- it may change
// the PDF we've given it).
Expand All @@ -63,23 +61,23 @@ auto TexImage::loadData(std::string&& bytes, GError** err) -> bool {
if (this->binaryData.length() < 4) {
return false;
}

const std::string type = binaryData.substr(1, 3);
if (type == "PDF") {
// Note: binaryData must not be modified while pdf is live.
this->pdf.reset(poppler_document_new_from_data(this->binaryData.data(), this->binaryData.size(), nullptr, err),
xoj::util::adopt);
if (!pdf.get() || poppler_document_get_n_pages(this->pdf.get()) < 1) {
pdf.reset();

g_warning("Generated PDF is empty:");
return false;
}
} else {
g_warning("Unknown Latex image type: \"%s\"", type.c_str());
if (type != "PDF") {
g_warning("Unsupported Latex image type: \"%s\"", type.c_str());
return false;
}

// Note: binaryData must not be modified while pdf is live.
this->pdf.reset(poppler_document_new_from_data(this->binaryData.data(), this->binaryData.size(), nullptr, err),
xoj::util::adopt);
if (!pdf.get() || poppler_document_get_n_pages(this->pdf.get()) < 1) {
pdf.reset();

g_warning("Generated PDF is empty:");
return false;
}

this->bounds = retrievePdfBounds(this->pdf.get());
return true;
}

Expand Down
Loading

0 comments on commit f32fac7

Please sign in to comment.