Commit Graph

72 Commits

Author SHA1 Message Date
Nico Weber
d569238896 LibGfx: Add support for stroke dash patterns
This adds support for dash patterns to Path::stroke_to_fill().
This is used in PDFs, <canvas>, and <svg>.

The implementation is based on the <canvas> spec. It seems to do
the right thing for PDF files too.

(This commit only adds the feature to LibGfx. Future commits will
hook this up for PDF, <canvas>, and <svg>.)
2024-11-07 09:12:13 -05:00
Nico Weber
dc59e001f9 LibGfx: Make miter_limit an input to Path::stroke_to_fill() 2024-10-25 20:35:23 -04:00
Nico Weber
d6e325ef6b LibGfx+Clients: Make Path::stroke_path() take a parameter struct
This makes several calling sites shorter, and makes it easier
to add a miter limit with less plumbing in the future.

This also exposed a bug in AffineDisplayListPlayerCPU.cpp: We weren't
passing path linecap style on. I added a FIXME for that for now.

No intended behavior change.
2024-10-25 20:35:23 -04:00
Nico Weber
cc5c332c2e LibGfx+Tests: Implement miter join for path stroking
The miter limit is hardcoded for now.
2024-10-25 18:12:58 -04:00
Nico Weber
b31ac25cbd LibGfx: Add support for janky bevel line joins
Similar to the first version of butt line joins, bevels are done
using the pen used for round joins and caps, we just don't add
any vertices except the last one on the pen. We can make this nicer
later on.
2024-10-25 18:12:58 -04:00
Nico Weber
ad6bda2add LibGfx: Add a add_linejoin() helper in Path::stroke_to_fill()
It just forwards to add_round_join() for now, but it'll do more once
we support more join styles.

No behavior change.
2024-10-25 18:12:58 -04:00
Nico Weber
cb63f7fb16 LibGfx: Replace a comemnt with a VERIFY() in Path::stroke_to_fill()
No behavior change.
2024-10-24 22:26:02 -07:00
Nico Weber
b2cdd0018a LibGfx: Clean up geometry when stroking closed paths
Previously, when stroking closed paths, we'd create a single
path where the inner stroke and the outer stroke were connected
with round linecaps. Since we currently only support round line joins,
this looked ok.

Now, we don't draw caps when stroking closed paths, but instead
generate a closed inner path and an independent closed outer path.

This has the advantage that it produces slightly less geometry, and
it's a prerequisite for implementing non-round line joins.

No visual difference.
2024-10-24 22:26:02 -07:00
Nico Weber
4bec146c4d LibGfx: Explicitly pass next index to add_round_join() in Path
No behavior change.
2024-10-24 22:26:02 -07:00
Nico Weber
cc9200c41c LibGfx: Inline a call to slope() in Path::stroke_to_fill()
No behavior change; allows passing in a different second index in
a follow-up.
2024-10-24 22:26:02 -07:00
Nico Weber
783a42b15f LibGfx: Rewrite trace_path_until_index() using add_round_join() in Path
Instead of a single loop that adds either a pen vertex or a path
vertex each round, there is now an outer loop for adding path vertices,
and an inner loop that adds all pen vertices needed at that vertex.

No behavior change.
2024-10-24 22:26:02 -07:00
Nico Weber
5a5d49599f LibGfx: Extract an add_round_join helper in Path::stroke_to_fill
No behavior change.
2024-10-24 22:26:02 -07:00
Nico Weber
66378471a0 LibGfx: When stroking zero-lengths paths, draw round and square end caps
PDF 1.7 spec, p231, 4.4.2 Path-Painting Operators, Stroking:

"If a subpath is degenerate (consists of a single-point closed path or
of two or more points at the same coordinates), the S operator paints it
only if round line caps have been specified, producing a filled circle
centered at the single point. If butt or projecting square line caps
have been specified, S produces no output, because the orientation of
the caps would be indeterminate. (This rule applies only to zero-length
subpaths of the path being stroked, and not to zero-length dashes in a
dash pattern. In the latter case, the line caps are always painted,
since their orientation is determined by the direction of the underlying
path.) A single-point open subpath (specified by a trailing m operator)
produces no output."

