aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/de.marvinborner.zathura-note.metainfo.xml1
-rw-r--r--license13
-rw-r--r--meson.build5
-rw-r--r--zathura-note/cairo_jpg.c612
-rw-r--r--zathura-note/cairo_jpg.h75
-rw-r--r--zathura-note/note.c244
6 files changed, 894 insertions, 56 deletions
diff --git a/data/de.marvinborner.zathura-note.metainfo.xml b/data/de.marvinborner.zathura-note.metainfo.xml
index 3ca7771..8958e32 100644
--- a/data/de.marvinborner.zathura-note.metainfo.xml
+++ b/data/de.marvinborner.zathura-note.metainfo.xml
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="addon">
<id>de.marvinborner.zathura-note</id>
- <project_license>WTFPL</project_license>
<extends>org.pwmt.zathura</extends>
<name>Zathura-note</name>
<summary>Notability .note support for zathura</summary>
diff --git a/license b/license
deleted file mode 100644
index b01085e..0000000
--- a/license
+++ /dev/null
@@ -1,13 +0,0 @@
- DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
- Version 2, December 2004
-
-Copyright (C) 2021 Marvin Borner <develop@marvinborner.de>
-
-Everyone is permitted to copy and distribute verbatim or modified
-copies of this license document, and changing it is allowed as long
-as the name is changed.
-
- DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. You just DO WHAT THE FUCK YOU WANT TO.
diff --git a/meson.build b/meson.build
index 8f7647f..5683db2 100644
--- a/meson.build
+++ b/meson.build
@@ -21,6 +21,7 @@ glib = dependency('glib-2.0')
cairo = dependency('cairo')
zip = dependency('libzip')
plist = dependency('libplist')
+jpeg = dependency('libjpeg')
build_dependencies = [
zathura,
@@ -28,7 +29,8 @@ build_dependencies = [
glib,
cairo,
zip,
- plist
+ plist,
+ jpeg
]
plugindir = zathura.get_pkgconfig_variable('plugindir')
@@ -53,6 +55,7 @@ flags = [
flags = cc.get_supported_arguments(flags)
sources = files(
+ 'zathura-note/cairo_jpg.c',
'zathura-note/plugin.c',
'zathura-note/note.c'
)
diff --git a/zathura-note/cairo_jpg.c b/zathura-note/cairo_jpg.c
new file mode 100644
index 0000000..cd47f20
--- /dev/null
+++ b/zathura-note/cairo_jpg.c
@@ -0,0 +1,612 @@
+/* Copyright 2018 Bernhard R. Fischer, 4096R/8E24F29D <bf@abenteuerland.at>
+ *
+ * This file is part of Cairo_JPG.
+ *
+ * Cairo_JPG is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Cairo_JPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Cairo_JPG. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*! \file cairo_jpg.c
+ * This file contains two functions for reading and writing JPEG files from
+ * and to Cairo image surfaces. It uses the functions from the libjpeg.
+ * Most of the code is directly derived from the online example at
+ * http://libjpeg-turbo.virtualgl.org/Documentation/Documentation
+ *
+ * All prototypes are defined in cairo_jpg.h All functions and their parameters
+ * and return values are described below directly at the functions. You may
+ * also have a look at the preprocessor macros defined below.
+ *
+ * To compile this code you need to have installed the packages libcairo2-dev
+ * and libjpeg-dev. Compile with the following to create an object file to link
+ * with your code:
+ * gcc -std=c99 -Wall -c `pkg-config cairo libjpeg --cflags --libs` cairo_jpg.c
+ * Use the following command to include the main() function and create an
+ * executable for testing of this code:
+ * gcc -std=c99 -Wall -o cairo_jpg -DCAIRO_JPEG_MAIN `pkg-config cairo libjpeg --cflags --libs` cairo_jpg.c
+ *
+ * @author Bernhard R. Fischer, 4096R/8E24F29D bf@abenteuerland.at
+ * @version 2020/01/18
+ * @license LGPL3.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cairo.h>
+#include <jpeglib.h>
+
+#include "cairo_jpg.h"
+
+/*! Macro to activate main() function. This is only used for testing. Comment
+ * it out (#undef) if you link this file to your own program.
+ */
+//#define CAIRO_JPEG_MAIN
+//
+/*! Define this to use an alternate implementation of
+ * cairo_image_surface_create_from_jpeg() which fstat(3)s the file before
+ * reading (see below). For huge files this /may/ be slightly faster.
+ */
+#undef CAIRO_JPEG_USE_FSTAT
+
+/*! This is the read block size for the stream reader
+ * cairo_image_surface_create_from_jpeg_stream().
+ */
+#ifdef USE_CAIRO_READ_FUNC_LEN_T
+#define CAIRO_JPEG_IO_BLOCK_SIZE 4096
+#else
+/*! Block size has to be one if cairo_read_func_t is in use because of the lack
+ * to detect EOF (truncated reads).
+ */
+#define CAIRO_JPEG_IO_BLOCK_SIZE 1
+/*! In case of original cairo_read_func_t is used fstat() should be used for
+ * performance reasons (see CAIRO_JPEG_USE_FSTAT above).
+ */
+#define CAIRO_JPEG_USE_FSTAT
+#endif
+
+/*! Define this to test jpeg creation with non-image surfaces. This is only for
+ * testing and is to be used together with CAIRO_JPEG_MAIN.
+ */
+#undef CAIRO_JPEG_TEST_SIMILAR
+#if defined(CAIRO_JPEG_TEST_SIMILAR) && defined(CAIRO_JPEG_MAIN)
+#include <cairo-pdf.h>
+#endif
+
+#ifndef LIBJPEG_TURBO_VERSION
+/*! This function makes a covnersion for "odd" pixel sizes which typically is a
+ * conversion from a 3-byte to a 4-byte (or more) pixel size or vice versa.
+ * The conversion is done from the source buffer src to the destination buffer
+ * dst. The caller MUST ensure that src and dst have the correct memory size.
+ * This is dw * num for dst and sw * num for src. src and dst may point to the
+ * same memory address.
+ * @param dst Pointer to destination buffer.
+ * @param dw Pixel width (in bytes) of pixels in destination buffer, dw >= 3.
+ * @param src Pointer to source buffer.
+ * @param sw Pixel width (in bytes) of pixels in source buffer, sw >= 3.
+ * @param num Number of pixels to convert, num >= 1;
+ */
+static void pix_conv(unsigned char *dst, int dw, const unsigned char *src, int sw, int num)
+{
+ int si, di;
+
+ // safety check
+ if (dw < 3 || sw < 3 || dst == NULL || src == NULL)
+ return;
+
+ num--;
+ for (si = num * sw, di = num * dw; si >= 0; si -= sw, di -= dw) {
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ dst[di + 2] = src[si];
+ dst[di + 1] = src[si + 1];
+ dst[di + 0] = src[si + 2];
+#else
+ // FIXME: This is untested, it may be wrong.
+ dst[di - 3] = src[si - 3];
+ dst[di - 2] = src[si - 2];
+ dst[di - 1] = src[si - 1];
+#endif
+ }
+}
+#endif
+
+/*! This function creates a JPEG file in memory from a Cairo image surface.
+ * @param sfc Pointer to a Cairo surface. It should be an image surface of
+ * either CAIRO_FORMAT_ARGB32 or CAIRO_FORMAT_RGB24. Other formats are
+ * converted to CAIRO_FORMAT_RGB24 before compression.
+ * Please note that this may give unexpected results because JPEG does not
+ * support transparency. Thus, default background color is used to replace
+ * transparent regions. The default background color is black if not specified
+ * explicitly. Thus converting e.g. PDF surfaces without having any specific
+ * background color set will apear with black background and not white as you
+ * might expect. In such cases it is suggested to manually convert the surface
+ * to RGB24 before calling this function.
+ * @param data Pointer to a memory pointer. This parameter receives a pointer
+ * to the memory area where the final JPEG data is found in memory. This
+ * function reserves the memory properly and it has to be freed by the caller
+ * with free(3).
+ * @param len Pointer to a variable of type size_t which will receive the final
+ * lenght of the memory buffer.
+ * @param quality Compression quality, 0-100.
+ * @return On success the function returns CAIRO_STATUS_SUCCESS. In case of
+ * error CAIRO_STATUS_INVALID_FORMAT is returned.
+ */
+cairo_status_t cairo_image_surface_write_to_jpeg_mem(cairo_surface_t *sfc, unsigned char **data,
+ size_t *len, int quality)
+{
+ struct jpeg_compress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ JSAMPROW row_pointer[1];
+ cairo_surface_t *other = NULL;
+
+ // check valid input format (must be IMAGE_SURFACE && (ARGB32 || RGB24))
+ if (cairo_surface_get_type(sfc) != CAIRO_SURFACE_TYPE_IMAGE ||
+ (cairo_image_surface_get_format(sfc) != CAIRO_FORMAT_ARGB32 &&
+ cairo_image_surface_get_format(sfc) != CAIRO_FORMAT_RGB24)) {
+ // create a similar surface with a proper format if supplied input format
+ // does not fulfill the requirements
+ double x1, y1, x2, y2;
+ other = sfc;
+ cairo_t *ctx = cairo_create(other);
+ // get extents of original surface
+ cairo_clip_extents(ctx, &x1, &y1, &x2, &y2);
+ cairo_destroy(ctx);
+
+ // create new image surface
+ sfc = cairo_surface_create_similar_image(other, CAIRO_FORMAT_RGB24, x2 - x1,
+ y2 - y1);
+ if (cairo_surface_status(sfc) != CAIRO_STATUS_SUCCESS)
+ return CAIRO_STATUS_INVALID_FORMAT;
+
+ // paint original surface to new surface
+ ctx = cairo_create(sfc);
+ cairo_set_source_surface(ctx, other, 0, 0);
+ cairo_paint(ctx);
+ cairo_destroy(ctx);
+ }
+
+ // finish queued drawing operations
+ cairo_surface_flush(sfc);
+
+ // init jpeg compression structures
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_compress(&cinfo);
+
+ // set compression parameters
+ jpeg_mem_dest(&cinfo, data, len);
+ cinfo.image_width = cairo_image_surface_get_width(sfc);
+ cinfo.image_height = cairo_image_surface_get_height(sfc);
+#ifdef LIBJPEG_TURBO_VERSION
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ //cinfo.in_color_space = JCS_EXT_BGRX;
+ cinfo.in_color_space = cairo_image_surface_get_format(sfc) == CAIRO_FORMAT_ARGB32 ?
+ JCS_EXT_BGRA :
+ JCS_EXT_BGRX;
+#else
+ //cinfo.in_color_space = JCS_EXT_XRGB;
+ cinfo.in_color_space = cairo_image_surface_get_format(sfc) == CAIRO_FORMAT_ARGB32 ?
+ JCS_EXT_ARGB :
+ JCS_EXT_XRGB;
+#endif
+ cinfo.input_components = 4;
+#else
+ cinfo.in_color_space = JCS_RGB;
+ cinfo.input_components = 3;
+#endif
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo, quality, TRUE);
+
+ // start compressor
+ jpeg_start_compress(&cinfo, TRUE);
+
+ // loop over all lines and compress
+ while (cinfo.next_scanline < cinfo.image_height) {
+#ifdef LIBJPEG_TURBO_VERSION
+ row_pointer[0] = cairo_image_surface_get_data(sfc) +
+ (cinfo.next_scanline * cairo_image_surface_get_stride(sfc));
+#else
+ unsigned char row_buf[3 * cinfo.image_width];
+ pix_conv(row_buf, 3,
+ cairo_image_surface_get_data(sfc) +
+ (cinfo.next_scanline * cairo_image_surface_get_stride(sfc)),
+ 4, cinfo.image_width);
+ row_pointer[0] = row_buf;
+#endif
+ (void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
+ }
+
+ // finalize and close everything
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+
+ // destroy temporary image surface (if available)
+ if (other != NULL)
+ cairo_surface_destroy(sfc);
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+/*! This is the internal write function which is called by
+ * cairo_image_surface_write_to_jpeg(). It is not exported.
+ */
+static cairo_status_t cj_write(void *closure, const unsigned char *data, unsigned int length)
+{
+ return write((intptr_t)closure, data, length) < length ? CAIRO_STATUS_WRITE_ERROR :
+ CAIRO_STATUS_SUCCESS;
+}
+
+/*! This function writes JPEG file data from a Cairo image surface by using the
+ * user-supplied stream writer function write_func().
+ * @param sfc Pointer to a Cairo *image* surface. Its format must either be
+ * CAIRO_FORMAT_ARGB32 or CAIRO_FORMAT_RGB24. Other formats are not supported
+ * by this function, yet.
+ * @param write_func Function pointer to a function which is actually writing
+ * the data.
+ * @param closure Pointer to user-supplied variable which is directly passed to
+ * write_func().
+ * @param quality Compression quality, 0-100.
+ * @return This function calles cairo_image_surface_write_to_jpeg_mem() and
+ * returns its return value.
+ */
+cairo_status_t cairo_image_surface_write_to_jpeg_stream(cairo_surface_t *sfc,
+ cairo_write_func_t write_func,
+ void *closure, int quality)
+{
+ cairo_status_t e;
+ unsigned char *data = NULL;
+ size_t len = 0;
+
+ // create JPEG data in memory from surface
+ if ((e = cairo_image_surface_write_to_jpeg_mem(sfc, &data, &len, quality)) !=
+ CAIRO_STATUS_SUCCESS)
+ return e;
+
+ // write whole memory block with stream function
+ e = write_func(closure, data, len);
+
+ // free JPEG memory again and return the return value
+ free(data);
+ return e;
+}
+
+/*! This function creates a JPEG file from a Cairo image surface.
+ * @param sfc Pointer to a Cairo *image* surface. Its format must either be
+ * CAIRO_FORMAT_ARGB32 or CAIRO_FORMAT_RGB24. Other formats are not supported
+ * by this function, yet.
+ * @param filename Pointer to the filename.
+ * @param quality Compression quality, 0-100.
+ * @return In case of success CAIRO_STATUS_SUCCESS is returned. If an error
+ * occured while opening/creating the file CAIRO_STATUS_DEVICE_ERROR is
+ * returned. The error can be tracked down by inspecting errno(3). The function
+ * internally calles cairo_image_surface_write_to_jpeg_stream() and returnes
+ * its return value respectively (see there).
+ */
+cairo_status_t cairo_image_surface_write_to_jpeg(cairo_surface_t *sfc, const char *filename,
+ int quality)
+{
+ cairo_status_t e;
+ int outfile;
+
+ // Open/create new file
+ if ((outfile = open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) ==
+ -1)
+ return CAIRO_STATUS_DEVICE_ERROR;
+
+ // write surface to file
+ e = cairo_image_surface_write_to_jpeg_stream(sfc, cj_write, (void *)(intptr_t)outfile,
+ quality);
+
+ // close file again and return
+ close(outfile);
+ return e;
+}
+
+/*! This function decompresses a JPEG image from a memory buffer and creates a
+ * Cairo image surface.
+ * @param data Pointer to JPEG data (i.e. the full contents of a JPEG file read
+ * into this buffer).
+ * @param len Length of buffer in bytes.
+ * @return Returns a pointer to a cairo_surface_t structure. It should be
+ * checked with cairo_surface_status() for errors.
+ */
+cairo_surface_t *cairo_image_surface_create_from_jpeg_mem(void *data, size_t len)
+{
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ JSAMPROW row_pointer[1];
+ cairo_surface_t *sfc;
+
+ // initialize jpeg decompression structures
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_decompress(&cinfo);
+ jpeg_mem_src(&cinfo, data, len);
+ (void)jpeg_read_header(&cinfo, TRUE);
+
+#ifdef LIBJPEG_TURBO_VERSION
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ cinfo.out_color_space = JCS_EXT_BGRA;
+#else
+ cinfo.out_color_space = JCS_EXT_ARGB;
+#endif
+#else
+ cinfo.out_color_space = JCS_RGB;
+#endif
+
+ // start decompressor
+ (void)jpeg_start_decompress(&cinfo);
+
+ // create Cairo image surface
+ sfc = cairo_image_surface_create(CAIRO_FORMAT_RGB24, cinfo.output_width,
+ cinfo.output_height);
+ if (cairo_surface_status(sfc) != CAIRO_STATUS_SUCCESS) {
+ jpeg_destroy_decompress(&cinfo);
+ return sfc;
+ }
+
+ // loop over all scanlines and fill Cairo image surface
+ while (cinfo.output_scanline < cinfo.output_height) {
+ unsigned char *row_address =
+ cairo_image_surface_get_data(sfc) +
+ (cinfo.output_scanline * cairo_image_surface_get_stride(sfc));
+ row_pointer[0] = row_address;
+ (void)jpeg_read_scanlines(&cinfo, row_pointer, 1);
+#ifndef LIBJPEG_TURBO_VERSION
+ pix_conv(row_address, 4, row_address, 3, cinfo.output_width);
+#endif
+ }
+
+ // finish and close everything
+ cairo_surface_mark_dirty(sfc);
+ (void)jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+
+ // set jpeg mime data
+ cairo_surface_set_mime_data(sfc, CAIRO_MIME_TYPE_JPEG, data, len, free, data);
+
+ return sfc;
+}
+
+/*! This function reads a JPEG image from a stream and creates a Cairo image
+ * surface.
+ * @param read_func Pointer to function which reads data.
+ * @param closure Pointer which is passed to read_func().
+ * @return Returns a pointer to a cairo_surface_t structure. It should be
+ * checked with cairo_surface_status() for errors.
+ * @note If the surface returned is invalid you can use errno(3) to determine
+ * further reasons. Errno is set according to realloc(3). If you
+ * intend to check errno you shall set it to 0 before calling this function
+ * because it modifies errno only in case of an error.
+ */
+#ifdef USE_CAIRO_READ_FUNC_LEN_T
+cairo_surface_t *cairo_image_surface_create_from_jpeg_stream(cairo_read_func_len_t read_func,
+ void *closure)
+#else
+cairo_surface_t *cairo_image_surface_create_from_jpeg_stream(cairo_read_func_t read_func,
+ void *closure)
+#endif
+{
+ cairo_surface_t *sfc;
+ void *data, *tmp;
+ ssize_t len, rlen;
+ int eof = 0;
+
+ // read all data into memory buffer in blocks of CAIRO_JPEG_IO_BLOCK_SIZE
+ for (len = 0, data = NULL; !eof; len += rlen) {
+ // grow memory buffer and check for error
+ if ((tmp = realloc(data, len + CAIRO_JPEG_IO_BLOCK_SIZE)) == NULL)
+ break;
+ data = tmp;
+
+ // read bytes into buffer and check for error
+ rlen = read_func(closure, data + len, CAIRO_JPEG_IO_BLOCK_SIZE);
+#ifdef USE_CAIRO_READ_FUNC_LEN_T
+ // check for error
+ if (rlen == -1)
+ break;
+
+ // check if EOF occured
+ if (rlen < CAIRO_JPEG_IO_BLOCK_SIZE)
+ eof++;
+#else
+ // check for error
+ if (rlen == CAIRO_STATUS_READ_ERROR)
+ eof++;
+#endif
+ }
+
+ // check for error in read loop
+ if (!eof) {
+ free(data);
+ return cairo_image_surface_create(CAIRO_FORMAT_INVALID, 0, 0);
+ }
+
+ // call jpeg decompression and return surface
+ sfc = cairo_image_surface_create_from_jpeg_mem(data, len);
+ if (cairo_surface_status(sfc) != CAIRO_STATUS_SUCCESS)
+ free(data);
+
+ return sfc;
+}
+
+#ifdef CAIRO_JPEG_USE_FSTAT
+/*! This function reads an JPEG image from a file an creates a Cairo image
+ * surface. Internally the filesize is determined with fstat(2) and then the
+ * whole data is read at once.
+ * @param filename Pointer to filename of JPEG file.
+ * @return Returns a pointer to a cairo_surface_t structure. It should be
+ * checked with cairo_surface_status() for errors.
+ * @note If the returned surface is invalid you can use errno to determine
+ * further reasons. Errno is set according to fopen(3) and malloc(3). If you
+ * intend to check errno you shall set it to 0 before calling this function
+ * because it does not modify errno itself.
+ */
+cairo_surface_t *cairo_image_surface_create_from_jpeg(const char *filename)
+{
+ void *data;
+ int infile;
+ struct stat stat;
+
+ // open input file
+ if ((infile = open(filename, O_RDONLY)) == -1)
+ return cairo_image_surface_create(CAIRO_FORMAT_INVALID, 0, 0);
+
+ // get stat structure for file size
+ if (fstat(infile, &stat) == -1)
+ return cairo_image_surface_create(CAIRO_FORMAT_INVALID, 0, 0);
+
+ // allocate memory
+ if ((data = malloc(stat.st_size)) == NULL)
+ return cairo_image_surface_create(CAIRO_FORMAT_INVALID, 0, 0);
+
+ // read data
+ if (read(infile, data, stat.st_size) < stat.st_size)
+ return cairo_image_surface_create(CAIRO_FORMAT_INVALID, 0, 0);
+
+ close(infile);
+
+ return cairo_image_surface_create_from_jpeg_mem(data, stat.st_size);
+}
+
+#else
+
+/*! This is the read function which is called by
+ * cairo_image_surface_create_from_jpeg_stream() (non-fstat-version below). It
+ * is not exported.
+ */
+#ifdef USE_CAIRO_READ_FUNC_LEN_T
+static ssize_t cj_read(void *closure, unsigned char *data, unsigned int length)
+{
+ return read((intptr_t)closure, data, length);
+}
+#else
+static cairo_status_t cj_read(void *closure, unsigned char *data, unsigned int length)
+{
+ return read((intptr_t)closure, data, length) < length ? CAIRO_STATUS_READ_ERROR :
+ CAIRO_STATUS_SUCCESS;
+}
+#endif
+
+/*! This function reads an JPEG image from a file an creates a Cairo image
+ * surface. Internally the function calls
+ * cairo_image_surface_create_from_jpeg_stream() to actually read the data.
+ * @param filename Pointer to filename of JPEG file.
+ * @return Returns a pointer to a cairo_surface_t structure. It should be
+ * checked with cairo_surface_status() for errors.
+ * @note If the returned surface is invalid you can use errno to determine
+ * further reasons. Errno is set according to fopen(3) and malloc(3). If you
+ * intend to check errno you shall set it to 0 before calling this function
+ * because it does not modify errno itself.
+ */
+cairo_surface_t *cairo_image_surface_create_from_jpeg(const char *filename)
+{
+ cairo_surface_t *sfc;
+ int infile;
+
+ // open input file
+ if ((infile = open(filename, O_RDONLY)) == -1)
+ return cairo_image_surface_create(CAIRO_FORMAT_INVALID, 0, 0);
+
+ // call stream loading function
+ sfc = cairo_image_surface_create_from_jpeg_stream(cj_read, (void *)(intptr_t)infile);
+ close(infile);
+
+ return sfc;
+}
+
+#endif
+
+#ifdef CAIRO_JPEG_MAIN
+#include <string.h>
+#include <strings.h>
+
+int strrcasecmp(const char *s1, const char *s2)
+{
+ int off = (int)strlen(s1) -
+ (int)strlen(s2); // typecast size_t to int because size_t typically is unsigned
+ return strcasecmp(s1 + (off < 0 ? 0 : off), s2);
+}
+
+/*! Main routine, only for testing. #undef CAIRO_JPEG_MAIN or simply delete
+ * this part if you link this file to your own program.
+ */
+int main(int argc, char **argv)
+{
+ cairo_surface_t *sfc;
+
+#ifndef CAIRO_JPEG_TEST_SIMILAR
+ if (argc < 3) {
+ fprintf(stderr, "usage: %s <infile> <outfile>\n", argv[0]);
+ return 1;
+ }
+
+ // test input file type and read file
+ if (!strrcasecmp(argv[1], ".png")) {
+ // read PNG file
+ sfc = cairo_image_surface_create_from_png(argv[1]);
+ } else if (!strrcasecmp(argv[1], ".jpg")) {
+ // read JPEG file
+ sfc = cairo_image_surface_create_from_jpeg(argv[1]);
+ } else {
+ fprintf(stderr, "source file is neither JPG nor PNG\n");
+ return 1;
+ }
+
+ // check surface status
+ if (cairo_surface_status(sfc) != CAIRO_STATUS_SUCCESS) {
+ fprintf(stderr, "error loading image: %s",
+ cairo_status_to_string(cairo_surface_status(sfc)));
+ return 2;
+ }
+
+ // test output file type and write file
+ if (!strrcasecmp(argv[2], ".png")) {
+ // write PNG file
+ cairo_surface_write_to_png(sfc, argv[2]);
+ } else if (!strrcasecmp(argv[2], ".jpg")) {
+ // write JPEG file
+ cairo_image_surface_write_to_jpeg(sfc, argv[2], 90);
+ } else {
+ fprintf(stderr, "destination file is neither JPG nor PNG\n");
+ return 1;
+ }
+
+ cairo_surface_destroy(sfc);
+
+#else
+ sfc = cairo_pdf_surface_create("xyz.pdf", 595.276, 841.890);
+
+ cairo_t *ctx = cairo_create(sfc);
+ cairo_set_source_rgb(ctx, 1, 1, 1);
+ cairo_paint(ctx);
+ cairo_move_to(ctx, 100, 100);
+ cairo_set_source_rgb(ctx, 1, 0, 0);
+ cairo_set_line_width(ctx, 3);
+ cairo_line_to(ctx, 400, 400);
+ cairo_stroke(ctx);
+ cairo_destroy(ctx);
+
+ cairo_image_surface_write_to_jpeg(sfc, "xyz.jpg", 90);
+ cairo_surface_destroy(sfc);
+#endif
+
+ return 0;
+}
+
+#endif
diff --git a/zathura-note/cairo_jpg.h b/zathura-note/cairo_jpg.h
new file mode 100644
index 0000000..5467581
--- /dev/null
+++ b/zathura-note/cairo_jpg.h
@@ -0,0 +1,75 @@
+/* Copyright 2018 Bernhard R. Fischer, 4096R/8E24F29D <bf@abenteuerland.at>
+ *
+ * This file is part of Cairo_JPG.
+ *
+ * Cairo_JPG is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Cairo_JPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Cairo_JPG. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef CAIRO_JPEG_H
+#define CAIRO_JPEG_H
+
+/*! \file cairo_jpg.h
+ * This file contains all prototypes for the Cairo-JPEG functions implemented
+ * in cairo_jpg.c. See there for the function documentation.
+ *
+ * @author Bernhard R. Fischer, 4096R/8E24F29D <bf@abenteuerland.at>
+ * @version 2018/12/11
+ * @license LGPL3
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <cairo.h>
+#include <stddef.h>
+
+#ifdef USE_CAIRO_READ_FUNC_LEN_T
+/*! This is the type for the stream read function. Which must be implemented by
+ * the user if cairo_image_surface_create_from_jpeg_stream() is used. Please
+ * note that this prototype is slightly different from cairo_read_func_t which
+ * is used by cairo_image_surface_create_from_png_stream().
+ * This new prototype is defined because the original prototype
+ * cairo_read_func_t does not allow to detect truncated reads. This issue was
+ * discussed on the cairographics mailinglist, see
+ * https://lists.cairographics.org/archives/cairo/2016-March/027298.html
+ * @param closure This parameter is directly passed through by
+ * cairo_image_surface_create_from_jpeg_stream().
+ * @param data Pointer to data buffer which will receive the data.
+ * @param length Size of the data buffer in bytes.
+ * @return This function must return the actually length that was read into the
+ * buffer. This may actually be less than length which indicates an EOF. In
+ * case of any fatal unrecoverable error on the input stream -1 shall be
+ * returned.
+ */
+typedef ssize_t (*cairo_read_func_len_t)(void *closure, unsigned char *data, unsigned int length);
+#endif
+
+cairo_status_t cairo_image_surface_write_to_jpeg_mem(cairo_surface_t *sfc, unsigned char **data,
+ size_t *len, int quality);
+cairo_status_t cairo_image_surface_write_to_jpeg_stream(cairo_surface_t *sfc,
+ cairo_write_func_t write_func,
+ void *closure, int quality);
+cairo_status_t cairo_image_surface_write_to_jpeg(cairo_surface_t *sfc, const char *filename,
+ int quality);
+cairo_surface_t *cairo_image_surface_create_from_jpeg_mem(void *data, size_t len);
+#ifdef USE_CAIRO_READ_FUNC_LEN_T
+cairo_surface_t *cairo_image_surface_create_from_jpeg_stream(cairo_read_func_len_t read_func,
+ void *closure);
+#else
+cairo_surface_t *cairo_image_surface_create_from_jpeg_stream(cairo_read_func_t read_func,
+ void *closure);
+#endif
+cairo_surface_t *cairo_image_surface_create_from_jpeg(const char *filename);
+
+#endif
diff --git a/zathura-note/note.c b/zathura-note/note.c
index d25ba0c..282e17b 100644
--- a/zathura-note/note.c
+++ b/zathura-note/note.c
@@ -1,19 +1,59 @@
+// Copyright (c) 2021 Marvin Borner
+// WTFPL License (only for note.*)
+
+#include "cairo_jpg.h"
#include "plugin.h"
#include <plist/plist.h>
#include <stdio.h>
#include <zip.h>
+// Data struct for entire document
typedef struct {
zip_t *zip;
plist_t objects;
+ char *root_name;
double width, height; // Page size is constant
} note_document_t;
+// Data struct for single page
+typedef struct {
+ double start, end;
+ cairo_t *cairo;
+ zathura_page_t *page;
+} note_page_t;
+
// Found by reverse engineering
#define SESSION_OBJECTS_GENERAL_INFO 1
#define SESSION_OBJECTS_LAYOUT_INFO 2
+static void zip_load(zip_t *zip, const char *root_name, const char *path, void **buf,
+ size_t *length)
+{
+ char name[1024] = { 0 };
+ snprintf(name, sizeof(name), "%s/%s", root_name, path);
+ zip_stat_t stat;
+ zip_stat(zip, name, 0, &stat);
+ zip_file_t *file = zip_fopen(zip, name, 0);
+ if (!file) {
+ zip_error_t *err = zip_get_error(zip);
+ fprintf(stderr, "Couldn't find '%s' in zip: %s\n", name, zip_error_strerror(err));
+ *buf = 0;
+ *length = 0;
+ return;
+ }
+
+ *buf = malloc(stat.size);
+ *length = zip_fread(file, *buf, stat.size);
+ if (*length < stat.size) {
+ fprintf(stderr, "Unexpected size difference\n");
+ free(*buf);
+ *buf = 0;
+ *length = 0;
+ return;
+ }
+}
+
// For debugging/reverse engineering
#define INDENT 4
static void plist_dump(plist_t plist, int depth)
@@ -112,32 +152,17 @@ static void plist_dump(plist_t plist, int depth)
static zathura_error_t plist_load(zip_t *zip, plist_t *plist, const char *root_name,
const char *path)
{
- char name[1024] = { 0 };
- snprintf(name, sizeof(name), "%s/%s", root_name, path);
- zip_stat_t stat;
- zip_stat(zip, name, 0, &stat);
- zip_file_t *file = zip_fopen(zip, name, 0);
- if (!file) {
- zip_error_t *err = zip_get_error(zip);
- fprintf(stderr, "Couldn't find '%s' in zip: %s\n", name, zip_error_strerror(err));
- return ZATHURA_ERROR_INVALID_ARGUMENTS;
- }
+ void *bin;
+ size_t length;
+ zip_load(zip, root_name, path, &bin, &length);
- void *bin = malloc(stat.size);
- size_t length = zip_fread(file, bin, stat.size);
- if (length < stat.size) {
- fprintf(stderr, "Unexpected size difference\n");
- free(bin);
- return ZATHURA_ERROR_INVALID_ARGUMENTS;
- }
-
- if (!plist_is_binary(bin, stat.size)) {
+ if (!plist_is_binary(bin, length)) {
fprintf(stderr, "Unexpected file format of '%s'\n", path);
free(bin);
return ZATHURA_ERROR_INVALID_ARGUMENTS;
}
- plist_from_bin(bin, stat.size, plist);
+ plist_from_bin(bin, length, plist);
free(bin);
return ZATHURA_ERROR_OK;
}
@@ -162,17 +187,23 @@ static plist_t plist_access(plist_t plist, int length, ...)
va_start(va, length);
unsigned long uid = 0;
- const char **ptr;
+ const char **ptr, *dict_key;
plist_t current = plist;
- int i;
+ int i, array_index;
for (i = 0; i < length && current; i++) {
plist_type type = plist_get_node_type(current);
switch (type) {
case PLIST_ARRAY:
- current = plist_array_get_item(current, va_arg(va, int));
+ array_index = va_arg(va, int);
+ current = plist_array_get_item(current, array_index);
+ if (!current)
+ fprintf(stderr, "Couldn't find %d in array\n", array_index);
break;
case PLIST_DICT:
- current = plist_dict_get_item(current, va_arg(va, const char *));
+ dict_key = va_arg(va, const char *);
+ current = plist_dict_get_item(current, dict_key);
+ if (!current)
+ fprintf(stderr, "Couldn't find '%s' in dict\n", dict_key);
break;
case PLIST_UID: // Automatic tracing!
plist_get_uid_val(current, &uid);
@@ -207,7 +238,7 @@ static plist_t plist_access(plist_t plist, int length, ...)
plist_get_real_val(current, va_arg(va, double *));
goto end;
default:
- fprintf(stderr, "Fatal failure in access loop\n");
+ fprintf(stderr, "Unknown plist type in access loop\n");
}
}
@@ -225,7 +256,7 @@ end:
// TODO: Exit entire zathura in these conditions? Hmmm
if (!current)
- fprintf(stderr, "Even more fatal failure in access loop\n");
+ fprintf(stderr, "Fatal failure in access loop\n");
return current;
}
@@ -243,6 +274,14 @@ static plist_t plist_handwriting_overlay(plist_t objects)
return overlay;
}
+// Converts the strange "{42.123, 69.123}" format to respective floats
+static void plist_string_to_floats(const char *string, float *a, float *b)
+{
+ char *end;
+ *a = strtof(string + 1, &end);
+ *b = strtof(end + 2, NULL);
+}
+
// TODO: Find more elegant solution for page count (there doesn't seem to be)
static int plist_page_count(plist_t objects, double page_height)
{
@@ -335,11 +374,13 @@ GIRARA_HIDDEN zathura_error_t note_document_open(zathura_document_t *document)
note_document->objects = plist_dict_get_item(session_plist, "$objects");
if (!PLIST_IS_ARRAY(note_document->objects)) {
fprintf(stderr, "Invalid objects type\n");
+ free(note_document);
free(root_name);
return ZATHURA_ERROR_NOT_IMPLEMENTED;
}
note_document->zip = zip;
+ note_document->root_name = root_name;
note_document->width = plist_page_width(note_document->objects);
if (note_document->width < 1) {
@@ -352,17 +393,19 @@ GIRARA_HIDDEN zathura_error_t note_document_open(zathura_document_t *document)
zathura_document_set_number_of_pages(document, plist_page_count(note_document->objects,
note_document->height));
- free(root_name);
return ZATHURA_ERROR_OK;
}
GIRARA_HIDDEN zathura_error_t note_document_free(zathura_document_t *document, void *data)
{
+ (void)document;
+
if (!data)
return ZATHURA_ERROR_OK;
note_document_t *note_document = data;
zip_close(note_document->zip);
+ free(note_document->root_name);
return ZATHURA_ERROR_OK;
}
@@ -372,24 +415,143 @@ GIRARA_HIDDEN zathura_error_t note_page_init(zathura_page_t *page)
zathura_page_set_width(page, note_document->width);
zathura_page_set_height(page, note_document->height);
+ double height = zathura_page_get_height(page);
+ unsigned int number = zathura_page_get_index(page);
+
+ note_page_t *note_page = malloc(sizeof(*note_page));
+ note_page->page = page;
+ note_page->start = height * number;
+ note_page->end = height * (number + 1);
+ zathura_page_set_data(page, note_page);
+
return ZATHURA_ERROR_OK;
}
GIRARA_HIDDEN zathura_error_t note_page_clear(zathura_page_t *page, void *data)
{
+ (void)page;
+ free(data);
return ZATHURA_ERROR_OK;
}
-static void note_page_render_object(cairo_t *cairo, plist_t objects, int index)
+typedef struct {
+ char *data;
+ size_t length;
+} cairo_read_closure;
+
+// Cairo is weird. Why can't we just pass a data buffer directly (like with cairo_jpg)?!
+static cairo_status_t cairo_read(void *data, unsigned char *buf, unsigned int length)
{
+ cairo_read_closure *closure = data;
+
+ if (length > closure->length)
+ return CAIRO_STATUS_READ_ERROR;
+
+ memcpy(buf, closure->data, length);
+
+ closure->length -= length;
+ closure->data += length;
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_surface_t *cairo_surface_scale(cairo_surface_t *surface, float width, float height)
+{
+ int unscaled_width = cairo_image_surface_get_width(surface);
+ int unscaled_height = cairo_image_surface_get_height(surface);
+ cairo_surface_t *result = cairo_surface_create_similar(
+ surface, cairo_surface_get_content(surface), width, height);
+ cairo_t *cairo = cairo_create(result);
+ cairo_scale(cairo, width / (float)unscaled_width, height / (float)unscaled_height);
+ cairo_set_source_surface(cairo, surface, 0, 0);
+ cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(cairo);
+ cairo_destroy(cairo);
+ return result;
+}
+
+static void note_page_render_image_object(note_page_t *page, plist_t objects, int index)
+{
+ char missing = 0;
+ plist_access(objects, 6, index, "figure", "FigureBackgroundObjectKey",
+ "kImageObjectSnapshotKey", "imageIsMissing", &missing);
+ if (missing)
+ return;
+
char *position = 0;
size_t position_length = 0;
plist_access(objects, 4, index, "documentContentOrigin", &position, &position_length);
- printf("%.*s\n", (int)position_length, position);
+ float x, y;
+ plist_string_to_floats(position, &x, &y);
+
+ char *size = 0;
+ size_t size_length = 0;
+ plist_access(objects, 4, index, "unscaledContentSize", &size, &size_length);
+ float width, height;
+ plist_string_to_floats(size, &width, &height);
+
+ if (y < page->start || y + height > page->end)
+ return;
+
+ char *path = 0;
+ size_t path_length = 0;
+ plist_access(objects, 7, index, "figure", "FigureBackgroundObjectKey",
+ "kImageObjectSnapshotKey", "relativePath", &path, &path_length);
+
+ char is_jpeg = 0; // 0 means png
+ plist_access(objects, 6, index, "figure", "FigureBackgroundObjectKey",
+ "kImageObjectSnapshotKey", "saveAsJPEG", &is_jpeg);
+
+ note_document_t *note_document =
+ zathura_document_get_data(zathura_page_get_document(page->page));
+ zip_t *zip = note_document->zip;
+ void *data;
+ size_t length;
+ zip_load(zip, note_document->root_name, path, &data, &length);
+ if (!data || !length) {
+ fprintf(stderr, "Invalid media object '%s' in zip\n", path);
+ return;
+ }
+
+ cairo_surface_t *surface = 0;
+ if (is_jpeg) {
+ surface = cairo_image_surface_create_from_jpeg_mem(data, length);
+ } else {
+ cairo_read_closure closure = { .data = data, .length = length };
+ surface = cairo_image_surface_create_from_png_stream(cairo_read, &closure);
+ }
+
+ if (!surface || cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
+ fprintf(stderr, "Invalid surface from png stream\n");
+ return;
+ }
+
+ surface = cairo_surface_scale(surface, width, height);
+
+ cairo_set_source_surface(page->cairo, surface, x, y - page->start);
+ cairo_paint(page->cairo);
+ cairo_surface_flush(surface);
+ cairo_surface_destroy(surface);
+}
+
+static void note_page_render_object(note_page_t *page, plist_t objects, int index)
+{
+ char *class = 0;
+ size_t class_length = 0;
+ plist_access(objects, 5, index, "$class", "$classname", &class, &class_length);
+
+ if (!memcmp(class, "ImageMediaObject", class_length)) {
+ note_page_render_image_object(page, objects, index);
+ } else if (!memcmp(class, "TextBlockMediaObject", class_length)) {
+ // TODO
+ } else {
+ fprintf(stderr, "Unknown media object type '%.*s', please report\n",
+ (int)class_length, class);
+ }
}
// It doesn't really matter if something in here fails
-static void note_page_render_objects(cairo_t *cairo, plist_t objects)
+static void note_page_render_objects(note_page_t *page, plist_t objects)
{
plist_t objects_array =
plist_access(objects, 3, SESSION_OBJECTS_LAYOUT_INFO, "mediaObjects", "NS.objects");
@@ -404,7 +566,7 @@ static void note_page_render_objects(cairo_t *cairo, plist_t objects)
size_t index;
plist_get_uid_val(val, &index);
- note_page_render_object(cairo, objects, index);
+ note_page_render_object(page, objects, index);
}
}
@@ -415,6 +577,9 @@ GIRARA_HIDDEN zathura_error_t note_page_render_cairo(zathura_page_t *page, void
return ZATHURA_ERROR_NOT_IMPLEMENTED;
note_document_t *note_document = zathura_document_get_data(zathura_page_get_document(page));
+ note_page_t *note_page = data;
+ note_page->cairo = cairo;
+
plist_t overlay = plist_handwriting_overlay(note_document->objects);
if (!overlay)
return ZATHURA_ERROR_NOT_IMPLEMENTED;
@@ -422,13 +587,8 @@ GIRARA_HIDDEN zathura_error_t note_page_render_cairo(zathura_page_t *page, void
/* plist_dump(note_document->session_plist, 0); */
/* return ZATHURA_ERROR_OK; */
- double height = zathura_page_get_height(page);
- unsigned int number = zathura_page_get_index(page);
- double page_start = height * number;
- double page_end = height * (number + 1);
-
// Render all media objects (images, ...)
- note_page_render_objects(cairo, note_document->objects);
+ note_page_render_objects(note_page, note_document->objects);
// Array of points on curve
size_t curves_length = 0;
@@ -463,15 +623,17 @@ GIRARA_HIDDEN zathura_error_t note_page_render_cairo(zathura_page_t *page, void
(float)(color[1] & 0xff) / 255,
(float)(color[2] & 0xff) / 255,
(float)(color[3] & 0xff) / 255);
+
+ // TODO: Fractional curve widths (?)
cairo_set_line_width(cairo, curves_width[i]);
- if (curves[pos + 1] >= page_start && curves[pos + 1] <= page_end)
- cairo_move_to(cairo, curves[pos], curves[pos + 1] - page_start);
+ if (curves[pos + 1] >= note_page->start && curves[pos + 1] <= note_page->end)
+ cairo_move_to(cairo, curves[pos], curves[pos + 1] - note_page->start);
// TODO: Render as bezier curves
for (unsigned int j = pos; j < pos + length * 2; j += 2)
- if (curves[j + 1] >= page_start && curves[j + 1] <= page_end)
- cairo_line_to(cairo, curves[j], curves[j + 1] - page_start);
+ if (curves[j + 1] >= note_page->start && curves[j + 1] <= note_page->end)
+ cairo_line_to(cairo, curves[j], curves[j + 1] - note_page->start);
cairo_stroke(cairo);
pos += length * 2;