qpdf-compress 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/fonts.h ADDED
@@ -0,0 +1,5 @@
1
+ #pragma once
2
+
3
+ #include <qpdf/QPDF.hh>
4
+
5
+ void optimizeFonts(QPDF &qpdf);
@@ -0,0 +1,15 @@
1
+ // FNV-1a hash utility — shared between image deduplication and stream
2
+ // deduplication
3
+ #pragma once
4
+
5
+ #include <cstddef>
6
+ #include <cstdint>
7
+
8
+ inline uint64_t fnv1aHash(const uint8_t *data, size_t size) {
9
+ uint64_t hash = 14695981039346656037ULL;
10
+ for (size_t i = 0; i < size; ++i) {
11
+ hash ^= static_cast<uint64_t>(data[i]);
12
+ hash *= 1099511628211ULL;
13
+ }
14
+ return hash;
15
+ }
package/src/images.cc CHANGED
@@ -1,10 +1,20 @@
1
1
  #include "images.h"
2
+ #include "hash_utils.h"
2
3
  #include "jpeg.h"
3
4
 
4
5
  #include <algorithm>
5
6
  #include <cmath>
6
7
  #include <cstdint>
8
+ #include <cstdlib>
7
9
  #include <cstring>
10
+
11
+ #ifdef __APPLE__
12
+ #include <xlocale.h>
13
+ #elif defined(_WIN32)
14
+ #include <locale.h>
15
+ #else
16
+ #include <locale.h>
17
+ #endif
8
18
  #include <limits>
9
19
  #include <map>
10
20
  #include <set>
@@ -12,6 +22,17 @@
12
22
 
13
23
  #include <qpdf/Buffer.hh>
14
24
 
25
+ // locale-independent double parser for PDF content stream operands
26
+ static double parsePdfDouble(const std::string &s) {
27
+ #ifdef _WIN32
28
+ static _locale_t c_locale = _create_locale(LC_NUMERIC, "C");
29
+ return _strtod_l(s.c_str(), nullptr, c_locale);
30
+ #else
31
+ static locale_t c_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
32
+ return strtod_l(s.c_str(), nullptr, c_locale);
33
+ #endif
34
+ }
35
+
15
36
  // ---------------------------------------------------------------------------
16
37
  // Color space helpers
17
38
  // ---------------------------------------------------------------------------
