Commit graph

153 commits

Author SHA1 Message Date
Dan Klishch
5ed7cd6e32 Everywhere: Use east const in more places
These changes are compatible with clang-format 16 and will be mandatory
when we eventually bump clang-format version. So, since there are no
real downsides, let's commit them now.
2024-04-19 06:31:19 -04:00
Nico Weber
f570678bf0 LibPDF: Invert image masks used as alpha too
Fixes #23824, a regression from the first commit in #23781.
2024-04-04 06:55:08 -04:00
Nico Weber
40780304b8 LibPDF: Add a fastpath for 1bpp grayscale to load_image()
We used to expand every bit in an 1bpp image to a 0 or 255 byte,
then map that to a float that's either 0.0f or 1.0f (or whatever's
in /DecodeArray), then multiply that by 255.0f to convert it to a
u8 and put that in the rgb channels of a Color.

Now we precompute the two possible outcomes (almost always Black
and White) and do a per-bit lookup.

Reduces time for

    Build/lagom/bin/pdf --render-bench --render-repeats 20 --page 36 \
        ~/Downloads/Flatland.pdf

(the "decoded data cached" case) from 3.3s to 1.1s on my system.

Reduces time for

    Build/lagom/bin/pdf --debugging-stats ~/Downloads/0000/0000231.pdf

(the "need to decode each page" case) from 52s to 43s on my machine.

Also makes paging through PDFs that contain a 1700x2200 pixel CCITT
or JBIG2 bitmap on each page noticeably snappier.
2024-04-02 08:07:46 +02:00
Nico Weber
c01acdd733 LibPDF: Move decode_array up a bit
No behavior change.
2024-04-02 08:07:46 +02:00
Nico Weber
81ff9f45d9 LibPDF: Move image mask inversion from load_image() to show_image()
No behavior change.
2024-04-02 08:07:46 +02: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
Nico Weber
24951a039e LibPDF: Clip stroke for B / B* operators
Fixes pages 17-19 on
https://www.iro.umontreal.ca/~feeley/papers/ChevalierBoisvertFeeleyECOOP15.pdf

Calling the fill handler after painting the stroke as previously doesn't
work, since we need to set up the clip before both stroke and fill, and
unset it after both. The duplication is a bit unfortunate, but also
minor.
2024-03-08 10:45:28 -05:00
Nico Weber
c6b484a728 LibPDF: Make image creation in Renderer::load_image() fallible 2024-03-03 11:18:37 -05:00
Nico Weber
9bff8abcc7 LibPDF: Add support for array image masks
An array image mask contains a min/max range for each channel,
and if each channel of a given pixel is in that channel's range,
that pixel is masked out (i.e. transparent). (It's similar to
having a single color or palette index be transparent, but it
supports a range of transparent colors if desired.)

What makes this a bit awkward is that the range is relative to the
origin bits per pixel and the inputs to the image's color space.

So an indexed (palettized) image with 4bpp has a 2-element mask
array where both entries are between 0 and 15.

We currently apply masks after converting images to a Gfx::Bitmap,
that is after converting to 8bpp sRGB. And we do this by mapping
everything to 8bpp very early on in load_image().

This leaves us with a bunch of options that are all a bit awkward:

1. Make load_image() store the up- (or for 16bpp inputs, down-)
   sampled-to-8bpp pixel data. And also return if we expanded the
   pixel range while resampling (for color values) or not (for
   palettized images). Then, when applying the image filter,
   resample the array bounds in exactly the same way. This requires
   passing around more stuff.

2. Like 1, but pass in the mask array to load_image() and apply
   the mask right there and then. This means we'd apply mask arrays
   at a different time than other masks.

3. Make the function that computes the mask from the mask array
   work from the original, unprocessed image data. This is the most
   local change, but probably also requires the largest amount of
   code (in return, the color mask for 16bpp images is precise, in
   addition that it separates concerns the most nicely).

This goes with 3 for now.
2024-03-03 11:18:37 -05:00
Nico Weber
98e272ce15 LibPDF: Silently ignore BX / EX operators
See the added comment for reasoning.
2024-03-02 17:43:53 -05:00
Nico Weber
9e502dcfe4 LibPDF: Honor writing mode in TJ operator as well 2024-03-02 12:25:09 +01:00
Nico Weber
c69797fda9 LibPDF: Implement support for vertical text for Type0
For Identity-V only for now.
2024-03-02 12:25:09 +01:00
Nico Weber
004e47df88 LibPDF: Remove minor duplication in Renderer::text_show_string_array()
This "regressed" in #10080 (back then, the branches were smaller).

