Reworks how the image sizes in the book reader gets handled (#1872)

* Fixes bus in vertical writing mode.

* Reworks how the images gets resized. They should be more adaptive and pages with single images will get centered.

* Adds back an unintended change

* Revert "Fixes bus in vertical writing mode."

This reverts commit a1cf9675def45fd626a149d90f573104b5d280cd.

* Added debounce and timeout to the updateImageSize to prevent it from getting run before the page loads

* Fixes semicolons missing semicolons

* Improves the detection in checkSingleImagePage.
This commit is contained in:
CKolle 2023-03-11 02:40:27 +01:00 committed by GitHub
parent e5e2d02e04
commit 1f8b591ed5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 94 additions and 40 deletions

View File

@ -212,7 +212,6 @@ $action-bar-height: 38px;
height: calc((var(--vh) * 100) - calc($action-bar-height)); // * 2
&.writing-style-vertical {
height: auto;
padding: 0 10px 0 0;
margin: 20px 0;
}
@ -222,12 +221,15 @@ $action-bar-height: 38px;
height: calc((var(--vh) * 100) - calc($action-bar-height)); // * 2
&.writing-style-vertical {
height: auto;
padding: 0 10px 0 0;
margin: 20px 0;
}
}
&.writing-style-vertical {
height: auto;
}
// &.immersive {
// // Note: I removed this for bug: https://github.com/Kareadita/Kavita/issues/1726
// //height: calc((var(--vh, 1vh) * 100) - $action-bar-height);
@ -301,11 +303,18 @@ $action-bar-height: 38px;
::ng-deep .kavita-scale-width-container {
width: auto;
max-height: calc(var(--book-reader-content-max-height) - ($action-bar-height)) !important;
max-width: calc(var(--book-reader-content-max-width)) !important;
position: var(--book-reader-content-position) !important;
top: var(--book-reader-content-top) !important;
left: var(--book-reader-content-left) !important;
transform: var(--book-reader-content-transform) !important;
}
// This is applied to images in the backend
::ng-deep .kavita-scale-width {
max-width: 100%;
max-height: calc(var(--book-reader-content-max-height) - ($action-bar-height)) !important;
max-width: calc(var(--book-reader-content-max-width)) !important;
object-fit: contain;
object-position: top center;
break-inside: avoid;

View File

@ -122,7 +122,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
* TODO: See if continuousChaptersStack can be moved into reader service so we can reduce code duplication between readers (and also use ChapterInfo with it instead)
*/
continuousChaptersStack: Stack<number> = new Stack();
/*
* The current page only contains an image. This is used to determine if we should show the image in the center of the screen.
*/
isSingleImagePage = false;
/**
* Belongs to the drawer component
*/
@ -153,6 +156,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
clickToPaginateVisualOverlay = false;
clickToPaginateVisualOverlayTimeout: any = undefined; // For animation
clickToPaginateVisualOverlayTimeout2: any = undefined; // For kicking off animation, giving enough time to render html
updateImageSizeTimeout: any = undefined;
/**
* This is the html we get from the server
*/
@ -372,17 +376,14 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (this.layoutMode !== BookPageLayoutMode.Default || this.writingStyle === WritingStyle.Vertical) {
// Take the height after page loads, subtract the top/bottom bar
const height = this.windowHeight - (this.topOffset * 2);
this.document.documentElement.style.setProperty('--book-reader-content-max-height', `${height}px`);
return height + 'px';
}
return 'unset';
}
get VerticalBookContentWidth() {
if (this.layoutMode !== BookPageLayoutMode.Default && this.writingStyle !== WritingStyle.Horizontal ) {
const width = this.getVerticalPageWidth()
this.document.documentElement.style.setProperty('--book-reader-content-max-width', `${width}px`);
return width + 'px';
}
return '';
@ -592,8 +593,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.libraryType = type;
});
this.updateImagesWithHeight();
this.updateImageSizes();
if (this.pageNum >= this.maxPages) {
this.pageNum = this.maxPages - 1;
@ -637,6 +637,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
onResize(){
// Update the window Height
this.updateWidthAndHeightCalcs();
this.updateImageSizes();
const resumeElement = this.getFirstVisibleElementXPath();
if (this.layoutMode !== BookPageLayoutMode.Default && resumeElement !== null && resumeElement !== undefined) {
this.scrollTo(resumeElement); // This works pretty well, but not perfect
@ -836,7 +837,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.cdRef.markForCheck();
this.bookService.getBookPage(this.chapterId, this.pageNum).pipe(take(1)).subscribe(content => {
this.isSingleImagePage = this.checkSingleImagePage(content) // This needs be performed before we set this.page to avoid image jumping
this.updateSingleImagePageStyles()
this.page = this.domSanitizer.bypassSecurityTrustHtml(content); // PERF: Potential optimization to prefetch next/prev page and store in localStorage
this.cdRef.markForCheck();
setTimeout(() => {
@ -854,40 +859,76 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
.map(img => new Promise(resolve => { img.onload = img.onerror = resolve; })))
.then(() => {
this.setupPage(part, scrollTop);
this.updateImagesWithHeight();
this.updateImageSizes();
});
}, 10);
});
}
/**
* Applies a max-height inline css property on each image in the page if the layout mode is column-based, else it removes the property
* Updates the image properties to fit the current layout mode and screen size
*/
updateImagesWithHeight() {
const images = this.readingSectionElemRef?.nativeElement.querySelectorAll('img') || [];
let maxHeight: number | undefined;
updateImageSizes() {
const isVerticalWritingStyle = this.writingStyle === WritingStyle.Vertical;
const height = this.windowHeight - (this.topOffset * 2);
let maxHeight = 'unset';
let maxWidth = '';
switch (this.layoutMode) {
case BookPageLayoutMode.Default:
if (isVerticalWritingStyle) {
maxHeight = `${height}px`;
} else {
maxWidth = `${this.getVerticalPageWidth()}px`;
}
break
if (this.layoutMode !== BookPageLayoutMode.Default && this.writingStyle !== WritingStyle.Vertical) {
maxHeight = (parseInt(this.ColumnHeight.replace('px', ''), 10) - (this.topOffset * 2));
} else if (this.layoutMode !== BookPageLayoutMode.Column2 && this.writingStyle === WritingStyle.Vertical) {
maxHeight = this.getPageHeight() - COLUMN_GAP;
} else if (this.layoutMode === BookPageLayoutMode.Column2 && this.writingStyle === WritingStyle.Vertical) {
maxHeight = this.getPageHeight() / 2 - COLUMN_GAP;
} else {
maxHeight = undefined;
case BookPageLayoutMode.Column1:
maxHeight = `${height}px`;
maxWidth = `${this.getVerticalPageWidth()}px`;
break
case BookPageLayoutMode.Column2:
maxWidth = `${this.getVerticalPageWidth()}px`;
if (isVerticalWritingStyle && !this.isSingleImagePage) {
maxHeight = `${height / 2}px`;
} else {
maxHeight = `${height}px`;
}
break
}
Array.from(images).forEach(img => {
if (maxHeight === undefined) {
this.renderer.removeStyle(img, 'max-height');
} else if (this.writingStyle === WritingStyle.Horizontal) {
this.renderer.setStyle(img, 'max-height', `${maxHeight}px`);
} else {
const aspectRatio = img.width / img.height;
const pageWidth = this.getVerticalPageWidth()
const maxImgHeight = Math.min(maxHeight, pageWidth / aspectRatio);
this.renderer.setStyle(img, 'max-height', `${maxImgHeight}px`);
}
});
this.document.documentElement.style.setProperty('--book-reader-content-max-height', maxHeight);
this.document.documentElement.style.setProperty('--book-reader-content-max-width', maxWidth);
}
updateSingleImagePageStyles() {
if (this.isSingleImagePage && this.layoutMode !== BookPageLayoutMode.Default) {
this.document.documentElement.style.setProperty('--book-reader-content-position', 'absolute');
this.document.documentElement.style.setProperty('--book-reader-content-top', '50%');
this.document.documentElement.style.setProperty('--book-reader-content-left', '50%');
this.document.documentElement.style.setProperty('--book-reader-content-transform', 'translate(-50%, -50%)');
} else {
this.document.documentElement.style.setProperty('--book-reader-content-position', '');
this.document.documentElement.style.setProperty('--book-reader-content-top', '');
this.document.documentElement.style.setProperty('--book-reader-content-left', '');
this.document.documentElement.style.setProperty('--book-reader-content-transform', '');
}
}
checkSingleImagePage(content: string) {
// Exclude the style element from the HTML content as it messes up innerText
const htmlContent = content.replace(/<style>.*<\/style>/s, '');
const parser = new DOMParser();
const doc = parser.parseFromString(htmlContent, 'text/html');
const html = doc.querySelector('html');
if (html?.innerText.trim() !== '') {
return false;
}
const images = doc.querySelectorAll('img, svg');
return images.length === 1;
}
@ -1166,9 +1207,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
const resumeElement: string | null | undefined = this.getFirstVisibleElementXPath();
// Needs to update the image size when reading mode is vertically
if (this.writingStyle === WritingStyle.Vertical) {
this.updateImagesWithHeight();
}
this.updateImageSizes();
// Line Height must be placed on each element in the page
// Apply page level overrides
@ -1335,11 +1375,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
updateWritingStyle(writingStyle: WritingStyle) {
this.writingStyle = writingStyle;
setTimeout(() => this.updateImageSizes());
if (this.layoutMode !== BookPageLayoutMode.Default) {
const lastSelector = this.lastSeenScrollPartPath;
setTimeout(() => {
this.scrollTo(lastSelector);
this.updateLayoutMode(this.layoutMode);
});
} else if (this.bookContentElemRef !== undefined) {
const resumeElement = this.getFirstVisibleElementXPath();
@ -1356,8 +1396,13 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
const layoutModeChanged = mode !== this.layoutMode;
this.layoutMode = mode;
this.cdRef.markForCheck();
// Remove any max-heights from column layout
this.updateImagesWithHeight();
this.clearTimeout(this.updateImageSizeTimeout);
this.updateImageSizeTimeout = setTimeout( () => {
this.updateImageSizes()
}, 200);
this.updateSingleImagePageStyles()
// Calulate if bottom actionbar is needed. On a timeout to get accurate heights
if (this.bookContentElemRef == null) {