In practice, Chrome, Firefox, and Preview all also draw a square
for square line endings (Preview a square rotated 45 degrees). Chrome
even draws something weird-looking for butt caps. (Acrobat only draws
the round cap, per spec.)

https://html.spec.whatwg.org/multipage/canvas.html#trace-a-path sounds
like zero-lengths paths should be ignored for canvas, but in practice
Chrome and Firefox do draw them. (Safari doesn't.)

We don't do linecaps in SVGs yet, but
https://www.w3.org/TR/SVG/paths.html#ZeroLengthSegments says:

"As mentioned in Stroke Properties, linecaps must be painted for
zero-length subpaths when stroke-linecap has a value of round or
square."

With this commit, we now draw round and square linecaps for
zero-lengths paths, which is what's apparently desired most of the
time. Maybe we can add a setting to pick different behavior for
PDF (only draw round caps), canvas (don't draw caps on zero-length
paths), and SVG (draw round and square caps) in the future.
2024-10-16 18:18:43 -04:00
Nico Weber
e90e4b4f25 LibGfx: Add support for square linecaps when stroking paths
This is now easy :^)
2024-10-16 18:18:43 -04:00
Nico Weber
03eda80d99 LibGfx: Make path stroke butt line caps look less janky
We now put butt line cap vertices in the correct position,
vertical to line direction. We still pretend we're walking on the
pen polygon though, so it's probably possible to make this produce
weird-looking output by making the line cap line segments very
short. In practice, for butt line caps of real-world paths,
it's a big improvement though.
2024-10-16 18:18:43 -04:00
Nico Weber
7d1d6012bf LibGfx: Make Path::stroke_to_fill longer
Instead of one dense loop, there are now one call for the outer
stroke, one for the first cap, one for the inner stroke, and one
for the second cap.

This will make it easier to do butt caps correctly, and to add
support for square caps.

It also makes it easier to not add any caps at all for closed
paths.

Maybe it also helps for adding non-round joins eventually.

In particular:

* `shape_idx` now starts at 1, since we now start with the
  stroke part, not the cap part, and explicitly call `close()`
  to connect the second cap with the first stroke
* Having explicit cap building code means that the convolution
  loop is kind-of duplicated for round caps
* We now need to remove duplicate points, else the explicit
  cap drawing gets confused. This is the only non-behavior-preserving
  part of this commit, and it's a progression for lines that have
  two identical points at the end of an open path (this would previously
  not correctly draw a round join)
* Similarly, there's no explicit rejection of empty paths
2024-10-16 18:18:43 -04:00
Aliaksandr Kalenik
b01060c7d8 Everywhere: Limit layout text fragments to use one font for all glyphs
The ChunkIterator now limits a chunk to using only one font (before, it
was possible to have a chunk with >1 font, when `unicode-range` CSS
property is used).

This change allows us to reduce some complexity in the text shaping and
painting code and makes us compatible with the APIs in Skia and
HarfBuzz.

(cherry picked from commit 7181c3f2ea5fba73e77d98acbf9e46640b4a9015,
minorly amended to fix conflicts caused by:
* Our VectorFont not being renamed to Typeface
* Us cherry-picking https://github.com/LadybirdBrowser/ladybird/pull/502
  first
* Us still having bitmap fonts, and hence needing glyph_spacing()

Also amended for:
* AffineDisplayListPlayerCPU changes
* Removing pure virtuals for glyph_id_for_code_point and
  glyph_id_for_code_point in Font.h again since we still have BitmapFont
  which can't implement them
* Updating more Painter methods that we still had
  (Painter::draw_glyph_or_emoji(), Painter::draw_text_run())
)
2024-10-09 20:12:39 -04:00
Nico Weber
b113ce62b5 LibGfx: Store if a subpath is closed, and use that when stroking
A stroked path that has the same start and end point looks different
depending on if it's closed or not: If it's closed, the start/end point
is drawn as a line join; if it's not closed, the start/end point is
drawn as a cap.