No behavior change.
2024-03-02 12:25:09 +01:00
Nico Weber
76105d5d7f LibPDF: Resize images to the larger of image and mask dimensions
Makes text show up on 0000646.pdf pages 87-92, which for some reason
renders all text using 2x2 images with huge masks that contain
rendered text outlines.
2024-02-27 17:39:13 -05:00
Nico Weber
472bc367d3 LibPDF: Do not have redundant variables for image size
This way, the size of the bitmap cannot become out of sync with these
variables.

No behavior change.
2024-02-27 17:39:13 -05:00
Nico Weber
03fab7089a LibPDF+PDFViewer: Extract Renderer::apply_page_rotation()
No behavior change.
2024-02-27 07:02:02 +01:00
Nico Weber
fa95e5ec0e LibPDF: Fix line drawing when line_width is 0
We used to skip lines with width 0. The correct behavior per spec
is to draw them one pixel wide instead.
2024-02-21 10:30:57 +01:00
Nico Weber
f840fb6b4e LibPDF: Make DeviceCMYKColorSpace::the() fallible
No behavior change.
2024-02-01 13:42:04 -07:00
Nico Weber
a0462f495c LibPDF+MacPDF: Clip text, and add a debug option for disabling it 2024-01-20 08:56:03 +01:00
Nico Weber
66f8259a0b LibPDF: Move ClipRAII to .h file
No behavior change.
2024-01-20 08:56:03 +01:00
Nico Weber
d2f3288666 LibPDF: Apply text matrix to each glyph's position
We still don't apply it to the glyph itself, so they don't show up
scaled or rotated, but they're at the right spot now.

One big thing this here hsa going for it is that the final glyph
position is now calculated with just
`ext_rendering_matrix.map(glyph_position)`.

Also, character_spacing and word_spacing are now used unmodified
in the SimpleFont::draw_string() loop. This also means we no longer
have to undo a scale when updating the position in
`Renderer::show_text()`.

Most of the rest stays pretty yucky though. The root cause of many
problems is that ScaledFont has its rendering sized baked into the
object. We want to render fonts at size font_size times scale from
text matrix times scale from current transformation matrix (but
not size from hotizontal_scaling). So we have to make that the
font_size, but then we have to undo that in a bunch of places to
get the actualy font size.

This will eventually get better when LibPDF moves off ScaledFont.
2024-01-18 14:01:30 +01:00
Nico Weber
f54b0e7c22 LibPDF: Don't accidentally put horizontal_scaling in places
Fonts should have size font_size times total scaling. We tried to
get that by computing text_rendering_matrix.x_scale() * font_size,
but text_rendering_matrix.x_scale() also includes
horizontal_scaling, which shouldn't be part of font size.

Same for character_spacing and word_spacing.

This is all a big mess that's caused by LibPDF using ScaledFont,
which requires scaling to be aprt of the text type. I have an
in-progress local branch that moves LibPDF to directly use VectorFont,
which will hopefully make this (and other things) nicer. But first,
let's get this right, and then make sure we don't regress it when
things change :^)
2024-01-18 14:01:30 +01:00
Nico Weber
abda5e66f6 LibPDF: Scale delta_x by horizontal_scaling in Renderer::show_text()
While PDFFont::draw_string() already returns a position scaled by
horizontal_scaling, the division by text_rendering_matrix.x_scale()
(which also contains the scaling factor) undid it. Reapply it.

Fixes the horizontal layout of the line
"should be the same on all lines: super" in Tests/LibPDF/text.pdf.
2024-01-18 14:01:30 +01:00
Nico Weber
470d1d8dcf LibPDF: Fix order of parameter, text, and current transform matrix
PDF spec 1.7 5.3.3 Text Space Details gives the correct multiplication
order: parameters * textmatrix * ctm.

We used to do text * ctm * parameters
(AffineTransform::multiply() does left-multiplication).

This only matters if `text_state().rise` is non-zero. In practice,
it's almost always zero, in which case the paramter matrix is a
diagonal matrix that commutes.

