import { Link, Links, Page, Publication, ReadingProgression } from "@readium/shared"; export enum Orientation { auto = "auto", landscape = "landscape", portrait = "portrait", } export enum Spread { auto = "auto", both = "both", none = "none", landscape = "landscape", } export class FXLSpreader { shift = true; // TODO getter private spreads: Link[][] = []; nLandscape: number = 0; // TODO getter constructor(publication: Publication) { this.index(publication); this.testShift(publication); console.log(`Indexed ${this.spreads.length} spreads for ${publication.readingOrder.items.length} items`); } private index(publication: Publication, redo = false) { this.nLandscape = 0; publication.readingOrder.items.forEach((item, index) => { // item.Properties.Spread = item.Properties.Spread ? item.Properties.Spread : "landscape"; // TODO Maybe default to auto instead if(!redo) { publication.readingOrder.items[index] = item.addProperties({ "number": index + 1, "isImage": item.type?.indexOf("image/") === 0, }); // if(!orientation) item.Properties.Orientation = item.Width > item.Height ? "landscape" : "portrait"; } const isLandscape = item.properties?.otherProperties["orientation"] === Orientation.landscape ? true : false; if((!item.properties?.page || redo)) item.properties = item.properties?.add({ "page": isLandscape ? // If a landscape image "center" : // Center it ((((this.shift ? 0 : 1) + index - this.nLandscape) % 2) ? (publication.metadata.readingProgression === ReadingProgression.rtl ? "right" : "left") : (publication.metadata.readingProgression === ReadingProgression.rtl ? "left" : "right")) }) if(isLandscape || item.properties?.otherProperties["addBlank"]) this.nLandscape++; }); if(redo) this.spreads = []; this.buildSpreads(publication.readingOrder); } private testShift(publication: Publication) { let wasLastSingle = false; this.spreads.forEach((item, index) => { if(item.length > 1) return; // Only left with single-page "spreads" const single = item[0]; const orientation = single.properties?.otherProperties["orientation"]; // First page is landscape/spread means no shift if(index === 0 && (orientation === Orientation.landscape || (orientation !== Orientation.portrait && ((single.width || 0) > (single.height || 0) || single.properties?.otherProperties["spread"] === Spread.both)))) this.shift = false; // If last was a true single, and this spread is a center page (that's not special), something's wrong if(wasLastSingle && single.properties?.page === Page.center) { this.spreads[index - 1][0].addProperties({"addBlank": true}); /*if(single.findFlag("final")) this.nLandscape++;*/ } // If this single page spread is an orphaned component of a double page spread (and it's not the first page) if(orientation === Orientation.portrait && single.properties?.page !== "center" && single.properties?.otherProperties["number"] > 1) wasLastSingle = true; else wasLastSingle = false; }); if(!this.shift) this.index(publication, true); // Re-index spreads } private buildSpreads(spine: Links) { let currentSet: Link[] = []; spine.items.forEach((item, index) => { if(!index && this.shift) { this.spreads.push([item]); } else if(item.properties?.page === Page.center) { // If a center (single) page spread, push immediately and reset current set if(currentSet.length > 0) this.spreads.push(currentSet); this.spreads.push([item]); currentSet = []; } else if (currentSet.length >= 2) { // Spread has max 2 pages this.spreads.push(currentSet); currentSet = [item]; } else // Add this item to current set currentSet.push(item); }); if(currentSet.length > 0) this.spreads.push(currentSet); } currentSpread(currentSlide: number, perPage: number): Link[] { return this.spreads[Math.min(Math.floor(currentSlide / perPage), this.spreads.length - 1)]; } findByLink(link: Link): Link[] | undefined { return this.spreads.find((spread) => spread.includes(link)) || undefined; } }