(It has an effect only for stroked paths, but not for filled paths.)

So make Path remember if it's closed or not by adding a ClosePath
segment type.

This matches the canvas, pdf, svg specs.
(TinyVG doesn't have strokes yet.)

This fixes the apparent rendering regression from #25040 / #25044
(which just made an existing bug visible).

(We should probably make an inner and an outer path when stroking a
closed path instead of just giving closed paths a round cap. When
filled, both look identical, but the current approach produces more
geometry. For now, this is good enough.)
2024-10-06 18:05:39 -04:00
Nico Weber
c1ea8fa01c LibGfx+Tests: Fix infinite loop in butt line cap drawing
Sometimes going across would put is one active range away from
being in-bounds, so check the two neighboring ranges too.
2024-09-27 20:18:55 -04:00
Nico Weber
67bf00c6b0 LibGfx/Path: Implement support for the butt line cap style
This is done by walking half the pen at the two line ends instead
of using each pen vertex (like it happens for round caps).
This won't be completely vertical to the stroke direction, but it
is kind of close, and it's very easy to implement. We'll improve
this later :^)
2024-09-27 15:05:30 -04:00
Nico Weber
f09b837846 LibGfx/Path: Slightly rewrite pen increment operation
This is shorter, and makes it easier to make a different number of
hops per update in a later commit.
2024-09-27 15:05:30 -04:00
Nico Weber
22185b15f5 LibGfx/Path: Extract a helper make_pen() function
No behavior change. The goal is to make Path::stroke_to_fill()
smaller and do fewer things.
2024-09-27 15:05:30 -04:00
Nico Weber
5ba5a599de LibGfx/Path: Make round end caps show up more consistently
On macOS, when going from the right end of a horizontal line
to the left, and when slope_now is pi, and the current
range.end is 0 (i.e. current_angle = pi, target_angle = 0),
clockwise() would set target_angle to 2 * pi, and
`target_angle - current_angle` (ie 2 * pi - pi) would end up
being ever so sligthly more than `pi` (handwavingly due to
floating point in accuracies), and we'd go in the wrong direction.

Add an explicit check for that, and a reftest that catches this.

No observed behavior change on linux, but the reftest only passes
on macOS with the code change.
2024-09-25 15:59:58 -04:00
MacDue
0ca71009d0 LibGfx: Add Path::transform()
This applies an affine transform to a path in-place.
2024-06-10 17:41:33 -04:00
MacDue
d62d5079f4 LibGfx: Add relative to last point mode to Path::append_path() 2024-06-10 17:41:33 -04:00
MacDue
318f2925d3 LibGfx+LibWeb: Move generally useful path methods to LibGfx
This adds:

  - Path::rect()
  - Path::quad()
  - Path::rounded_rect()

Moving the corresponding implementations from LibWeb.
2024-06-09 14:12:06 -04:00
MacDue
d7e2894e57 LibGfx: Output an SVG compatible string from Path::to_byte_string()
This is much more useful than the previous format, as you can now just
paste the path into a site like https://svg-path-visualizer.netlify.app/
to debug issues.
2024-03-19 09:55:55 -04:00
MacDue
8057542dea LibGfx: Simplify path storage and tidy up APIs
Rather than make path segments virtual and refcounted let's store
`Gfx::Path`s as a list of `FloatPoints` and a separate list of commands.

This reduces the size of paths, for example, a `MoveTo` goes from 24
bytes to 9 bytes (one point + a single byte command), and removes a
layer of indirection when accessing segments. A nice little bonus is
transforming a path can now be done by applying the transform to all
points in the path (without looking at the commands).

Alongside this there's been a few minor API changes:

- `path.segments()` has been removed
  * All current uses could be replaced by a new `path.is_empty()` API
  * There's also now an iterator for looping over `Gfx::Path` segments