Fixes the horizontal offset of "super" in Tests/LibPDF/text.pdf.
2024-01-18 14:01:30 +01:00
Nico Weber
6c65c18c40 LibPDF: Add spec ref to Renderer::calculate_text_rendering_matrix() 2024-01-18 14:01:30 +01:00
Nico Weber
1845a406ea LibPDF: Add debug settings for clipping paths and images 2024-01-17 08:42:56 +00:00
Nico Weber
2d8a22f4b4 LibPDF: Clip images too
Since we can't clip against a general path yet, this clips images
against the bounding box of the current clip path as well.

Clips for images are often rectangular, so this works out well.

(We wastefully still decode and color-convert the entire image.
In a follow-up, we could consider only converting the unclipped
part.)
2024-01-17 08:42:56 +00:00
Nico Weber
5615a2691a LibPDF: Extract activate_clip() / deactivate_clip() functions
No behavior change.
2024-01-17 08:42:56 +00:00
MacDue
d55867e563 LibPDF: Fix paths with negatively sized re (rect) commands
Turns out the width/height in a `re` command can be negative. This
results in rectangles with different winding orders. For example, a
negative width results in a reversed winding order.

Previously, this was lost by passing the rect through an
`AffineTransform` before constructing the path. So instead, this
constructs the rect path, and then transforms the resulting path.
2024-01-16 21:31:20 +00:00
Nico Weber
9a93f677f4 LibPDF: Mark text rendering matrix as dirty after TJ numbers
Mostly because I audited all places that assigned to `m_text_matrix`
after #22760.

This one is very difficult to trigger in practice.

`show_text()` marks the text rendering matrix dirty already,
so this only has an effect if the `TJ` array starts with a
number, and the matrix isn't marked dirty going in.

`Tm` caches the text rendering matrix, so I changed text.pdf
to contain:

```
1 0 0 1 45 130 Tm
[ 200 (Hello) -2000 (World) ] TJ T*
```

This first sets an x offset of 5 (on top of the normal 40), and
then undoes it (`200` is multiplied by font size (25) / -1000,
and `200 * 25 / -1000` is -5). Before this change, the topmost
"Hello World" ended up slightly indented.

Likely no behavior change in practice, but makes the code easier
to understand, and maybe it helps in the wild somewhere.
2024-01-15 08:39:04 +00:00
Nico Weber
f23f5dcd62 LibPDF: Mark text rendering matrix dirty for Td operator
0000342.pdf page 5 contains this snippet:

```
/T1_1 10.976 Tf
0 -31.643 TD
(This)Tj

1 0 0 1 54 745.563 Tm
22.181 -31.643 Td
[(vehicle)-270.926(uses)...
```

The `Tm` marked the text rendering matrix as dirty at the start,
but it then calls calculate_text_rendering_matrix() almost in the
next line, which recalculates the text rendering matrix and caches
the new matrix. The `Td` used to not mark it as dirty, and we'd
draw "vehicle" with an incorrect matrix.
2024-01-15 08:37:55 +00:00
Nico Weber
f4ee9a2333 LibPDF: Support drawing images with 16 bits per channel
This uses the tried-and-true "throw away the lower 8 bits" technique
for now. This lets us render  Tests/LibPDF/wide-gamut-only.pdf.
2024-01-12 16:20:46 -07:00
Nico Weber
5f85aff036 LibPDF: Move ColorSpace::style() to take ReadonlySpan<float>
All ColorSpace subclasses converted to float anyways, and this
allows us to save lots of float->Value->float conversions during
image color space processing.

A bit faster:

```
    N           Min           Max        Median         Avg       Stddev
x  50    0.99054313     1.0412271    0.99933481   1.0052408  0.012931916
+  50    0.97073889     1.0075941    0.97849107  0.98184034 0.0090329046
Difference at 95.0% confidence
	-0.0234004 +/- 0.00442595
	-2.32785% +/- 0.440287%
	(Student's t, pooled s = 0.0111541)
```
2024-01-12 12:37:56 +00:00
Nico Weber
f7fc2df8ac LibPDF: Simplify load_image() a tiny bit
Images can't use Pattern color spaces, so we'll always have a Color.