@@ -223,12 +244,12 @@ getImageRenderedSizes(QPDFPageObjectHelper &page) {
223
244
  // cm operator: a b c d e f cm
224
245
  try {
225
246
  Matrix m;
226
- m.a = std::stod(operandStack[operandStack.size() - 6]);
227
- m.b = std::stod(operandStack[operandStack.size() - 5]);
228
- m.c = std::stod(operandStack[operandStack.size() - 4]);
229
- m.d = std::stod(operandStack[operandStack.size() - 3]);
230
- m.e = std::stod(operandStack[operandStack.size() - 2]);
231
- m.f = std::stod(operandStack[operandStack.size() - 1]);
247
+ m.a = parsePdfDouble(operandStack[operandStack.size() - 6]);
248
+ m.b = parsePdfDouble(operandStack[operandStack.size() - 5]);
249
+ m.c = parsePdfDouble(operandStack[operandStack.size() - 4]);
250
+ m.d = parsePdfDouble(operandStack[operandStack.size() - 3]);
251
+ m.e = parsePdfDouble(operandStack[operandStack.size() - 2]);
252
+ m.f = parsePdfDouble(operandStack[operandStack.size() - 1]);
232
253
  ctm = multiply(m, ctm);
233
254
  } catch (...) {
234
255
  }
@@ -403,10 +424,7 @@ void optimizeImages(QPDF &qpdf, const CompressOptions &opts) {
403
424
  return;
404
425
 
405
426
  // replace stream data with JPEG
406
- std::string jpegStr(reinterpret_cast<char *>(jpegData.data()),
407
- jpegData.size());
408
- xobj.replaceStreamData(jpegStr, QPDFObjectHandle::newName("/DCTDecode"),
409
- QPDFObjectHandle::newNull());
427
+ replaceWithJpeg(xobj, jpegData);
410
428
 
411
429
  // update color space to DeviceRGB if converted from CMYK/ICCBased
412
430
  if (isCMYK || !dict.getKey("/ColorSpace").isName() ||
@@ -453,13 +471,7 @@ void deduplicateImages(QPDF &qpdf) {
453
471
  auto rawData = xobj.getRawStreamData();
454
472
  size_t size = rawData->getSize();
455
473
 
456
- // FNV-1a hash
457
- uint64_t hash = 14695981039346656037ULL;
458
- auto *p = rawData->getBuffer();
459
- for (size_t i = 0; i < size; ++i) {
460
- hash ^= static_cast<uint64_t>(p[i]);
461
- hash *= 1099511628211ULL;
462
- }
474
+ uint64_t hash = fnv1aHash(rawData->getBuffer(), size);
463
475
 
464
476
  hashGroups[hash].push_back({og, size, xobj});
465
477
  } catch (...) {
@@ -536,10 +548,7 @@ void optimizeExistingJpegs(QPDF &qpdf) {
536
548
  if (optimized.size() >= rawData->getSize())
537
549
  return;
538
550
 
539
- std::string jpegStr(reinterpret_cast<char *>(optimized.data()),
540
- optimized.size());
541
- xobj.replaceStreamData(jpegStr, QPDFObjectHandle::newName("/DCTDecode"),
542
- QPDFObjectHandle::newNull());
551
+ replaceWithJpeg(xobj, optimized);
543
552
  } catch (...) {
544
553
  }
545
554
  });
@@ -681,10 +690,7 @@ void downscaleImages(QPDF &qpdf, int maxDpi, int quality) {
681
690
  if (jpegData.size() >= rawData->getSize())
682
691
  return;
683
692
 
684
- std::string jpegStr(reinterpret_cast<char *>(jpegData.data()),
685
- jpegData.size());
686
- xobj.replaceStreamData(jpegStr, QPDFObjectHandle::newName("/DCTDecode"),
687
- QPDFObjectHandle::newNull());
693
+ replaceWithJpeg(xobj, jpegData);
688
694
 
689
695
  dict.replaceKey("/Width", QPDFObjectHandle::newInteger(newW));
690
696
  dict.replaceKey("/Height", QPDFObjectHandle::newInteger(newH));
@@ -702,11 +708,13 @@ void downscaleImages(QPDF &qpdf, int maxDpi, int quality) {
702
708
  }
703
709
 
704
710
  // ---------------------------------------------------------------------------
705
- // Grayscale detection — convert RGB images that are actually grayscale to
706
- // DeviceGray (1/3 the raw data size)
711
+ // Color space optimization — single pass over all images:
712
+ // 1. Replace ICCBased color spaces with Device equivalents
713
+ // 2. Convert RGB images that are actually grayscale to DeviceGray
714
+ // 3. Convert 8-bit grayscale images that are black & white to 1-bit
707
715
  // ---------------------------------------------------------------------------
708
716
 
709
- void convertGrayscaleImages(QPDF &qpdf) {
717
+ void optimizeColorSpaces(QPDF &qpdf) {
710
718
  std::set<QPDFObjGen> processed;
711
719
 
712
720
  forEachImage(qpdf, [&](const std::string & /*key*/, QPDFObjectHandle xobj,
@@ -719,20 +727,37 @@ void convertGrayscaleImages(QPDF &qpdf) {
719
727
 
720
728
  auto dict = xobj.getDict();
721
729
 
722
- // only handle 8-bit RGB images
723
- if (!dict.getKey("/BitsPerComponent").isInteger() ||
724
- dict.getKey("/BitsPerComponent").getIntValue() != 8)
725
- return;
726
-
730
+ // --- phase 1: strip ICC profiles ---
727
731
  auto cs = dict.getKey("/ColorSpace");
728
- if (!cs.isName() || cs.getName() != "/DeviceRGB")
732
+ if (cs.isArray() && cs.getArrayNItems() >= 2) {
733
+ auto csName = cs.getArrayItem(0);
734
+ if (csName.isName() && csName.getName() == "/ICCBased") {
735
+ auto profile = cs.getArrayItem(1);
736
+ if (profile.isStream()) {
737
+ auto n = profile.getDict().getKey("/N");
738
+ if (n.isInteger()) {
739
+ int components = static_cast<int>(n.getIntValue());
740
+ if (components == 3)
741
+ dict.replaceKey("/ColorSpace",
742
+ QPDFObjectHandle::newName("/DeviceRGB"));
743
+ else if (components == 1)
744
+ dict.replaceKey("/ColorSpace",
745
+ QPDFObjectHandle::newName("/DeviceGray"));
746
+ else if (components == 4)
747
+ dict.replaceKey("/ColorSpace",
748
+ QPDFObjectHandle::newName("/DeviceCMYK"));
749
+ }
750
+ }
751
+ }
752
+ }
753
+
754
+ // re-read after potential ICC replacement
755
+ cs = dict.getKey("/ColorSpace");
756
+ if (!cs.isName())
729
757
  return;
730
758
 
731
- // skip JPEG-compressed images — converting to raw gray + Flate would be
732
- // larger than the original JPEG. in lossy mode, optimizeImages downstream
733
- // re-encodes, but in lossless mode nothing would, causing size inflation.
734
- auto filter = dict.getKey("/Filter");
735
- if (filter.isName() && filter.getName() == "/DCTDecode")
759
+ if (!dict.getKey("/BitsPerComponent").isInteger() ||
760
+ dict.getKey("/BitsPerComponent").getIntValue() != 8)
736
761
  return;
737
762
 
738
763
  int width = 0, height = 0;
@@ -743,114 +768,86 @@ void convertGrayscaleImages(QPDF &qpdf) {
743
768
  if (width <= 0 || height <= 0)
744
769
  return;
745
770
 
746
- // decode pixels
747
- std::shared_ptr<Buffer> streamData;
748
- try {
749
- streamData = xobj.getStreamData(qpdf_dl_all);
750
- } catch (...) {
751
- return;
752
- }
753
-
754
771
  auto w = static_cast<size_t>(width);
755
772
  auto h = static_cast<size_t>(height);
756
- size_t expectedSize = w * h * 3;
757
- if (streamData->getSize() != expectedSize)
758
- return;
759
-
760
- const auto *pixels = streamData->getBuffer();
761
- size_t pixelCount = w * h;
762
773
 
763
- // check if all RGB triples have equal channels (R == G == B)
764
- bool isGray = true;
765
- for (size_t i = 0; i < pixelCount; ++i) {
766
- auto r = pixels[i * 3 + 0];
767
- auto g = pixels[i * 3 + 1];
768
- auto b = pixels[i * 3 + 2];
769
- if (r != g || g != b) {
770
- isGray = false;
771
- break;
774
+ // --- phase 2: RGB → grayscale detection ---
775
+ if (cs.getName() == "/DeviceRGB") {
776
+ // skip JPEG-compressed images — converting to raw gray + Flate would be
777
+ // larger than the original JPEG
778
+ auto filter = dict.getKey("/Filter");
779
+ if (filter.isName() && filter.getName() == "/DCTDecode")
780
+ return;
781
+ if (filter.isArray()) {
782
+ for (int i = 0; i < filter.getArrayNItems(); ++i) {
783
+ auto f = filter.getArrayItem(i);
784
+ if (f.isName() && f.getName() == "/DCTDecode")
785
+ return;
786
+ }
772
787
  }
773
- }
774
788
 
775
- if (!isGray)
776
- return;
789
+ std::shared_ptr<Buffer> streamData;
790
+ try {
791
+ streamData = xobj.getStreamData(qpdf_dl_all);
792
+ } catch (...) {
793
+ return;
794
+ }
777
795
 
778
- // build grayscale pixel buffer
779
- std::vector<uint8_t> grayPixels(pixelCount);
780
- for (size_t i = 0; i < pixelCount; ++i)
781
- grayPixels[i] = pixels[i * 3];
796
+ if (streamData->getSize() != w * h * 3)
797
+ return;
782
798
 
783
- // replace stream with raw grayscale data (Flate-compressed by QPDFWriter)
784
- std::string grayStr(reinterpret_cast<char *>(grayPixels.data()),
785
- grayPixels.size());
786
- xobj.replaceStreamData(grayStr, QPDFObjectHandle::newNull(),
787
- QPDFObjectHandle::newNull());
788
- dict.replaceKey("/ColorSpace", QPDFObjectHandle::newName("/DeviceGray"));
799
+ const auto *pixels = streamData->getBuffer();
800
+ size_t pixelCount = w * h;
789
801
 
790
- // remove JPEG-specific params
791
- if (dict.hasKey("/DecodeParms"))
792
- dict.removeKey("/DecodeParms");
793
- if (dict.hasKey("/Predictor"))
794
- dict.removeKey("/Predictor");
795
- });
796
- }
802
+ bool isGray = true;
803
+ for (size_t i = 0; i < pixelCount; ++i) {
804
+ if (pixels[i * 3] != pixels[i * 3 + 1] ||
805
+ pixels[i * 3 + 1] != pixels[i * 3 + 2]) {
806
+ isGray = false;
807
+ break;
808
+ }
809
+ }
797
810
 
798
- // ---------------------------------------------------------------------------
799
- // Bitonal conversion — convert 8-bit grayscale images that are effectively
800
- // black & white (all pixels near 0 or 255) to 1-bit, saving ~8x raw data
801
- // ---------------------------------------------------------------------------
811
+ if (!isGray)
812
+ return;
802
813
 
803
- void convertBitonalImages(QPDF &qpdf) {
804
- std::set<QPDFObjGen> processed;
814
+ std::vector<uint8_t> grayPixels(pixelCount);
815
+ for (size_t i = 0; i < pixelCount; ++i)
816
+ grayPixels[i] = pixels[i * 3];
805
817
 
806
- forEachImage(qpdf, [&](const std::string & /*key*/, QPDFObjectHandle xobj,
807
- QPDFObjectHandle /*xobjects*/,
808
- QPDFPageObjectHelper & /*page*/) {
809
- auto og = xobj.getObjGen();
810
- if (processed.count(og))
811
- return;
812
- processed.insert(og);
818
+ replaceWithRaw(xobj, grayPixels);
819
+ dict.replaceKey("/ColorSpace", QPDFObjectHandle::newName("/DeviceGray"));
813
820
 
814
- auto dict = xobj.getDict();
821
+ if (dict.hasKey("/DecodeParms"))
822
+ dict.removeKey("/DecodeParms");
823
+ if (dict.hasKey("/Predictor"))
824
+ dict.removeKey("/Predictor");
815
825
 
816
- // only handle 8-bit grayscale images
817
- if (!dict.getKey("/BitsPerComponent").isInteger() ||
818
- dict.getKey("/BitsPerComponent").getIntValue() != 8)
819
- return;
826
+ // fall through to bitonal check on the now-gray image
827
+ cs = dict.getKey("/ColorSpace");
828
+ }
820
829
 
821
- auto cs = dict.getKey("/ColorSpace");
822
- if (!cs.isName() || cs.getName() != "/DeviceGray")
830
+ // --- phase 3: grayscale → bitonal conversion ---
831
+ if (cs.getName() != "/DeviceGray")
823
832
  return;
824
833
 
825
- // skip images with masks (bitonal conversion may not be safe)
834
+ // skip images with masks
826
835
  if (dict.hasKey("/SMask") || dict.hasKey("/Mask"))
827
836
  return;
828
837
 
829
- int width = 0, height = 0;
830
- if (dict.getKey("/Width").isInteger())
831
- width = static_cast<int>(dict.getKey("/Width").getIntValue());
832
- if (dict.getKey("/Height").isInteger())
833
- height = static_cast<int>(dict.getKey("/Height").getIntValue());
834
- if (width <= 0 || height <= 0)
835
- return;
836
-
837
- // decode pixels
838
- std::shared_ptr<Buffer> streamData;
838
+ std::shared_ptr<Buffer> grayData;
839
839
  try {
840
- streamData = xobj.getStreamData(qpdf_dl_all);
840
+ grayData = xobj.getStreamData(qpdf_dl_all);
841
841
  } catch (...) {
842
842
  return;
843
843
  }
844
844
 
845
- auto w = static_cast<size_t>(width);
846
- auto h = static_cast<size_t>(height);
847
- if (streamData->getSize() != w * h)
845
+ if (grayData->getSize() != w * h)
848
846
  return;
849
847
 
850
- const auto *pixels = streamData->getBuffer();
848
+ const auto *pixels = grayData->getBuffer();
851
849
  size_t pixelCount = w * h;
852
850
 
853
- // check if all pixels are near black (<=32) or near white (>=224)
854
851
  bool isBitonal = true;
855
852
  for (size_t i = 0; i < pixelCount; ++i) {
856
853
  if (pixels[i] > 32 && pixels[i] < 224) {
@@ -862,29 +859,22 @@ void convertBitonalImages(QPDF &qpdf) {
862
859
  if (!isBitonal)
863
860
  return;
864
861
 
865
- // pack into 1-bit: 0 = black, 1 = white
866
- // each row padded to byte boundary
867
862
  size_t rowBytes = (w + 7) / 8;
868
863
  std::vector<uint8_t> bitonalData(rowBytes * h, 0);
869
864
 
870
865
  for (size_t y = 0; y < h; ++y) {
871
866
  for (size_t x = 0; x < w; ++x) {
872
- bool white = pixels[y * w + x] >= 224;
873
- if (white)
867
+ if (pixels[y * w + x] >= 224)
874
868
  bitonalData[y * rowBytes + x / 8] |=
875
869
  static_cast<uint8_t>(0x80 >> (x % 8));
876
870
  }
877
871
  }
878
872
 
879
- // only replace if 1-bit data is smaller than original raw stream
880
873
  auto rawData = xobj.getRawStreamData();
881
874
  if (bitonalData.size() >= rawData->getSize())
882
875
  return;
883
876
 
884
- std::string bitStr(reinterpret_cast<char *>(bitonalData.data()),
885
- bitonalData.size());
886
- xobj.replaceStreamData(bitStr, QPDFObjectHandle::newNull(),
887
- QPDFObjectHandle::newNull());
877
+ replaceWithRaw(xobj, bitonalData);
888
878
  dict.replaceKey("/BitsPerComponent", QPDFObjectHandle::newInteger(1));
889
879
 
890
880
  if (dict.hasKey("/DecodeParms"))
@@ -892,6 +882,43 @@ void convertBitonalImages(QPDF &qpdf) {
892
882
  if (dict.hasKey("/Predictor"))
893
883
  dict.removeKey("/Predictor");
894
884
  });
885
+
886
+ // strip ICC profiles from page-level color space resources
887
+ for (auto &page : QPDFPageDocumentHelper(qpdf).getAllPages()) {
888
+ auto resources = page.getObjectHandle().getKey("/Resources");
889
+ if (!resources.isDictionary())
890
+ continue;
891
+
892
+ auto colorSpaces = resources.getKey("/ColorSpace");
893
+ if (!colorSpaces.isDictionary())
894
+ continue;
895
+
896
+ for (auto &key : colorSpaces.getKeys()) {
897
+ auto cs = colorSpaces.getKey(key);
898
+ if (!cs.isArray() || cs.getArrayNItems() < 2)
899
+ continue;
900
+
901
+ auto csName = cs.getArrayItem(0);
902
+ if (!csName.isName() || csName.getName() != "/ICCBased")
903
+ continue;
904
+
905
+ auto profile = cs.getArrayItem(1);
906
+ if (!profile.isStream())
907
+ continue;
908
+
909
+ auto n = profile.getDict().getKey("/N");
910
+ if (!n.isInteger())
911
+ continue;
912
+
913
+ int components = static_cast<int>(n.getIntValue());
914
+ if (components == 3)
915
+ colorSpaces.replaceKey(key, QPDFObjectHandle::newName("/DeviceRGB"));
916
+ else if (components == 1)
917
+ colorSpaces.replaceKey(key, QPDFObjectHandle::newName("/DeviceGray"));
918
+ else if (components == 4)
919
+ colorSpaces.replaceKey(key, QPDFObjectHandle::newName("/DeviceCMYK"));
920
+ }
921
+ }
895
922
  }
896
923
 
897
924
  // ---------------------------------------------------------------------------
@@ -933,10 +960,7 @@ void optimizeSoftMasks(QPDF &qpdf) {
933
960
  if (optimized.size() >= rawData->getSize())
934
961
  return;
935
962
 
936
- std::string jpegStr(reinterpret_cast<char *>(optimized.data()),
937
- optimized.size());
938
- smask.replaceStreamData(jpegStr, QPDFObjectHandle::newName("/DCTDecode"),
939
- QPDFObjectHandle::newNull());
963
+ replaceWithJpeg(smask, optimized);
940
964
  } catch (...) {
941
965
  }
942
966
  });
package/src/images.h CHANGED
@@ -1,5 +1,9 @@
1
1
  #pragma once
2
2
 
3
+ #include <cstdint>
4
+ #include <string>
5
+ #include <vector>
6
+
3
7
  #include <qpdf/QPDF.hh>
4
8
  #include <qpdf/QPDFObjectHandle.hh>
5
9
  #include <qpdf/QPDFPageDocumentHelper.hh>
@@ -28,6 +32,21 @@ template <typename Fn> void forEachImage(QPDF &qpdf, Fn &&fn) {
28
32
  }
29
33
  }
30
34
 
35
+ // replace an image XObject's stream data with JPEG-encoded bytes
36
+ inline void replaceWithJpeg(QPDFObjectHandle xobj, std::vector<uint8_t> &data) {
37
+ std::string str(reinterpret_cast<char *>(data.data()), data.size());
38
+ xobj.replaceStreamData(str, QPDFObjectHandle::newName("/DCTDecode"),
39
+ QPDFObjectHandle::newNull());
40
+ }
41
+
42
+ // replace an image XObject's stream data with raw bytes (Flate-compressed by
43
+ // QPDFWriter)
44
+ inline void replaceWithRaw(QPDFObjectHandle xobj, std::vector<uint8_t> &data) {
45
+ std::string str(reinterpret_cast<char *>(data.data()), data.size());
46
+ xobj.replaceStreamData(str, QPDFObjectHandle::newNull(),
47
+ QPDFObjectHandle::newNull());
48
+ }
49
+
31
50
  struct CompressOptions {
32
51
  int skipThreshold = 0; // skip existing JPEGs at or below this quality
33
52
  int targetQuality = 0; // encode/re-encode at this quality
@@ -37,6 +56,5 @@ void optimizeImages(QPDF &qpdf, const CompressOptions &opts);
37
56
  void downscaleImages(QPDF &qpdf, int maxDpi, int quality);
38
57
  void deduplicateImages(QPDF &qpdf);
39
58
  void optimizeExistingJpegs(QPDF &qpdf);
40
- void convertGrayscaleImages(QPDF &qpdf);
41
- void convertBitonalImages(QPDF &qpdf);
59
+ void optimizeColorSpaces(QPDF &qpdf);
42
60
  void optimizeSoftMasks(QPDF &qpdf);
package/src/jpeg.cc CHANGED
@@ -8,6 +8,9 @@ void jpegErrorExit(j_common_ptr cinfo) {
8
8
  std::longjmp(myerr->jmpbuf, 1);
9
9
  }
10
10
 
11
+ // suppress non-fatal warnings (e.g. "Corrupt JPEG data: N extraneous bytes")
12
+ void jpegSilenceOutput(j_common_ptr) {}
13
+
11
14
  // ---------------------------------------------------------------------------
12
15
  // Lossless JPEG optimization
13
16
  // ---------------------------------------------------------------------------
@@ -22,6 +25,7 @@ static bool losslessJpegOptimizeImpl(const unsigned char *data, size_t size,
22
25
 
23
26
  srcinfo.err = jpeg_std_error(&jerr.pub);
24
27
  jerr.pub.error_exit = jpegErrorExit;
28
+ jerr.pub.output_message = jpegSilenceOutput;
25
29
  dstinfo.err = &jerr.pub;
26
30
 
27
31
  if (setjmp(jerr.jmpbuf)) {
@@ -95,6 +99,7 @@ static bool encodeJpegImpl(const unsigned char *pixels, int width, int height,
95
99
 
96
100
  cinfo.err = jpeg_std_error(&jerr.pub);
97
101
  jerr.pub.error_exit = jpegErrorExit;
102
+ jerr.pub.output_message = jpegSilenceOutput;
98
103
 
99
104
  if (setjmp(jerr.jmpbuf)) {
100
105
  jpeg_destroy_compress(&cinfo);
@@ -176,6 +181,7 @@ static int estimateJpegQualityImpl(const unsigned char *data, size_t size) {
176
181
 
177
182
  cinfo.err = jpeg_std_error(&jerr.pub);
178
183
  jerr.pub.error_exit = jpegErrorExit;
184
+ jerr.pub.output_message = jpegSilenceOutput;
179
185
 
180
186
  if (setjmp(jerr.jmpbuf)) {
181
187
  jpeg_destroy_decompress(&cinfo);
package/src/jpeg.h CHANGED
@@ -15,6 +15,7 @@ struct JpegErrorMgr {
15
15
  };
16
16
 
17
17
  void jpegErrorExit(j_common_ptr cinfo);
18
+ void jpegSilenceOutput(j_common_ptr cinfo);
18
19
 
19
20
  // rewrites Huffman tables at DCT coefficient level (2–15% savings, zero quality
20
21
  // loss)
package/src/optimize.h CHANGED
@@ -1,15 +1,7 @@
1
+ // umbrella include for all optimization passes
1
2
  #pragma once
2
3
 
3
- #include <qpdf/QPDF.hh>
4
-
5
- void stripMetadata(QPDF &qpdf);
6
- void removeUnusedFonts(QPDF &qpdf);
7
- void coalesceContentStreams(QPDF &qpdf);
8
- void deduplicateStreams(QPDF &qpdf);
9
- void subsetFonts(QPDF &qpdf);
10
- void stripIccProfiles(QPDF &qpdf);
11
- void stripEmbeddedFiles(QPDF &qpdf);
12
- void stripJavaScript(QPDF &qpdf);
13
- void flattenForms(QPDF &qpdf);
14
- void flattenPageTree(QPDF &qpdf);
15
- void minifyContentStreams(QPDF &qpdf);
4
+ #include "content.h"
5
+ #include "fonts.h"
6
+ #include "strip.h"
7
+ #include "structure.h"