- `path.add_path(other_path)` has been removed
  * This was a duplicate of `path.append_path(other_path)`
- `path.ensure_subpath(point)` has been removed
  * Had one use and is equivalent to an `is_empty()` check + `move_to()`
- `path.close()` and `path.close_all_subpaths()` assume an implicit
  `moveto 0,0` if there's no `moveto` at the start of a path (for
  consistency with `path.segmentize_path()`).

Only the last point could change behaviour (though in LibWeb/SVGs all
paths start with a `moveto` as per the spec, it's only possible to
construct a path without a starting `moveto` via LibGfx APIs).
2024-03-18 07:09:37 +01:00
MacDue
e9e1ee11d4 LibGfx: Decrease flatness a little in Path::stroke_to_fill()
Noticed larger stroke widths were looking a little 'low poly', this
looks a little nicer.

(Minor LibWeb test changes)
2024-01-21 19:23:31 +01:00
MacDue
13a4fb0325 LibGfx: Increase bezier splitting tolerance to 0.5
No noticeable difference a bit faster. This is still arbitrary and
should be somehow derived from the curve.
2024-01-08 09:26:43 +01:00
MacDue
d327104910 LibGfx: Add Path::place_text_along(text, font)
This method returns a new path with the input text placed along the edge
of the original path.
2023-12-19 21:29:03 +01:00
Ali Mohammad Pur
5e1499d104 Everywhere: Rename {Deprecated => Byte}String
This commit un-deprecates DeprecatedString, and repurposes it as a byte
string.
As the null state has already been removed, there are no other
particularly hairy blockers in repurposing this type as a byte string
(what it _really_ is).

This commit is auto-generated:
  $ xs=$(ack -l \bDeprecatedString\b\|deprecated_string AK Userland \
    Meta Ports Ladybird Tests Kernel)
  $ perl -pie 's/\bDeprecatedString\b/ByteString/g;
    s/deprecated_string/byte_string/g' $xs
  $ clang-format --style=file -i \
    $(git diff --name-only | grep \.cpp\|\.h)
  $ gn format $(git ls-files '*.gn' '*.gni')
2023-12-17 18:25:10 +03:30
Aliaksandr Kalenik
df57d7ca68 LibGfx+LibWeb: Update for_each_glyph_position to use font cascade list
This change updates function that builds list of glyphs to use font
cascade list to find font for each code point.
2023-12-10 17:32:04 +01:00
Nicolas Ramz
b3cbe0fdb9 LibGfx/Path: Round numerator in elliptical_arc_to
This avoids rounding problems which made Ladybird crash with some SVGs
on macOS/Clang.
2023-11-19 22:33:48 +01:00
MacDue
f3c8e88e5e LibGfx: Use BoundingBox helper in Gfx::Path 2023-11-14 10:13:10 +01:00
Aliaksandr Kalenik
efdbd8238e LibGfx: Decouple glyph positions calculation from draw_text_run()
This change separates a part of the `draw_text_run()` function, which
is responsible for calculating the positions for glyphs that need to be
painted, into a separate function called `get_glyph_run()`.

It is a part of the preparation for text run painting using OpenGL,
where we can't immediately blit glyph bitmaps but instead need to
prepare a sequence of quads for them in advance.
2023-11-06 09:53:11 +01:00
MacDue
50d33f79fa LibGfx: Allow extracting paths from fonts and add Gfx::Path::text()
This updates fonts so rather than rastering directly to a bitmap, you
can extract paths for glyphs. This is then used to implement a
Gfx::Path::text("some text", font) API, that if given a vector font
appends the path of the text to your Gfx::Path. This then allows
arbitrary manipulation of the text (rotation, skewing, etc), paving the
way for Word Art in Serenity.
2023-11-05 02:46:46 +01:00
Gabriel Nava
9a61041941 LibWeb: Add CanvasPath arcTo support
Adds initial CanvasPath arcTo support for 2D rendering contexts
https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-arcto
2023-09-17 17:22:52 +02:00
MacDue
a9b4e876d0 LibGfx: Only attempt to paint strokes with a width > 0 2023-07-16 18:52:38 +02:00
MacDue
1bc7b0320e LibGfx: Approximate elliptical arcs with cubic beziers
Unlike all other primitives elliptical arcs are non-trivial to
manipulate, it's tricky to correctly apply a Gfx::AffineTransform to
them. Prior to this change, Path::copy_transformed() was still
incorrectly applying transforms such as flips and skews to arcs.