No behavior (or perf) change.
2024-01-10 23:26:57 +01:00
Nico Weber
df5451a889 LibPDF: Mark text rendering matrix dirty after changing it in text_begin
A certain PDF was drawing some text used `9 0 0 9 474.54 700.6801 Tm`
to set the text matrix to a matrix that scaled by 9 in one text object.

Then, after ending that text object, it had the following new text
object which contained nothing that invalidated the text matrix:

```
BT
/F1 7 Tf
/DeviceRGB CS
0 0 0 SC
10 TL
86.37849 21.908 Td
(Authorized licensed use limited to: ...) Tj
ET
```

`BT` did reset it as required, but since we didn't mark the matrix
as dirty, we never recomputed it and drew the additional text scaled
up 9x.
2024-01-10 19:42:08 +01:00
Nico Weber
4fd5d450be LibPDF: Add support for image masks
An image mask is a 1-bit-per-pixel bitmap that's black where the
current color should be painted, and white where it should be
transparent (think: like ink).

load_image() already converts images like this into 8-bit-per-pixel
images that have 0xff, 0xff, 0xff in rgb for opaque (originally 0 bit)
pixels and 0, 0, 0 in rgb for transparent pixels.

So we just move copy the image mask's image data into the alpha
channel and replace rgb with the current color, and then draw
it like a regular bitmap.
2024-01-10 09:10:11 +00:00
Nico Weber
e770cf06b0 LibPDF: Send jpeg data down the same path as all other data
JPEG images now honor decode arrays and color spaces.
2024-01-10 09:39:00 +01:00
Nico Weber
b63eb4a4dd LibPDF: Implement /Mask support with stream object argument 2023-12-23 20:39:11 +01:00
Nico Weber
a3507ef65b LibPDF: Move error for /ImageMask out of load_image()
...and tweak load_image() to support loading mask images
(which don't have a color space and are always 1 bit per pixel).
2023-12-23 20:39:11 +01:00
Nico Weber
3ad9782e25 LibPDF: Extract a apply_alpha_channel() function
No behavior change.
2023-12-23 20:39:11 +01:00
Nico Weber
4bd11c8eb4 LibPDF: Show a 'rendering unsupported' error for images with /Mask key 2023-12-23 20:39:11 +01:00
Nico Weber
387fecea7f LibPDF: Fix typo in a variable name
No behavior change.
2023-12-23 10:10:24 +01:00
Nico Weber
6032c06f6b Revert "LibPDF: Add basic tiled, coloured pattern rendering"
This reverts commit 8ff87911a3.
2023-12-21 19:24:56 +01:00
Nico Weber
7cb216c95b Revert "LibPDF: Offset PaintStyle when painting so pattern overlaps..."
This reverts commit 8c7fc4fe6c.
2023-12-21 19:24:56 +01:00
Nico Weber
6de32e5359 LibPDF: Draw inline images
The idea is to massage the inline image data into something that
looks like a regular image, and then use the normal image drawing code:
We translate the inline image abbreviations to the expanded version at
rendering time, then unfilter (i.e. uncompress) the image data at
rendering time, and the go down the usual image drawing path.

Normal streams are unfiltered when they're first accessed, but
inline image streams live in a page's drawing operators, and this
fits the current approach of parsing a page's operators anew
every time the page is rendered.

(We also need to add some special-case handling for color spaces
of inline images: Inline images can use named color spaces, while
regular images always use direct color space objects.)
2023-12-20 12:45:16 -07:00
Nico Weber
022fce75a6 LibPDF: Get inline image data from parser to renderer
We create a inline_image_end operator that has all the relevant data
in a synthetic StreamObject.

inline_image_end is still a RENDERER_TODO(), so no real behavior
change. (Previously we'd call only inline_image_begin, so string the
todo message is about is now a bit different. But no interesting
behavior change.)
2023-12-20 12:19:08 +01:00
Nico Weber
b21f867e88 LibPDF: Don't crash on images with empty filter arrays
0000967.pdf page 2 contains a bunch of inline images with empty
filter arrays.
2023-12-20 12:19:08 +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
Kyle Pereira
8c7fc4fe6c LibPDF: Offset PaintStyle when painting so pattern overlaps properly 2023-12-10 16:44:24 +01:00
Kyle Pereira
8ff87911a3 LibPDF: Add basic tiled, coloured pattern rendering 2023-12-10 16:44:24 +01:00