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-shift-g": "image_viewer::FindPrevious",
|
||||
"ctrl-s": "image_viewer::SaveCopyAs",
|
||||
"ctrl-z": "image_viewer::UndoEdit",
|
||||
"ctrl-shift-t": "image_viewer::ToggleTextPanel"
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1682,7 +1682,6 @@
|
||||
"cmd-g": "image_viewer::FindNext",
|
||||
"cmd-shift-g": "image_viewer::FindPrevious",
|
||||
"cmd-s": "image_viewer::SaveCopyAs",
|
||||
"cmd-z": "image_viewer::UndoEdit",
|
||||
"cmd-shift-t": "image_viewer::ToggleTextPanel"
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1605,7 +1605,6 @@
|
||||
"ctrl-g": "image_viewer::FindNext",
|
||||
"ctrl-shift-g": "image_viewer::FindPrevious",
|
||||
"ctrl-s": "image_viewer::SaveCopyAs",
|
||||
"ctrl-z": "image_viewer::UndoEdit",
|
||||
"ctrl-shift-t": "image_viewer::ToggleTextPanel"
|
||||
},
|
||||
},
|
||||
|
||||
+582
-1100
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
|
||||
|
||||
- **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.
|
||||
- Annotation authoring, editing and deletion.
|
||||
- *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
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn clear(&mut self) {
|
||||
self.entries.clear();
|
||||
|
||||
@@ -22,9 +22,8 @@ pub mod watcher;
|
||||
pub use cache::{CacheKey, CacheablePage, DEFAULT_MEMORY_BUDGET_BYTES, PageLruCache, RenderedPage};
|
||||
pub use document::{PageDimensions, PdfDocument};
|
||||
pub use pdfium::{
|
||||
PdfAnnotationColor, PdfAnnotationInfo, PdfAnnotationKind, PdfCharBox, PdfDocumentDetails,
|
||||
PdfFormEdit, PdfFormWidget, PdfFormWidgetKind, PdfLinkAnnotation, PdfMetadata, PdfOutlineItem,
|
||||
PdfPageDetails, PdfTextSegment, PdfiumEngine,
|
||||
PdfCharBox, PdfDocumentDetails, PdfLinkAnnotation, PdfMetadata, PdfOutlineItem, PdfPageDetails,
|
||||
PdfTextSegment, PdfiumEngine,
|
||||
};
|
||||
pub use rasterizer::{LuminosityToneMapper, PageRasterizer, RasterizerOptions};
|
||||
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::document::PageDimensions;
|
||||
use crate::pdfium::compute_target_dimensions;
|
||||
use anyhow::Result;
|
||||
|
||||
/// Rendering and tone mapping configuration.
|
||||
@@ -160,7 +161,16 @@ impl PageRasterizer {
|
||||
dimensions: PageDimensions,
|
||||
options: RasterizerOptions,
|
||||
) -> 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;
|
||||
|
||||
// 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;
|
||||
|
||||
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_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_SHA="fa29823"
|
||||
PDFIUM_TAG="chromium/8066"
|
||||
|
||||
function download_and_unpack() {
|
||||
local url=$1
|
||||
@@ -192,6 +193,42 @@ function download_git() {
|
||||
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() {
|
||||
rm -rf "${app_path}/Contents/Frameworks"
|
||||
mkdir -p "${app_path}/Contents/Frameworks"
|
||||
@@ -199,6 +236,10 @@ function sign_app_binaries() {
|
||||
echo "Downloading git binary"
|
||||
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.
|
||||
cp crates/zed/contents/$channel/embedded.provisionprofile "${app_path}/Contents/"
|
||||
|
||||
|
||||
Reference in new issue
Block a user