This patch very closely approximates arcs with cubic beziers (I can not
visually spot any differences), which can then be easily and correctly
transformed in all cases.

Most of the maths here was taken from:
https://mortoray.com/rendering-an-svg-elliptical-arc-as-bezier-curves/
(which came from https://www.joecridge.me/content/pdf/bezier-arcs.pdf,
now a dead link).
2023-07-16 06:22:55 +02:00
MacDue
2a1bf63f9e LibGfx: Use AK::sincos() and AK::Pi<double> in Path::elliptical_arc_to() 2023-07-16 06:22:55 +02:00
MacDue
8d4f90df4e LibGfx: Pass angles as floats to Path::elliptical_arc_to()
These are stored as floats internally and other parameters are
FloatPoints, so taking doubles is a little inconsistent. Doubles are
needed for the endpoint -> center parametrization conversion, but that
does not need exposing in the API.
2023-07-16 06:22:55 +02:00
MacDue
90e836deae LibGfx: Fix elliptical arcs after non orientation preserving transform
That is flipping/reflecting the arc.
2023-07-14 06:51:05 +02:00
MacDue
3d755a57b6 LibGfx: Ensure last subpath is closed by Path::close_all_subpaths() 2023-06-24 19:31:30 +02:00
MacDue
95a07bd4e5 LibGfx: Add Path::stroke_to_fill(thickness)
This function generates a new path, which can be filled to rasterize
a stroke of the original path (at whatever thickness you like). It
does this by convolving a circular pen with the path, so right now
only supports round line caps.

Since filled paths now have good antialiasing, doing this results in
good stroked paths for "free". It also (for free) fixes stroked lines
with an opacity < 1, nice line joins, and is possible to fill with a
paint style (e.g. a gradient or an image).

Algorithm from: https://keithp.com/~keithp/talks/cairo2003.pdf
2023-06-06 09:17:06 +02:00
MacDue
6abc51d9f8 LibGfx: Simplify segmentizing paths
Remove SplitLineSegment and replace it with a FloatLine, nobody was
interested in its extra fields anymore. Also, remove the sorting of
the split segments, this really should not have been done here
anyway, and is not required by the rasterizer anymore. Keeping the
segments in stroke order will also make it possible to generate
stroked path geometry (in future).
2023-06-04 05:40:39 +02:00
Andreas Kling
cc86c07f58 LibGfx: Transform the x axis rotation for elliptical arcs
Without this, copy_transformed() will create paths with bogus elliptical
arcs. This was very noticeable with transformed ellipses in SVG.
2023-04-27 07:24:53 +02:00
MacDue
26e56bdd08 LibGfx: Fix crash due to vector resize in close_all_subpaths()
Since close_all_subpaths() appends while iterating, the vector can
end up being resized and the iterator invalidated. Previously, this
led to a crash/UAF in some cases.
2023-04-09 18:42:45 +02:00
Andreas Kling
8a48246ed1 Everywhere: Stop using NonnullRefPtrVector
This class had slightly confusing semantics and the added weirdness
doesn't seem worth it just so we can say "." instead of "->" when
iterating over a vector of NNRPs.

This patch replaces NonnullRefPtrVector<T> with Vector<NNRP<T>>.
2023-03-06 23:46:35 +01:00
Luke Wilde
1f97adbee8 LibGfx: Add a function that adds two paths together
This will be used by Path2D#addPath in LibWeb.
2023-02-27 20:55:09 +01:00