Compare commits
8
Commits
pdf-viewer
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
935e2b0bd0 | ||
|
|
4ac68ee5af | ||
|
|
0030767614 | ||
|
|
67b9fe8b73 | ||
|
|
157c4646bb | ||
|
|
5fd4113081 | ||
|
|
5ee6aa1e50 | ||
|
|
312b37539c |
No files matched your search
@@ -1630,7 +1630,6 @@
|
|||||||
"ctrl-g": "image_viewer::FindNext",
|
"ctrl-g": "image_viewer::FindNext",
|
||||||
"ctrl-shift-g": "image_viewer::FindPrevious",
|
"ctrl-shift-g": "image_viewer::FindPrevious",
|
||||||
"ctrl-s": "image_viewer::SaveCopyAs",
|
"ctrl-s": "image_viewer::SaveCopyAs",
|
||||||
"ctrl-z": "image_viewer::UndoEdit",
|
|
||||||
"ctrl-shift-t": "image_viewer::ToggleTextPanel"
|
"ctrl-shift-t": "image_viewer::ToggleTextPanel"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1682,7 +1682,6 @@
|
|||||||
"cmd-g": "image_viewer::FindNext",
|
"cmd-g": "image_viewer::FindNext",
|
||||||
"cmd-shift-g": "image_viewer::FindPrevious",
|
"cmd-shift-g": "image_viewer::FindPrevious",
|
||||||
"cmd-s": "image_viewer::SaveCopyAs",
|
"cmd-s": "image_viewer::SaveCopyAs",
|
||||||
"cmd-z": "image_viewer::UndoEdit",
|
|
||||||
"cmd-shift-t": "image_viewer::ToggleTextPanel"
|
"cmd-shift-t": "image_viewer::ToggleTextPanel"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1605,7 +1605,6 @@
|
|||||||
"ctrl-g": "image_viewer::FindNext",
|
"ctrl-g": "image_viewer::FindNext",
|
||||||
"ctrl-shift-g": "image_viewer::FindPrevious",
|
"ctrl-shift-g": "image_viewer::FindPrevious",
|
||||||
"ctrl-s": "image_viewer::SaveCopyAs",
|
"ctrl-s": "image_viewer::SaveCopyAs",
|
||||||
"ctrl-z": "image_viewer::UndoEdit",
|
|
||||||
"ctrl-shift-t": "image_viewer::ToggleTextPanel"
|
"ctrl-shift-t": "image_viewer::ToggleTextPanel"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+542
-1060
File diff suppressed because it is too large.
Load diff
@@ -52,8 +52,9 @@ A native, high performance PDF viewing engine and GPUI workspace item engineered
|
|||||||
## 2. Scope Boundaries
|
## 2. Scope Boundaries
|
||||||
|
|
||||||
- **Continuous Scroll & Text Selection**: Supports fluid high frame rate document browsing and visual text selection.
|
- **Continuous Scroll & Text Selection**: Supports fluid high frame rate document browsing and visual text selection.
|
||||||
- **Excluded from initial scope**:
|
- **Excluded from scope**:
|
||||||
- Interactive PDF form field editing.
|
- Interactive PDF form field editing.
|
||||||
|
- Annotation authoring, editing and deletion.
|
||||||
- *Rationale*: Delivering an ultra fast, stutter free viewing experience with zero UI locks takes precedence.
|
- *Rationale*: Delivering an ultra fast, stutter free viewing experience with zero UI locks takes precedence.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -191,6 +191,26 @@ impl<T: CacheablePage> PageLruCache<T> {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if an entry with exactly this key is currently cached.
|
||||||
|
pub fn contains(&self, key: &CacheKey) -> bool {
|
||||||
|
self.entries.contains_key(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the cached page for `page_index` whose zoom is closest to `zoom_factor`,
|
||||||
|
/// ignoring LRU order, so a zoom change can keep showing the previous bitmap while a
|
||||||
|
/// fresh one renders.
|
||||||
|
pub fn closest_to_zoom(&self, page_index: usize, zoom_factor: f32) -> Option<&T> {
|
||||||
|
self.entries
|
||||||
|
.iter()
|
||||||
|
.filter(|(key, _)| key.page_index == page_index)
|
||||||
|
.min_by(|(a, _), (b, _)| {
|
||||||
|
let distance_a = (a.zoom_bucket as f32 - zoom_factor * 100.0).abs();
|
||||||
|
let distance_b = (b.zoom_bucket as f32 - zoom_factor * 100.0).abs();
|
||||||
|
distance_a.total_cmp(&distance_b)
|
||||||
|
})
|
||||||
|
.map(|(_, value)| value)
|
||||||
|
}
|
||||||
|
|
||||||
/// Clears all entries from the cache and resets memory tracking.
|
/// Clears all entries from the cache and resets memory tracking.
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.entries.clear();
|
self.entries.clear();
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ pub mod watcher;
|
|||||||
pub use cache::{CacheKey, CacheablePage, DEFAULT_MEMORY_BUDGET_BYTES, PageLruCache, RenderedPage};
|
pub use cache::{CacheKey, CacheablePage, DEFAULT_MEMORY_BUDGET_BYTES, PageLruCache, RenderedPage};
|
||||||
pub use document::{PageDimensions, PdfDocument};
|
pub use document::{PageDimensions, PdfDocument};
|
||||||
pub use pdfium::{
|
pub use pdfium::{
|
||||||
PdfAnnotationColor, PdfAnnotationInfo, PdfAnnotationKind, PdfCharBox, PdfDocumentDetails,
|
PdfCharBox, PdfDocumentDetails, PdfLinkAnnotation, PdfMetadata, PdfOutlineItem, PdfPageDetails,
|
||||||
PdfFormEdit, PdfFormWidget, PdfFormWidgetKind, PdfLinkAnnotation, PdfMetadata, PdfOutlineItem,
|
PdfTextSegment, PdfiumEngine,
|
||||||
PdfPageDetails, PdfTextSegment, PdfiumEngine,
|
|
||||||
};
|
};
|
||||||
pub use rasterizer::{LuminosityToneMapper, PageRasterizer, RasterizerOptions};
|
pub use rasterizer::{LuminosityToneMapper, PageRasterizer, RasterizerOptions};
|
||||||
pub use settings::{DefaultZoomPolicy, PageLayoutMode, PdfViewerSettings};
|
pub use settings::{DefaultZoomPolicy, PageLayoutMode, PdfViewerSettings};
|
||||||
|
|||||||
+44
-1305
File diff suppressed because it is too large.
Load diff
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
use crate::cache::RenderedPage;
|
use crate::cache::RenderedPage;
|
||||||
use crate::document::PageDimensions;
|
use crate::document::PageDimensions;
|
||||||
|
use crate::pdfium::compute_target_dimensions;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
/// Rendering and tone mapping configuration.
|
/// Rendering and tone mapping configuration.
|
||||||
@@ -160,7 +161,16 @@ impl PageRasterizer {
|
|||||||
dimensions: PageDimensions,
|
dimensions: PageDimensions,
|
||||||
options: RasterizerOptions,
|
options: RasterizerOptions,
|
||||||
) -> Result<RenderedPage> {
|
) -> Result<RenderedPage> {
|
||||||
let (width, height) = dimensions.to_pixel_size(options.zoom_factor, options.target_dpi);
|
// Mirror the native renderer's clamp: an unclamped frame at high zoom can grow
|
||||||
|
// past the page cache budget, which would make the cache refuse the entry and
|
||||||
|
// the caller re-render it forever.
|
||||||
|
let (width, height) = compute_target_dimensions(
|
||||||
|
dimensions.width,
|
||||||
|
dimensions.height,
|
||||||
|
options.target_dpi,
|
||||||
|
options.zoom_factor,
|
||||||
|
);
|
||||||
|
let (width, height) = (width.max(1) as u32, height.max(1) as u32);
|
||||||
let total_bytes = (width as usize) * (height as usize) * 4;
|
let total_bytes = (width as usize) * (height as usize) * 4;
|
||||||
|
|
||||||
// Create standard white paper background with an inner border
|
// Create standard white paper background with an inner border
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use pdf_viewer::{PageLayoutMode, PdfToolbarState, PdfView, PdfViewerSettings};
|
use pdf_viewer::rasterizer::RasterizerOptions;
|
||||||
|
use pdf_viewer::{PageLayoutMode, PdfToolbarState, PdfView, PdfViewerSettings, PdfiumEngine};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
const MINIMAL_PDF: &[u8] = b"%PDF-1.7\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\nxref\n0 4\n0000000000 65535 f \n0000000010 00000 n \n0000000060 00000 n \n0000000117 00000 n \ntrailer\n<< /Size 4 /Root 1 0 R >>\nstartxref\n185\n%%EOF\n";
|
const MINIMAL_PDF: &[u8] = b"%PDF-1.7\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\nxref\n0 4\n0000000000 65535 f \n0000000010 00000 n \n0000000060 00000 n \n0000000117 00000 n \ntrailer\n<< /Size 4 /Root 1 0 R >>\nstartxref\n185\n%%EOF\n";
|
||||||
@@ -105,3 +106,83 @@ fn test_toolbar_synchronization_with_view_state() {
|
|||||||
assert!(toolbar_state.dark_mode);
|
assert!(toolbar_state.dark_mode);
|
||||||
assert_eq!(toolbar_state.layout_mode, PageLayoutMode::Continuous);
|
assert_eq!(toolbar_state.layout_mode, PageLayoutMode::Continuous);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds a single-page PDF whose entire canvas is filled pure red, with a fully
|
||||||
|
/// computed xref table so no repair parsing is involved.
|
||||||
|
fn red_page_pdf() -> Vec<u8> {
|
||||||
|
let content = b"1 0 0 rg 0 0 612 792 re f";
|
||||||
|
let objects: Vec<Vec<u8>> = vec![
|
||||||
|
b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
|
||||||
|
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_vec(),
|
||||||
|
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << >> /Contents 4 0 R >>"
|
||||||
|
.to_vec(),
|
||||||
|
format!(
|
||||||
|
"<< /Length {} >>\nstream\n{}\nendstream",
|
||||||
|
content.len(),
|
||||||
|
String::from_utf8_lossy(content)
|
||||||
|
)
|
||||||
|
.into_bytes(),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut out = b"%PDF-1.7\n".to_vec();
|
||||||
|
let mut offsets = Vec::with_capacity(objects.len());
|
||||||
|
for (index, object) in objects.iter().enumerate() {
|
||||||
|
offsets.push(out.len() as u32);
|
||||||
|
out.extend_from_slice(format!("{} 0 obj\n", index + 1).as_bytes());
|
||||||
|
out.extend_from_slice(object);
|
||||||
|
out.extend_from_slice(b"\nendobj\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
let xref_offset = out.len() as u32;
|
||||||
|
out.extend_from_slice(format!("xref\n0 {}\n", objects.len() + 1).as_bytes());
|
||||||
|
out.extend_from_slice(b"0000000000 65535 f \n");
|
||||||
|
for offset in &offsets {
|
||||||
|
out.extend_from_slice(format!("{offset:010} 00000 n \n").as_bytes());
|
||||||
|
}
|
||||||
|
out.extend_from_slice(
|
||||||
|
format!(
|
||||||
|
"trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{}\n%%EOF",
|
||||||
|
objects.len() + 1,
|
||||||
|
xref_offset
|
||||||
|
)
|
||||||
|
.as_bytes(),
|
||||||
|
);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Guards against PDFium's native BGRA bitmap order leaking through: the rasterizer
|
||||||
|
/// must hand out RGBA buffers, so a red page must read as high-R/low-B. The mock
|
||||||
|
/// renderer also draws red, so this only proves something when a native engine
|
||||||
|
/// bound; without libpdfium the test cannot exercise the real byte order.
|
||||||
|
#[test]
|
||||||
|
fn test_native_render_outputs_rgba_channel_order() {
|
||||||
|
let engine = PdfiumEngine::new();
|
||||||
|
if !engine.is_native_available() {
|
||||||
|
eprintln!("skipping: native libpdfium not available on this machine");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rendered = engine
|
||||||
|
.render_page_from_bytes(&red_page_pdf(), 0, RasterizerOptions::default())
|
||||||
|
.expect("native render should succeed");
|
||||||
|
|
||||||
|
let stride = rendered.width as usize * 4;
|
||||||
|
assert_eq!(rendered.rgba_buffer.len(), stride * rendered.height as usize);
|
||||||
|
|
||||||
|
for (x, y) in [
|
||||||
|
(rendered.width as usize / 2, rendered.height as usize / 2),
|
||||||
|
(rendered.width as usize / 4, rendered.height as usize / 4),
|
||||||
|
(
|
||||||
|
rendered.width as usize * 3 / 4,
|
||||||
|
rendered.height as usize * 3 / 4,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let pixel = &rendered.rgba_buffer[y * stride + x * 4..y * stride + x * 4 + 4];
|
||||||
|
let (r, g, b, a) = (pixel[0], pixel[1], pixel[2], pixel[3]);
|
||||||
|
assert!(
|
||||||
|
r > 200 && b < 60,
|
||||||
|
"buffer is not RGBA-ordered at ({x},{y}): R={r} G={g} B={b} A={a}"
|
||||||
|
);
|
||||||
|
assert_eq!(a, 255, "opaque page should carry alpha 255");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -146,6 +146,7 @@ fi
|
|||||||
|
|
||||||
GIT_VERSION="v2.43.3"
|
GIT_VERSION="v2.43.3"
|
||||||
GIT_VERSION_SHA="fa29823"
|
GIT_VERSION_SHA="fa29823"
|
||||||
|
PDFIUM_TAG="chromium/8066"
|
||||||
|
|
||||||
function download_and_unpack() {
|
function download_and_unpack() {
|
||||||
local url=$1
|
local url=$1
|
||||||
@@ -192,6 +193,42 @@ function download_git() {
|
|||||||
rm -rf "$tmp_dir"
|
rm -rf "$tmp_dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function download_pdfium() {
|
||||||
|
local architecture=$1
|
||||||
|
local target_path=$2
|
||||||
|
|
||||||
|
case "$architecture" in
|
||||||
|
aarch64-apple-darwin)
|
||||||
|
pdfium_flavor="mac-arm64"
|
||||||
|
pdfium_sha256="336219e80580b93c6523f44db7dc1de59cc497b13a7390ddac84223f68ca162b"
|
||||||
|
;;
|
||||||
|
x86_64-apple-darwin)
|
||||||
|
pdfium_flavor="mac-x64"
|
||||||
|
pdfium_sha256="841ecac278cdd46288dd065873522cf72f3996560d8978f473d336f01d59942c"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported architecture for pdfium: $architecture"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# sha256sum is not present on stock macOS, so checksums go through shasum.
|
||||||
|
pdfium_tgz="${target_dir}/pdfium-${pdfium_flavor}.tgz"
|
||||||
|
if ! echo "${pdfium_sha256} ${pdfium_tgz}" | shasum -a 256 -c >/dev/null 2>&1; then
|
||||||
|
mkdir -p "${target_dir}"
|
||||||
|
curl --silent --fail --location --output "${pdfium_tgz}" \
|
||||||
|
"https://github.com/bblanchon/pdfium-binaries/releases/download/${PDFIUM_TAG}/pdfium-${pdfium_flavor}.tgz"
|
||||||
|
echo "${pdfium_sha256} ${pdfium_tgz}" | shasum -a 256 -c
|
||||||
|
fi
|
||||||
|
|
||||||
|
pdfium_tmp=$(mktemp -d)
|
||||||
|
tar -xzf "${pdfium_tgz}" -C "${pdfium_tmp}" "lib/libpdfium.dylib" "LICENSE" "licenses"
|
||||||
|
cp "${pdfium_tmp}/lib/libpdfium.dylib" "${target_path}"
|
||||||
|
mkdir -p "${app_path}/Contents/Resources/pdfium-licenses"
|
||||||
|
cp "${pdfium_tmp}/LICENSE" "${pdfium_tmp}/licenses/"* "${app_path}/Contents/Resources/pdfium-licenses/"
|
||||||
|
rm -rf "${pdfium_tmp}"
|
||||||
|
}
|
||||||
|
|
||||||
function sign_app_binaries() {
|
function sign_app_binaries() {
|
||||||
rm -rf "${app_path}/Contents/Frameworks"
|
rm -rf "${app_path}/Contents/Frameworks"
|
||||||
mkdir -p "${app_path}/Contents/Frameworks"
|
mkdir -p "${app_path}/Contents/Frameworks"
|
||||||
@@ -199,6 +236,10 @@ function sign_app_binaries() {
|
|||||||
echo "Downloading git binary"
|
echo "Downloading git binary"
|
||||||
download_git "${target_triple}" "${app_path}/Contents/MacOS/git"
|
download_git "${target_triple}" "${app_path}/Contents/MacOS/git"
|
||||||
|
|
||||||
|
# PDFium is dlopen'd at runtime by the bundled pdf_viewer for native PDF viewing.
|
||||||
|
echo "Downloading pdfium library"
|
||||||
|
download_pdfium "${target_triple}" "${app_path}/Contents/Frameworks/libpdfium.dylib"
|
||||||
|
|
||||||
# Note: The app identifier for our development builds is the same as the app identifier for nightly.
|
# Note: The app identifier for our development builds is the same as the app identifier for nightly.
|
||||||
cp crates/zed/contents/$channel/embedded.provisionprofile "${app_path}/Contents/"
|
cp crates/zed/contents/$channel/embedded.provisionprofile "${app_path}/Contents/"
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user